SafeArea is being ignored in modal bottom sheet - flutter

Just noticed that the top SafeArea is being ignored when showModalBottomSheet is set to isScrollControlled: true.(I want my modal to fit the screen) Anyone found a way to fix it?
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => MyModal(),
)
My test modal
class MyModal extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
children: <Widget>[
Text('Modal'),
Expanded(
child: Container(
color: Colors.red,
))
],
),
);
}
}
OUTPUT
I kinda temporary fixed it by setting height of modal content
SafeArea(
child: Container(
height: MediaQuery.of(context).size.height - 80,
child: Column(
children: ...
OUTPUT

If you're using GetX
Try it
final padding = MediaQuery.of(Get.context!).viewPadding;
Else you can pass parent context as param
final padding = MediaQuery.of(parentContext).viewPadding;

Related

Safearea not working in showModalBottomSheet

Safearea() does not wrap the showModalBottomSheet properly. I need to show the modal under the status bar.
class ModalBottomSheet {
static void renderModalBottomSheet(BuildContext context, Widget widget) {
showModalBottomSheet(
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(border2),
),
),
context: context,
builder: (BuildContext context) {
return SafeArea(
child: Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: widget,
),
);
},
);
}
}
I have tried the following solutions but it still doesn't work properly
MediaQuery.of(context).padding.top
MediaQueryData.fromWindow(WidgetsBinding.instance.window).padding.top
Update:
I managed to solve it this way.
add this to the bottomsheet
backgroundColor: Colors.transparent,
and padding top
top: MediaQuery.of(context).padding.top,
full code:
class ModalBottomSheet {
static void renderModalBottomSheet(BuildContext context, Widget widget) {
showModalBottomSheet(
isScrollControlled: true,
backgroundColor: Colors.transparent,
context: context,
builder: (_) {
return SafeArea(
child: Padding(
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top,
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: widget,
),
);
},
);
}
}
your child widget can have the border radius and colors instead.
open keyboard
closed keyboard
Try this. If it doesn't work, refer this page
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[...
you should get MediaQuery.of(context).viewPadding.top before showModalBottomSheet and pass it to sheet inside, then use EdgeInsets.top inside the sheet.

Overflow when using viewinsets in a modalButtomSheet

Problem:
I'm using MediaQuery and viewInsets to add Padding, when the user
triggers the keyboard in a modalBottomSheet.
It looks OK, but I get a message about overflow
When I draw down the modalBottomSheet manually, I can see the overflow happening behind the sheet.
Code first, then screenshots:
This is the GestureDetector opening the modal sheet:
GestureDetector(
onTap: () {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(23.r),
),
),
isScrollControlled: true,
context: context,
builder: (bctx) => StatefulBuilder(builder:
(BuildContext context, StateSetter setModalState) {
return ModalAddFavorite();
}));
},
This is the Widegt that I use as modal sheeet:
class ModalAddFavorite extends StatefulWidget {
const ModalAddFavorite({Key? key}) : super(key: key);
#override
_ModalAddFavoriteState createState() => _ModalAddFavoriteState();
}
class _ModalAddFavoriteState extends State<ModalAddFavorite> {
#override
Widget build(BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setModalState) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom * 0.98.h),
//
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 20.h,
),
Container(
width: 80.w,
height: 6.w,
decoration: BoxDecoration(
color: Provider.of<CustomColors>(context, listen: false)
.customColorScheme['Grey 2'],
borderRadius: BorderRadius.circular(6.r),
),
),
SizedBox(
height: 25.h,
),
//
Text(
'ADD A FAVORITE',
style: Provider.of<CustomTextStyle>(context)
.customTextStyle('ModalHeader'),
),
SizedBox(
height: 25.5.h,
),
//
//
InputFieldAddFavorite(),
SizedBox(
height: 40.h,
)
],
),
),
);
});
}
}
Screenshots:
Modal Sheet open / keyboard inactive / no overflow
Modal sheet open / keyboard active / overflow warning in Flutter
Modal shett pulled back manually // overflow visible behind the sheet:
Try to add physics: NeverScrollableScrollPhysics() under your SingleChildScrollView().
Issue solved: Instead of wrapping the modal sheet in a SingleChildScrollView, I needed to wrap the Column that contains the page itself.

flutter - chat's input box keyboard pushing message-list upwards

I have a fairly simple flutter app. It has a chat feature.
However, I have a problem with the chat feature.
It's made up of a widget does Scaffold and in it SingleChildScrollView - which has a message-list (container) and input-area (container). Code is attached.
Problem is: if I click on the input box, the keyboard opens and it pushes the message-list.
Pushing the message-list is an acceptable thing if you are already at the bottom of the message-list.
However, if the user scrolled up and saw some old messages, I don't want the message-list widget to be pushed up.
Also, I don't want the message-list to be pushed up if I have only a handful of messages (because that just makes the messages disappear when keyboard opens, and then I need to go and scroll to the messages that have been pushed [user is left with 0 visible messages until they scroll]).
I tried different approaches - like
resizeToAvoidBottomInset: false
But nothing seems to work for me, and this seems like it should be a straightforward behavior (for example, whatsapp act like the desired behavior).
My only option I fear is to listen to keyboard opening event, but I was hoping for a more elegant solution.
Here's my code:
#override
Widget build(BuildContext context) {
height = MediaQuery.of(context).size.height;
width = MediaQuery.of(context).size.width;
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(height: height * 0.1),
buildMessageList(), // container
buildInputArea(context), // container
],
),
),
);
Widget buildInputArea(BuildContext context) {
return Container(
height: height * 0.1,
width: width,
child: Row(
children: <Widget>[
buildChatInput(),
buildSendButton(context),
],
),
);
}
Widget buildMessageList() {
return Container(
height: height * 0.8,
width: width,
child: ListView.builder(
controller: scrollController,
itemCount: messages.length,
itemBuilder: (BuildContext context, int index) {
return buildSingleMessage(index);
},
),
);
}
This seems to work for me:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ScrollController _controller = ScrollController();
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
buildMessageList(),
buildInputArea(context),
],
),
),
);
}
Widget buildInputArea(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: TextField(),
),
RaisedButton(
onPressed: null,
child: Icon(Icons.send),
),
],
);
}
Widget buildMessageList() {
return Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: 50,
controller: _controller,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.all(10),
child: Container(
color: Colors.red,
height: 20,
child: Text(index.toString()),
),
);
},
),
);
}
}
I think the problem is that you are using fixed sizes for all widgets. In this case it is better to use Expanded for the ListView and removing the SingleChildScrollView. That way the whole Column won't scroll, but only the ListView.
Try to use Stack:
#override
Widget build(BuildContext context) {
height = MediaQuery.of(context).size.height;
width = MediaQuery.of(context).size.width;
return Scaffold(
body: Stack(
children: <Widget>[
SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(height: height * 0.1),
buildMessageList(),
],
),
),
Positioned(
bottom: 0.0,
child: buildInputArea(context),
),
],
),
);
}
Setting resizeToAvoidBottomInset property to false in your Scaffold should work.
You can use NotificationListener to listen to scroll notifications to detect that user is at the bottom of the message-list. If you are at the bottom you can then set resizeToAvoidBottomInset to true.
Something like this should work
final resizeToAvoidBottomInset = true;
_onScrollNotification (BuildContext context, ScrollNotification scrollNotification) {
if (scrollNotification is ScrollUpdateNotification) {
// detect scroll position here
// and set resizeToAvoidBottomInset to false if needed
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: this.resizeToAvoidBottomInset,
body: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
return _onScrollNotification(context, scrollNotification);
},
child: SingleChildScrollView(
child: Column(
children: <Widget>[
buildMessageList(), // container
buildInputArea(context), // container
],
),
),
),
);
}
this is technically already answered, and the answer is almost correct. However, I have found a better solution to this. Previously the author mentions that he wants to have a similar experience to WhatsApp. By using the previous solution, the listview would not be able to scrolldown to maxExtent when the sent button is pressed. To fix this I implemented Flex instead of Expanded, and use a singlechildscrollview for the input area
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ScrollController _controller = ScrollController();
TextEditingController _textcontroller=TextEditingController();
List<String> messages=[];
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: ListView.builder(
shrinkWrap: true,
itemCount: messages.length,
controller: _controller,
itemBuilder: (BuildContext context, int index) {
print("From listviewbuilder: ${messages[index]}");
return Padding(
padding: EdgeInsets.all(10),
child: Container(
color: Colors.red,
height: 20,
child: Text(messages[index])
),
);
},
),
),
SingleChildScrollView(
child: Row(
children: <Widget>[
Expanded(
child: TextField(controller: _textcontroller),
),
RaisedButton(
onPressed: (){
Timer(Duration(milliseconds: 100), () {
_controller.animateTo(
_controller.position.maxScrollExtent,
//scroll the listview to the very bottom everytime the user inputs a message
curve: Curves.easeOut,
duration: const Duration(milliseconds: 300),
);
});
setState(() {
messages.add(_textcontroller.text);
});
print(messages);
},
child: Icon(Icons.send),
),
],
),
),
],
),
),
);
}
}
It's better to use flex because expanded as the documentation says, expands over available space, whereas flex would resize to the appropriate proportion. This way if you are going for the "WhatsApp experience" in which the listview scrolls down once you sent a message. The listview would resize when the keyboard pops up and you will get to the bottom, instead of it not going fully to the bottom.

No MediaQuery ancestor could be found?

I'm trying to build a Container that is a third of size of the page but i'm getting an error No MediaQuery ancestor could be found starting from the context that was passed to MediaQuery.of(). and I'm not sure why at all. Its in MaterialApp.
My Code:
import 'package:flutter/material.dart';
void main() => runApp(LoginPage());
class LoginPage extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
home: Scaffold(
body: Container(
constraints: BoxConstraints.expand(),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
color: Colors.red,
width: double.infinity,
height: MediaQuery.of(context).size.height/3,
)
],
)
)
)
);
}
}
You just have to give the MaterialApp as the ancestor..as the error says..
Do it like this..
void main() => runApp(MaterialApp(home:LoginPage()));
Your must have a MaterialApp widget because you are using the Material class from your import statement
Check the code below, it works fine:
import 'package:flutter/material.dart';
// wrap your LoginPage widget with a MaterialApp widget
void main() => runApp(MaterialApp(home:LoginPage()));
class LoginPage extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
home: Scaffold(
body: Container(
constraints: BoxConstraints.expand(),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
color: Colors.red,
width: double.infinity,
height: MediaQuery.of(context).size.height/3,
)
],
)
)
)
);
}
}
None of the solutions worked for me. It was because the showModalBottomSheet tries to access the ancestor of type MaterialApp from the given context.
Use Builder widget to get new context with MaterialApp ancestor
OR
Separate your MaterialAapp and Scaffold widgets into separate widgets.
My solution using Builder :
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
child: Icon(Icons.add),
onPressed: () { showModalBottomSheet(
context: context,
builder: (context) {
return Text('Modal bottom sheet', style: TextStyle(fontSize: 30));
});
}
),
),
to add to this, there are cases where the generated test case is not updated to match your current main class

How to scale a Text widget that would behave like a scaling of an image

I would like to perform an Hero transition that will change, at the end, the size of a Widget.
This widget contains some text.
By default, the text widget will resize and the text inside move and resize to fit the text widget.
I would like to make to whole widget behave like an image would do : Everything will scale (zoom).
I tried :
auto_size_text package : The text will still move and the result is not perfect
screenshot package : It take too long to generate the image, replace the current widget with the image before performing the hero transition.
I am thinking about RenderRepaintBoundary, but this seems a lot of work for a simple task.
Any idea ?
If I understand what you want to achieve, you may want to use FittedBox.
This is what I used to create the animation below, where the Text widgets have a different size between the beginning and the end of the animation:
Thanks to #Romain, the easy answer was indeed FittedBox.
Making a Hero transition that will change the size of a Text Widget will be smooth when I put a FittedBox on the second page.
But I needed to pass down the original size to the second page to make the Text inside the FittedBox appear on the same number of lines that it was previously displayed.
Here the result :
https://vimeo.com/346745092
Here the code :
import 'package:flutter/material.dart';
const String textThatCouldChangeDependingOnContext = "Hero Text .... ";
main() {
runApp(MaterialApp(home: MyHomePage()));
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
GlobalKey _textKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(64.0),
child: Column(
children: <Widget>[
Hero(
tag: 'tag',
child: Material(
child: Container(
color: Colors.red,
child: Text(textThatCouldChangeDependingOnContext,
key: _textKey),
),
),
),
FlatButton(
child: Text('Fly'),
onPressed: () {
Size originalTextSize = _textKey.currentContext.size;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
MySecondPage(originalTextSize)));
},
)
],
),
),
),
);
}
}
class MySecondPage extends StatefulWidget {
final Size originalTextSize;
MySecondPage(this.originalTextSize);
#override
_MySecondPageState createState() => _MySecondPageState();
}
class _MySecondPageState extends State<MySecondPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
GestureDetector(
onTap: () => Navigator.pop(context),
child: Hero(
tag: 'tag',
transitionOnUserGestures: true,
child: Material(
child: Container(
color: Colors.red,
child: FittedBox(
fit: BoxFit.contain,
child: SizedBox(
height: widget.originalTextSize.height,
width: widget.originalTextSize.width,
child: Text(textThatCouldChangeDependingOnContext),
),
),
),
),
),
)
],
),
),
);
}
}