SafeArea not working in persistent bottomsheet in Flutter - dart

I am using MaterialApp and Scaffold + SafeArea for screens. All works well until I try to use persistent BottomSheet. BottomSheet content igores SafeArea and are shown below system controls, for example in iPhone X.
I tried to wrap BottomSheet contents in another SafeArea element, but it did not help.
Is there a way to get the same functionality as SafeArea to work in BottomSheet? If yes then how?

Just make the root Widget of the showModalBottomSheet be a SafeArea Widget
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[...

I have faced this issue too. When I changed code from showModalBottomSheet to _scaffoldKey.currentState.showBottomSheet my SafeArea stopped working.
You can solve it with these steps:
create key for your Scaffold GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();
assign created key to your Scaffold key: _scaffoldKey,
set bottom padding to your bottom sheet
padding: EdgeInsets.only(bottom: MediaQuery.of(_scaffoldKey.currentState.context).viewPadding.bottom)
Here is a result, I also added 15 padding to top, left and right.

I solved the problem by adding container and padding inside with: MediaQueryData.fromWindow(WidgetsBinding.instance.window).padding.top
Container(
padding: EdgeInsets.only(
top: MediaQueryData.fromWindow(WidgetsBinding.instance.window).padding.top),
child: MyOriginalWidgetForBottomSheet(),
)
Don't get from the context (like MediaQuery.of(context).padding.top) as the padding always returns 0

In my case the top safe area was the problem and a practical workaround was to set padding to the height of the app bar with AppBar().preferredSize.height
Padding(
padding: EdgeInsets.fromLTRB(0,AppBar().preferredSize.height,0,0),
child: Text(
'Your Order',
style: headingMediumBlack,
),
),

I tried out this simple code and it works as intended in the iOS Simulator with an iPhone X:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SafeArea(
child: new Scaffold(
appBar: new AppBar(
title: new Text('SafeArea demo'),
),
body: new Center(
child: new TapMe(),
),
),
));
}
}
class TapMe extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text('Tap Me'),
onPressed: () => Scaffold
.of(context)
.showBottomSheet((context) => new Text('I\'ve been tapped')),
);
}
}
What version of Flutter are you using?

Faced this issue too with top-notch.
Resolved using dart:ui.window.viewPadding.top / ui.window.devicePixelRatio
How it works:
The dart:ui.window.viewPadding is the number of physical pixels on each side of the display rectangle into which the view can render.
By dividing to ui.window.devicePixelRatio we should receive logic pixels.
So in summary code is
showModalBottomSheet<bool>(
context: context,
builder: (BuildContext context) => Container(
height: MediaQuery.of(context).size.height,
padding: EdgeInsets.only(
top: ui.window.viewPadding.top / ui.window.devicePixelRatio,
),
),
);

IMHO, The cleanest workaround is to set InitialChildSize to 0.9
DraggableScrollableSheet(
initialChildSize: 0.9,
minChildSize: 0.2,
maxChildSize: 0.9,
expand: false,
builder: (BuildContext context, ScrollController scrollController) {
}
)

This has been fixed -- just set useSafeArea: true in showModalBottomSheet. https://github.com/flutter/flutter/issues/39205

Related

Container decoration is not visible when inside a column or row

I am newbie in flutter and i want to show the image with full width at the top after AppBar i got the code from stack-overflow and it is working fine if i put the container inside the body of Scaffold it is showing me the image with full width along the screen but when i put this code inside the column or row it is not visible .
i have tried to show the image inside the Image() class but it doesn't adjust according to screen size. i mean it is not responsive.
import 'package:flutter/material.dart';
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
MediaQueryData queryData;
queryData = MediaQuery.of(context);
return Scaffold(
appBar: AppBar(),
body: Container(
margin: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
bannerImage(),
],
)
],
),
),
);
}
}
class bannerImage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return new DecoratedBox(
decoration: new BoxDecoration(
image: new DecorationImage(
image: new AssetImage("images/banner.png"),
fit: BoxFit.fitWidth,
),
));
}
}
I created separate widget for a bannerImage. kindly suggest me how i show my image with full width of screen or how i can show container without defining the child.
Thanks
put DecoratedBox inside a container and give height and width.
Try this way:
Container(
decoration: BoxDecoration(
color: const Color(0xff7c94b6),
image: DecorationImage(
image: ExactAssetImage('images/flowers.jpeg'),
fit: BoxFit.fitWidth,
),
border: Border.all(
color: Colors.black,
width: 8.0,
),
),
)
Use this container in your Column().
First of all What you want is not clear and also you can't use queryData = MediaQuery.of(context); directly you have to wrap with MaterialApp/WidgetsApp widget.
You have to set child of DecoratedBox to show image and also need to Wrap Expanded bannerImage. below are some code from you.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Streams Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: SecondRoute());
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
margin: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(child: bannerImage()),
],
)
],
),
),
);
}
}
class bannerImage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return new DecoratedBox(
decoration: new BoxDecoration(
image: new DecorationImage(
image: new NetworkImage("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSGTVf63Vm3XgOncMVSOy0-jSxdMT8KVJIc8WiWaevuWiPGe0Pm"),
fit: BoxFit.fitWidth,
),
), child: Text('dddsfdff dfdfds dfgfdgfg fdgfdddsfdff dfdfds dfgfdgfg fdgfdddsfdff dfdfds dfgfdgfg fdgfdddsfdff dfdfds dfgfdgfg fdgfdddsfdff dfdfds dfgfdgfg fdgfdddsfdff dfdfds dfgfdgfg fdgfdddsfdff dfdfds dfgfdgfg fdgfdddsfdff dfdfds dfgfdgfg fdgf'),);
}
}
Wrapping the Image code with a Container Widget that has its height and width specified, helps Container() and Row() to have your image displayed. Rows display images when they have width (double) value specified in Container() child within them, while Container() displays when a height (double) value is specified. I simply wrapped your image in a Container() widget within the Row() children and specified width and height double values. Feel free to adjust values to suit the ratio dimensions of your actual image. Hope this guide helps you on your project.
Replace the Row() widget with the below Code:
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
height: 50.0,
width: 100.0,
decoration: BoxDecoration(
image: bannerImage(),
),
),
],
),
Please explain what you are trying to show on the screen.
When you add a column, it will place the widgets vertically and you have added a row as the only element on the screen.
When you add row, you would want to add multiple widgets placed horizontally to each other.
I recommend you to read this article to understand the layouts properly - https://medium.com/flutter-community/flutter-layout-cheat-sheet-5363348d037e

Flutter background of unsafe area in SafeArea widget.

When I provide the SafeArea to a Widget, then it gets some margin from the notches and home button (horizontal line in iPhone X +). How can I change the background of the unsafe area ? (The margin portion)?
Wrap your SafeArea into a widget that adds a background:
Container(
color: Colors.red,
child: SafeArea(...),
),
Another way to do it.
import 'package:flutter/services.dart';
Scaffold(
body: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light.copyWith(
statusBarColor: Theme.of(context).primaryColor
),
child: SafeArea(
child: Container(...),
),
),
)
Following on from RĂ©mi Rousselet's answer...
In my case, I created a new widget called ColoredSafeArea:
import 'package:flutter/material.dart';
class ColoredSafeArea extends StatelessWidget {
final Widget child;
final Color? color;
const ColoredSafeArea({
Key? key,
required this.child,
this.color,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: color ?? Theme.of(context).appBarTheme.backgroundColor,
child: SafeArea(
child: Container(
color: Theme.of(context).colorScheme.background,
child: child,
),
),
);
}
}
And use this in place of SafeArea in my Scaffold. I have it set up to use the current AppBar colour from my theme, by default. But you can use whatever works for you, of course.
Basically, this widget will change the SafeArea colour without affecting your app background colour, due to the Container within, which takes the background colour from the current theme's colorScheme. The advantage of this is that the background colour will work with any dark or light themes you have set up.
This is probably the easiest way to accomplish this:
const Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Text(
"White scaffold background that also applies to status bar",
style: TextStyle(fontSize: 20),
),
),
);
Basically use SafeArea as a child of Scaffold and set the scaffold's background color to whatever you want or use ThemeData to set it globally using the scaffoldBackgroundColor prop
I have combined both the above answers to achieve
the system theme set (dark/light)
the color/gradient of unsafe area
The code I've used is
var brightness = SchedulerBinding.instance.window.platformBrightness;
bool isDarkModeOn = brightness == Brightness.dark;
Widget build(BuildContext context) {
return Scaffold(
body: AnnotatedRegion<SystemUiOverlayStyle>(
value: isDarkModeOn
? SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Theme.of(context).primaryColor,
)
: SystemUiOverlayStyle.light.copyWith(
statusBarColor: Theme.of(context).primaryColor,
),
child: Container(
decoration: getScreenGradient(),
child: SafeArea(
child: Container(
child: Center(
child: Stack(
children: [
getBackgroundImage(),
getBody(),
],
),
),
),
),
),
),
);
}

Flutter application design without AppBar

I tried using this kind of approach to have all of my UI (here only a Text) in the application below the status bar, but without AppBar:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
title: "example",
home: Scaffold(
body: Text("text widget"),
),
));
This question was already asked once similar to my text, but the answer to it (which is also accepted) only takes margin into account. This, to me, does not seem like a satisfying solution, especially because you need to access MediaQuery.of(context).padding, where I could not even figure out how to use context in my simple example.
My code gives me the following result:
But I want to see this:
Now to make the differenciation between my question and the other question clear: I am not searching for a margin, I am searching for a legitimate idiomatic way of doing this. What I mean with this might look like this:
ScaffoldWithoutAppBar(body: ...
Obviously this does not exist, but I do not want a margin fix.
Wrap your page content (Text or Scaffold) inside a SafeArea widget
A widget that insets its child by sufficient padding to avoid intrusions by the operating system.
return new SafeArea(child: new Text('text widget'));
first solution: My favorite solution
appBar: PreferredSize(
preferredSize: Size.fromHeight(MediaQuery.of(context).padding.top),
child: SizedBox(
height: MediaQuery.of(context).padding.top,
),
),
solution (2):
body: SafeArea(
top: true,
left: false,
right: false,
bottom: false,
child:Container(),
)
solution (3):
body: ListView(
children:[],
)
You can wrap the Scaffold into SafeArea, as below:
import 'package:flutter/material.dart';
void main() => runApp(MyApp(
textInput: Text("Text Widget"),
));
class MyApp extends StatefulWidget {
final Widget textInput;
MyApp({this.textInput});
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
bool checkBoxValue = false;
#override
Widget build(BuildContext ctxt) {
return new MaterialApp(
home: SafeArea(
child: Scaffold(
body: new Center(
child: new Column(
children: <Widget>[
widget.textInput,
Checkbox(
value: checkBoxValue,
onChanged: (bool newValue){
setState(() {
checkBoxValue = newValue;
});
}
)
],
))),
),
);
}
}
About the opposite, in case you do not need to keep repeating the AppBar in your multiple screens, you can create separate widget:
import 'package:flutter/material.dart';
import 'state.dart';
AppBar commonAppBar(String title, void action()) {
return new AppBar(
title: new Text(title),
actions: [
new IconButton(icon: new Icon(Icons.flip), onPressed: action),
new IconButton(icon: new Icon(Icons.exit_to_app), onPressed: () {
new StateSubject().switchToLogin();
}),
],
);
}

Can FlutterLogo be made to stretch-to-fill?

I would like to display a large FlutterLogo in my app:
https://docs.flutter.io/flutter/material/FlutterLogo-class.html
In order to account for varying screen sizes I would like to make it stretch-to fill. Is that possible? Or do I need to use a MediaQuery to determine the parent's size and pass that into FlutterLogo(size:)?
My current code:
Widget build(BuildContext context) {
return new Center(
child: new FlutterLogo(size: 800.0, style: FlutterLogoStyle.horizontal, textColor: Colors.white),
);
}
You can accomplish this with a ConstrainedBox:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData.dark(),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Example App')),
body: new ConstrainedBox(
constraints: new BoxConstraints.expand(),
child: new FlutterLogo(
style: FlutterLogoStyle.horizontal,
textColor: Colors.white,
),
),
);
}
}
I believe I have answered a similar question
How to stretch an icon to fill parent?
https://docs.flutter.io/flutter/widgets/Expanded-class.html
https://groups.google.com/forum/#!msg/flutter-dev/lsgdU1yl7xc/0pYS2qrzBQAJ
https://docs.flutter.io/flutter/widgets/FittedBox-class.html
https://docs.flutter.io/flutter/painting/BoxFit-class.html
new Expanded(
child: new FittedBox(
fit: BoxFit.fill,
child: new FlutterLogo( style: FlutterLogoStyle.horizontal, textColor: Colors.white),
),
),
I feel kinda strange. Looking at the OP profile ID, I wonder if I answer the question correctly.
I hope this helps.
used this code to run it
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
MyAppBar({this.title});
// Fields in a Widget subclass are always marked "final".
final Widget title;
#override
Widget build(BuildContext context) {
return new Container(
height: 56.0, // in logical pixels
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(color: Colors.blue[500]),
// Row is a horizontal, linear layout.
child: new Row(
// <Widget> is the type of items in the list.
children: <Widget>[
new IconButton(
icon: new Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null disables the button
),
// Expanded expands its child to fill the available space.
new Expanded(
child: title,
),
new IconButton(
icon: new Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
class MyScaffold extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Material is a conceptual piece of paper on which the UI appears.
return new Material(
// Column is a vertical, linear layout.
child: new Column(
children: <Widget>[
new MyAppBar(
title: new Text(
'Example title',
style: Theme.of(context).primaryTextTheme.title,
),
),
new Expanded(
child: new FittedBox(
fit: BoxFit.fill,
child: new FlutterLogo( style: FlutterLogoStyle.horizontal, textColor: Colors.white),
),
),
],
),
);
}
}
void main() {
runApp(new MaterialApp(
title: 'My app', // used by the OS task switcher
home: new MyScaffold(),
));
}
edit: I posted complete code just for darky, since I forgot to mention that expanded needs to be wrapped into row, column, or flex container to expand

Replace initial Route in MaterialApp without animation?

Our app is built on top of Scaffold and to this point we have been able to accommodate most of our routing and navigation requirements using the provided calls within NavigatorState (pushNamed(), pushReplacementNamed(), etc.). What we don't want though, is to have any kind of 'push' animation when a user selects an item from our drawer (nav) menu. We want the destination screen from a nav menu click to effectively become the new initial route of the stack. For the moment we are using pushReplacementNamed() for this to ensure no back arrow in the app bar. But, the slide-in-from-the-right animation implies a stack is building.
What is our best option for changing that initial route without animation, and, can we do that while also concurrently animating the drawer closed? Or are we looking at a situation here where we need to move away from Navigator over to just using a single Scaffold and updating the 'body' directly when the user wants to change screens?
We note there is a replace() call on NavigatorState which we assume might be the right place to start looking, but it's unclear how to access our various routes originally set up in new MaterialApp(). Something like replaceNamed() might be in order ;-)
What you're doing sounds somewhat like a BottomNavigationBar, so you might want to consider one of those instead of a Drawer.
However, having a single Scaffold and updating the body when the user taps a drawer item is a totally reasonable approach. You might consider a FadeTransition to change from one body to another.
Or, if you like using Navigator but don't want the default slide animation, you can customize (or disable) the animation by extending MaterialPageRoute. Here's an example of that:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyCustomRoute<T> extends MaterialPageRoute<T> {
MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Navigation example',
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/': return new MyCustomRoute(
builder: (_) => new MyHomePage(),
settings: settings,
);
case '/somewhere': return new MyCustomRoute(
builder: (_) => new Somewhere(),
settings: settings,
);
}
assert(false);
}
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Navigation example'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
new ListTile(
leading: const Icon(Icons.navigate_next),
title: const Text('Navigate somewhere'),
onTap: () {
Navigator.pushNamed(context, '/somewhere');
},
),
],
),
),
body: new Center(
child: new Text(
'This is a home page.',
),
),
);
}
}
class Somewhere extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Text(
'Congrats, you did it.',
),
),
appBar: new AppBar(
title: new Text('Somewhere'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
],
),
),
);
}
}
Use PageRouteBuilder like:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => Screen2(),
transitionDuration: Duration.zero,
),
);
And if you want transition, simply add following property to above PageRouteBuilder, and change seconds to say 1.
transitionsBuilder: (_, a, __, c) => FadeTransition(opacity: a, child: c),

Resources