Using SafeArea in Flutter - flutter

I am trying to understand the SafeArea widget in Flutter.
SafeArea code added to Flutter Gallery app here in github show top:false and bottom:false everywhere. Why do these need to be set false in these cases?

SafeArea is basically a glorified Padding widget. If you wrap another widget with SafeArea, it adds any necessary padding needed to keep your widget from being blocked by the system status bar, notches, holes, rounded corners, and other "creative" features by manufacturers.
If you are using a Scaffold with an AppBar, the appropriate spacing will be calculated at the top of the screen without needing to wrap the Scaffold in a SafeArea and the status bar background will be affected by the AppBar color (Red in this example).
If you wrap the Scaffold in a SafeArea, then the status bar area will have a black background rather than be influenced by the AppBar.
Here is an example without SafeArea set:
Align(
alignment: Alignment.topLeft, // and bottomLeft
child: Text('My Widget: ...'),
)
And again with the widget wrapped in a SafeArea widget:
Align(
alignment: Alignment.topLeft, // and bottomLeft
child: SafeArea(
child: Text('My Widget: ...'),
),
)
You can set a minimum padding for edges not affected by notches and such:
SafeArea(
minimum: const EdgeInsets.all(16.0),
child: Text('My Widget: ...'),
)
You can also turn off the safe area insets for any side:
SafeArea(
left: false,
top: false,
right: false,
bottom: false,
child: Text('My Widget: ...'),
)
Setting them all to false would be the same as not using SafeArea. The default for all sides is true. Most of the time you will not need to use these settings, but I can imagine a situation where you have a widget that fills the whole screen. You want the top to not be blocked by anything, but you don't care about the bottom. In that case, you would just set bottom: false but leave the other sides to their default true values.
SafeArea(
bottom: false,
child: myWidgetThatFillsTheScreen,
)
Supplemental code
In case you want to play around more with this, here is main.dart:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: BodyWidget(),
),
);
}
}
class BodyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: SafeArea(
left: true,
top: true,
right: true,
bottom: true,
minimum: const EdgeInsets.all(16.0),
child: Text(
'My Widget: This is my widget. It has some content that I don\'t want '
'blocked by certain manufacturers who add notches, holes, and round corners.'),
),
);
}
}

When you wrap a widget A in a safe area, you are asking to the framework "Please, keep my widget A away from the device's UI navigation and notches".
The arguments 'top, bottom, right and left' are used to tell to the framework if you want him to avoid the device's intrusions from that sides specifically.
For example: if you put your widget A inside a safe area in the top of the screen and sets the "top" argument to false, it will be cropped by the iPhone's X and Pixel 3's notches.

SafeArea is a widget that sets its child by enough padding to avoid intrusions by the operating system and improve the user interface.
import 'package:flutter/material.dart';
class SafeArea extends StatefulWidget {
#override
_SafeAreaState createState() => _SafeAreaState();
}
class _SafeAreaState extends State<SafeArea> {
#override
Widget build(BuildContext context) {
MediaQueryData mediaQueryData=MediaQuery.of(context);
double screenWidth = mediaQueryData.size.width;
var bottomPadding=mediaQueryData.padding.bottom;
return Padding(
padding: EdgeInsets.only(bottom: bottomPadding),
child: Scaffold(
body: new Container(
),
),
); }}

Without using SafeArea in iPhone 12 pro max
With using SafeArea
Code snippet using SafeArea
SafeArea(
child: Text('Your Widget'),
)

Related

How can Flutter Widgets be sized relatively to each other?

I'm a couple days into learning Flutter, and I keep running into a situation where I want to scale a collection of widgets (aka a Row, Column, Stack, etc) down when the screen shrinks, akin to what Flexible() does. Except unlike Flexible(), I want all children to scale down when the container shrinks, not just the largest elements. Consider this example of a Column() (green) with two children: a red child matching its parent's width, and a blue child with half the parent's width:
In the above case, if the green Column() were to be constrained such that its width was less, you would end up with something like such:
However, what I am wanting is for each of the child elements (red/blue) to scale their width/height relative to each other, like this:
How can I accomplish this?
In the specific Column() case I illustrated above, my workaround was to add a Row() containing the blue widget as well as a Padding(), both with equal flex properties, so that when the Column() shrunk the blue element scaled correctly. I feel like this is a hack though, and wouldn't be as feasible if I had many elements with differing widths/alignments. It would be a nightmare to add one Padding() for left/right aligned elements and two for centered ones. Is there a better way?
I'd also like to know of a similar solution for using Stack()s. Using the Positional() element seems to set the width/height in a way that the Stack() crops any overflow. It'd be nice to have a Stack() that scales all its children the same, just like if it was a single image, and similar to my Column() example above.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatelessWidget(),
),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({super.key});
#override
Widget build(BuildContext context) {
return SizedBox.expand(
child: FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 0.5,
alignment: FractionalOffset.center,
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue,
width: 4,
),
),
),
),
);
}
}
FractionallySizedBox Widget is a widget that sizes its child to a fraction of the total available space. To make it easy to understand how FractionallySizedBox works
You can have a look at FractionallySizedBox class, you can specify the widthFactor and heightFactor e.g. 0.5 so that it is always half the size of the parent container.
Example:
Flexible(
child: FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 0.5,
alignment: FractionalOffset.centerLeft,
child: Container(
color: Colors.blue),
),
),

How to hide an element that cannot be fully displayed in flutter?

I have a Text widget that sometimes can be fully displayed, sometimes not, depending on the widgets around.
If there is not enough space to fully display the widget, I want the widget to not show at all, I don't want it to show partially like with the overflow attribute.
If you know a way to do this, thanks.
LayoutBuilder to the rescue for you!
Builds a widget tree that can depend on the parent widget's size.
Reference
Try this! Play around with the allowedTextHeightInPixels value to see how it works.
/// Breakpoint or condition to WHEN should we display the Text widget
const allowedTextHeightInPixels = 150.0;
/// Test height for the [Text] widget.
const givenTextHeightByScreenPercentage = 0.3;
class ResponsiveTextWidget extends StatelessWidget {
const ResponsiveTextWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
print('Text height in pixels: ${constraints.maxHeight * givenTextHeightByScreenPercentage}');
return Column(
children: [
Container(
color: Colors.red,
height: constraints.maxHeight * 0.5,
),
if (constraints.maxHeight * givenTextHeightByScreenPercentage > allowedTextHeightInPixels)
const SizedBox(
child: Text(
'Responsive Me',
style: TextStyle(fontSize: 15.0),
),
),
Container(
color: Colors.blue,
height: constraints.maxHeight * 0.2,
),
],
);
},
),
),
);
}
}
I don't know why you need to do this but i thing overflow is good enough for most case, you can also use Fittedbox to scale the text with the box with no redundant space.
In case you still want do it, you need to find the RenderBox of that specific widget, which will contain its global position and rendered size from BuildContext. But BuildContext can be not exist if the widget is not rendered yet.
If by "fully displayed" you mean that, for example, you have a SingleChildScrollView and only half of your Text widget is visible, you can try out this library :
https://pub.dev/packages/visibility_detector.
You can retrieve the visible percentage of your widget with the method visibilityInfo.visibleFraction.

How to keep the screen in a fixed position when the keyboard is opened in Flutter?

In my log in screen attached below, I want the view to stay fixed even when I open the keyboard.
If you see the screenshot of how my screen currently behaves, you can notice that it gets scrolled up(pay attention to the Logo TextField) when the keyboard is opened.
I am currently using SingleChildScrollView to avoid overflow, but even if i do not use a SingleChildScrollView, the screen still repositions after opening the keyboard, only this time, with a pixel overflow.
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: <Widget>[
_logo(),
LoginForm(),
],
),
),
);
}
}
How can I keep the original positioning of the screen(as shown in the screenshot on the left) even when the keyboard is opened?
Edit: Already tried resizeToAvoidBottomInset, it still scrolls.
In your Scaffold, try putting resizeToAvoidBottomInset property to "false".
You can wrap your content in a Scaffold then set the property resizeToAvoidBottomInset to false so it will not resize your content when the device's Keyboard is shown. I've updated your code so you can use it.
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: <Widget>[
_logo(),
LoginForm(),
],
),
),
)
);
}
}

How to align widget to another widget in Flutter?

I have a RaisedButton widget inside of a Center widget as one of the widgets in a Column of widgets. I want to add a CircularProgressIndicator to the right side of this button and show it when the button is pressed. Yet I want to leave the button centred when the progress bar is shown. In other words I want the button always be in the center and the progress bar aligned to this button.
I tried to use a Row here but this pushes the button and it becomes not centred any more.
EDIT1: Looking at the result of the solution provided by #Anil Chauhan (thanks for the answer):
Like I said before that I tried to use Row like he did, the problem is that in this case the button is not in the centred in the screen and is pushed by the progress bar. And I need it to stay in the middle of it's row.
EDIT2: #Anil Chauhan edited answer now works for a specific case in which the button is predetermined size. But if the size of the button is changed based on the language of the text (in apps that have several languages) this solution will not work.
This is the reason the question I asked is: "How to align widget to another widget". Because if I could that I don't have to worry about the button text size any more.
What would be the right way to handle this in Flutter?
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
bool _showIndicator = false;
void _onButtonClicked() {
setState(() {
_showIndicator = !_showIndicator;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
children: <Widget>[
const Expanded(child: SizedBox()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: RaisedButton(
child: Text("I am Too Big"),
onPressed: _onButtonClicked,
),
),
Expanded(
child: _showIndicator
? const Align(
alignment: Alignment.centerLeft,
child: CircularProgressIndicator(),
)
: const SizedBox(),
),
],
),
),
);
}
}
Here is my explanation:
The RaisedButton size is depends on its child. If you add it to Row it will automatically align to left(or start).
Expanded widget will fill the remaining space in Flex widget(Row & Column are child classes of Flex). If you add more than one Expanded widgets, it will split equally. So I added two Expanded to both the side of button to make it center.
Now We should give child for Expanded Widget.
For the first one(left) we don't have anything to display so I added SizedBox.
For the second one(right) we need CircularProgressIndicator. so I added it.
The Expanded widget will try to make its child to fill the space inside of it. So the CircularProgressIndicator will become Ellipse shaped. We can avoid this by using Align Widget.
Try this:
Updated:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyAppOne(),
);
}
}
class MyAppOne extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyAppOne>{
bool show = false;
#override
Widget build(BuildContext context){
return Scaffold(
body: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
alignment: Alignment.center,
child: RaisedButton(
onPressed: () {
setState(() {
show =!show;
});
},
child: Text('Show'),
),
),
Positioned(
right: MediaQuery.of(context).size.width * .20,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10.0),
child: show ? CircularProgressIndicator() : Container(),
),
)
],
)
);
}
}
Flutter's Column and Row widgets have two convenient properties called mainAxisAlignment and crossAxisAlignment. I assume since you're using a Column and want the CircularProgressIndicator to the right of the button, you might be want to use crossAxisAlignment since the cross-axis of a Column is along the horizontal.
If possible, please share your code for better understanding and support of the issue.

How to keep a custom widget common across more than one screen in flutter

I am making a flutter application in which I need two put a widget which is common for more than one screen. Here is the code of build method of screen one
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: app_color,
body: _isLoading == false ? Stack(
new Container(//used for whole screen ),
Positioned(
left: 0,
right: 0,
bottom: 0,
//a bottom tab like which is common across screens
),
],
)
:
//code to show progress indicator while loading data in background
Stack(
children: <Widget>[
Center(
child:Container(
height: 50,
width: 50,
child: CircularProgressIndicator(),
)
),
Positioned(
left: 0,
right: 0,
bottom: 0,
//keep this to show bottom tab while loading
),
],
)
);
}
Above code has a positioned widget at the bottom of the screen which I want to keep common across more than one screen? How can I achieve it? I have knowledge of android and in that case I can achieve using fragment transaction but here I have to keep the bottom positioned widget across all screen and the problem with this is that after changing screen bottom position widget disappears for some time but I want that bottom widget to be kept static and change only the screen not bottom positioned widget
One way to accomplish that is to customize the builder, in the MaterialApp widget.
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: TestPage(),
initialRoute: "/test",
builder: (context, child) {
return Scaffold(
body: Stack(
children: [
child!,
Positioned(
left: 0,
right: 0,
bottom: 0,
),
],
),
);
},
routes: routes(context),
);
}
}
That will render the widget in all screens, and the transition animation between screens will not mess it up, and you can freely use other Scaffolds in your pages as you normally would.
You can do one of two things
1- create a custom widget
create a file called common_view.dart and add the view to it
import 'package:flutter/material.dart';
class CommonBottom extends StatelessWidget {
CommonBottom();
#override
Widget build(BuildContext context) {
return Positioned(
left: 0,
right: 0,
.......
);
}
}
Then use it in all your pages as CommonBottom()
2- create your app as a single page app
create a stateful widget for every page you need and render it inside your page wrapping it with a visibility widget.
well it won't be static for all pages unless you implemented it in each page and in order to do that you need to import the file where that class exist and use it directly.
import 'packages/mycommonclass.dart';
and then place it at the same place as the other pages.