Last active
October 22, 2025 18:01
-
-
Save jamilsaadeh97/98fa12eb07038a4be724c103c42a038b to your computer and use it in GitHub Desktop.
Bottom sheet with text fields
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| // This is an example of a bottom sheet with a text field inside a tab | |
| // and a footer with another text field. | |
| // Please use a tablet simulator on landscape mode to better see the issue. | |
| // When the text field of tab 1 is pressed and focused, the keyboard appears and | |
| // resizes the bottom sheet correctly. However, the footer go over the whole content of tab 1 which | |
| // causes the user to not see what's being written. | |
| // I came up with several workarounds but I don't feel it's the right way to handle that kind of situation. | |
| // It really felt too "hacky" | |
| // My current workarounds: | |
| // 1- I tried switching the visibility off of the footer when the tab1's text field is focused. | |
| // 2- I tried addind a "resizeToAvoidBottomInset: false" to the Scaffold of the bottom sheet and | |
| // adding a listener to the focus node of the footer text field. | |
| // Whenever it is focused, I set the bottom padding to MediaQuery.viewInsetsOf(context).bottom | |
| // This way, only when the footer text field is focused, the footer is "lifted up" to avoid the keyboard. | |
| // When the tab1's text field is focused, nothing is being resized. | |
| // Any suggestions or pointers would be appreciated. Thank you | |
| void main() { | |
| runApp(const MyApp()); | |
| } | |
| class MyApp extends StatelessWidget { | |
| const MyApp({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| title: 'Flutter Demo', | |
| theme: ThemeData.dark(), | |
| home: const MyHomePage(title: 'Bottom sheet demo'), | |
| ); | |
| } | |
| } | |
| class MyHomePage extends StatefulWidget { | |
| const MyHomePage({super.key, required this.title}); | |
| final String title; | |
| @override | |
| State<MyHomePage> createState() => _MyHomePageState(); | |
| } | |
| class _MyHomePageState extends State<MyHomePage> { | |
| void _onPressed() { | |
| showModalBottomSheet( | |
| context: context, | |
| useSafeArea: true, | |
| isScrollControlled: true, | |
| isDismissible: true, | |
| builder: (context) => SomeBottomSheet(), | |
| ); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar(title: Text(widget.title)), | |
| body: Center( | |
| child: Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: <Widget>[ | |
| ElevatedButton( | |
| onPressed: _onPressed, | |
| child: Text("Open bottom sheet"), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class SomeBottomSheet extends StatelessWidget { | |
| const SomeBottomSheet({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return GestureDetector( | |
| onTap: () => FocusManager.instance.primaryFocus?.unfocus(), | |
| child: Scaffold( | |
| body: SafeArea( | |
| child: Padding( | |
| padding: const EdgeInsets.all(16.0), | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| children: [ | |
| Expanded(child: SomeBody()), | |
| SomeFooter(), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class SomeBody extends StatelessWidget { | |
| const SomeBody({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return DefaultTabController( | |
| length: 2, | |
| child: Column( | |
| children: [ | |
| TabBar( | |
| tabs: [ | |
| Tab(text: "Tab 1"), | |
| Tab(text: "Tab 2"), | |
| ], | |
| ), | |
| Expanded( | |
| child: Padding( | |
| padding: const EdgeInsets.all(8.0), | |
| child: TabBarView(children: [Tab1Content(), Tab2Content()]), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| class Tab1Content extends StatelessWidget { | |
| const Tab1Content({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return SingleChildScrollView( | |
| child: Column( | |
| spacing: 20, | |
| children: [ | |
| TextField(decoration: InputDecoration(labelText: "Some label")), | |
| ElevatedButton(onPressed: () {}, child: Text("Submit")), | |
| ], | |
| ), | |
| ); | |
| } | |
| } | |
| /// Demonstration purposes only - close to my real use case | |
| class Tab2Content extends StatelessWidget { | |
| const Tab2Content({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return ListView.separated( | |
| itemCount: 20, | |
| separatorBuilder: (context, index) => Divider(), | |
| itemBuilder: (context, index) => | |
| ListTile(title: Text("Item ${index + 1}")), | |
| ); | |
| } | |
| } | |
| class SomeFooter extends StatelessWidget { | |
| const SomeFooter({super.key}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return DecoratedBox( | |
| decoration: BoxDecoration( | |
| color: Colors.black45, | |
| borderRadius: BorderRadius.circular(12), | |
| ), | |
| child: Padding( | |
| padding: const EdgeInsets.all(8.0), | |
| child: Column( | |
| children: [ | |
| TextField(decoration: InputDecoration(labelText: "Footer input")), | |
| SizedBox(height: 75), // This is a placeholder for any content. | |
| ElevatedButton(onPressed: () {}, child: Text("Footer button")), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment