Modal Bottom Sheet hidden by keyboard in TabBarView
Solution 1
Thanks to Stewie, I realized that MediaQuery.of(context).viewInsets.bottom
wasn't registering the appearance of the keyboard.
I was able to solve the issue by passing in the BuildContext
from the page with DefaultTabController
to the TabView
page. Using this BuildContext
resolved the issue.
Code below:
ContractorServiceDetailsPage.dart
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: UtilWidget.getLogo(),
bottom: TabBar(
labelColor: Colors.deepPurpleAccent,
unselectedLabelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
indicator: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10)),
color: Colors.white),
tabs: [
UtilWidget.addTab('Details'),
UtilWidget.addTab('Procedures'),
],
),
),
body: TabBarView(
children: <Widget>[
EditServiceFragment(contractorServiceId: widget.contractorServiceId),
ServiceProceduresFragment(
baseContext: context,
contractorServiceId: widget.contractorServiceId
)
],
),
)
);
}
ServiceProceduresFragment.dart
class ServiceProceduresFragment extends StatefulWidget {
final BuildContext baseContext;
final int contractorServiceId;
const ServiceProceduresFragment({
Key key,
this.contractorServiceId,
this.baseContext}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _ServiceProceduresState();
}
}
class _ServiceProceduresState extends State<ServiceProceduresFragment> {
...
@override
Widget build(BuildContext context) {
return Scaffold(
primary: false,
resizeToAvoidBottomPadding: false,
key: _scaffoldKey,
body: _buildPage(),
floatingActionButton: new FloatingActionButton.extended(
onPressed: () {
_addProcedureBottomSheet(widget.baseContext);
},
label: Text('Add Procedure'),
icon: new Icon(Icons.add),
),
);
}
}
Solution 2
You can check the value of MediaQuery.of(context).viewInsets.bottom
to know whether the keyboard is active and put some space to the bottom of the modal.
Also, you can use AnimatedPadding
to make it look smooth.
void _addProcedureBottomSheet(BuildContext context) {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
var keyboardHeight = MediaQuery.of(context).viewInsets.bottom ?? 0.0;
return AnimatedPadding(
padding: EdgeInsets.only(bottom: keyboardHeight),
duration: Duration(milliseconds: 300),
child: SafeArea(
bottom: keyboardHeight <= 0.0,
child: SingleChildScrollView(
padding: EdgeInsets.only(
top: 10,
left: 15,
right: 15,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Text(
'Add Procedure to Service',
style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
),
_addProcedureForm()
],
),
),
),
);
},
);
}
Comments
-
1000Suns over 1 year
I am having an issue where the keyboard covers and hides the bottom sheet.
I tried a number of solutions but nothing seems to work. I am not too sure what is causing the problem. My guess is that it has something to do with either nested
Scaffold
and/or using bottom sheet inTabBarView
.Any help would be greatly appreciated.
I tried to include as much detail as possible. If you need anything else, please let me know.
Code below:
DetailsPage.dart
@override Widget build(BuildContext context) { return DefaultTabController( length: 2, child: Scaffold( key: _scaffoldKey, appBar: AppBar( centerTitle: true, title: UtilWidget.getLogo(), bottom: TabBar( labelColor: Colors.deepPurpleAccent, unselectedLabelColor: Colors.white, indicatorSize: TabBarIndicatorSize.label, indicator: BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(10), topRight: Radius.circular(10)), color: Colors.white), tabs: [ UtilWidget.addTab('Details'), UtilWidget.addTab('Procedures'), ], ), ), body: TabBarView( children: <Widget>[ EditServiceFragment(contractorServiceId: widget.contractorServiceId), ServiceProceduresFragment(contractorServiceId: widget.contractorServiceId) ], ), ) ); }
ServiceProceduresFragment.dart
@override Widget build(BuildContext context) { return Scaffold( primary: false, resizeToAvoidBottomInset: true, key: _scaffoldKey, body: _buildPage(), floatingActionButton: new FloatingActionButton.extended( onPressed: () { _addProcedureBottomSheet(context); }, label: Text('Add Procedure'), icon: new Icon(Icons.add), ), ); } Widget _buildPage() { return FutureBuilder( future: _loadedContractorService, builder: (BuildContext context, AsyncSnapshot<ContractorService> serviceRes) { if(serviceRes.hasError) { print('Error while loading Asset - EditService'); print(serviceRes.error); return UtilWidget.pageLoadError( "SERVICE PROCEDURES", "An Error has occurred", "Got an error getting Contractor Service from the API." ); } if (serviceRes.connectionState == ConnectionState.waiting) return UtilWidget.progressIndicator(); ContractorService loadedService = serviceRes.data; List<Procedure> procedures = loadedService.procedures; if(procedures.length == 0) return UtilWidget.emptyListView('SERVICE PROCEDURES', 'No procedures were found. Add one.'); return SingleChildScrollView( child: ListView.builder( shrinkWrap: true, itemCount: procedures.length + 1, itemBuilder: (context, index) { if(index == 0){ return UtilWidget.pageHeader('SERVICE PROCEDURES'); } int listIndex = index - 1; Procedure procedure = procedures[listIndex]; return Card( elevation: 2, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)), margin: EdgeInsets.only(top: 10, bottom: 10, left: 20, right: 20), child: Theme( data: ThemeData().copyWith( dividerColor: Colors.transparent, accentColor: Colors.deepPurpleAccent ), child: ExpansionTile( title: ListTile( title: Text( procedure.name, style: TextStyle(fontWeight: FontWeight.bold) ), subtitle: Text( 'Requirements: ' + procedure.requirements.length.toString() ), ), childrenPadding: EdgeInsets.only(left: 20, right: 20), children: [ UtilWidget.addRowWithText('Notes', FontWeight.bold), UtilWidget.addRowWithText(procedure.notes, FontWeight.normal), _deleteProcedureBtn(procedure.id), Padding( padding: const EdgeInsets.only(top: 10), child: UtilWidget.addRowWithText('Requirements', FontWeight.bold), ), for(var req in procedure.requirements) UtilWidget.addRowWithText(req.name, FontWeight.normal) , _addRequirementBtn(procedure.id) ] ) ) ); } ) ); } ); } void _addProcedureBottomSheet(BuildContext context) { showModalBottomSheet( isScrollControlled: true, context: context, builder: (BuildContext bc) { return SafeArea( child: SingleChildScrollView( padding: EdgeInsets.only( top: 10, left:15, right:15, bottom: MediaQuery.of(context).viewInsets.bottom ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Center( child: Text( 'Add Procedure to Service', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18 ), ), ), _addProcedureForm() ], ), ) ); } ); } Form _addProcedureForm() { return Form( key: _formKey, child: Column( children: <Widget>[ TextFormField( validator: (value) { if(value.isEmpty) { return 'Service name is required.'; } return null; }, controller: _name, decoration: InputDecoration( labelText: 'Name', hintText: "Specific/applicable procedure", icon: Icon(Icons.edit, color: Colors.deepPurple) ) ), TextFormField( validator: (value) { if(value.isEmpty) { return 'Service description is required.'; } return null; }, maxLines: null, keyboardType: TextInputType.multiline, controller: _notes, decoration: InputDecoration( labelText: 'Description', hintText: "Applicable procedure description.", icon: Icon(Icons.notes, color: Colors.deepPurple) ) ), Container( margin: const EdgeInsets.only(top: 20.0, bottom: 10), child: SizedBox( width: double.maxFinite, child: RaisedButton( color: Colors.deepPurple, textColor: Colors.white, padding: EdgeInsets.all(5), child: Text('Save Procedure', style: TextStyle(fontSize: 15) ), onPressed: () { if(_formKey.currentState.validate()) { _submitProcedureForm(); } }, ), ), ) ], ), ); } Widget _addRequirementBtn(int procedureId) { return Padding( padding: const EdgeInsets.only(top: 10.0, bottom: 10), child: RaisedButton( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)), color: Colors.deepPurple, textColor: Colors.white, child: Text('Add Requirement', style: TextStyle(fontSize: 14) ), onPressed: () { _addProcedureRequirementBottomSheet(procedureId, context); } ) ); }
Outputs
-
1000Suns about 3 yearsHey Stewie, This did not work but it helped me come to a solution. I tried what you suggest but keyboardHeight always returned 0.0. It seems that for some reason, MediaQuery.of(context).viewInsets.bottom wasn't registering when the keyboard was displayed.