Proper way of changing scaffold content depending on orientation - dart

I have a screen with GoogleMap widget and couple Text widgets. What I'm trying to do is to let map utilize whole screen in landscape orientation without AppBar, while being restricted to Container size in portrait orientaion.
Now I just have 2 scaffold widgets which beign redrawn on each orientaion change, and after couple rotations whole device freezes and I have to reboot it.
Widget build(BuildContext context) {
final mediaQueryData = MediaQuery.of(context);
if (mediaQueryData.orientation == Orientation.landscape) {
return Scaffold(
body:GoogleMap(
...
),
);
}
else{
return Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: ListView(
scrollDirection: Axis.vertical,
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
Text('Text'),
Text('Text'),
Container(
height: MediaQuery.of(context).size.height/3,
child: GoogleMap(
...
),
),
],
),
);
}
So I would really appreciate if someone could guide me to more efficient way of doing something like this.
Also if there's not, I would also like to know if it's possible to have working gestures (scroll, pan, etc.) in GoogleMap widget nested in scrollable ListView.

If anyone intrested someone suggested this method, but comment got deleted:
Widget build(BuildContext context) {
final isLandScape = MediaQuery.of(context).orientation == Orientation.landscape;
return Scaffold(
appBar: isLandScape ? null : AppBar(
title: Text('Title'),
),
I used same method also for Visibility widget that includes other widgets besides map:
visible: isLandScape ? false : true,
And same for Container with map:
Container(
height: isLandScape ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height/3,
child: GoogleMap(
///
),
),
Seems to be working stable now.

Related

How to use both ScrollView and full size for an image?

I am trying to show my selected image in full screen, which works absolutely fine on a phone but, on a tablet, the image does not cover the screen. I am using boxFit.cover, so I'm not sure why it is not covering the screen.
One solution is to use height: double.infinity and width: double.infinity. Then the tablet screen is covered. However, if I do that, I have to remove my "SingleChildScrollView" which means that I can't scroll pictures that are wider, when needed.
Here is my code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Container(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Image(
image: NetworkImage(widget.imPath),
fit: BoxFit.cover,
),
),
),
Is there any workaround so that I can both cover my screen and scroll when needed?
First off I want to point out that you will not be able to scroll with your current setup. See, you have exactly one widget that covers exactly the area that is available to the scroll view.
I will just assume that you plan on adding other widgets to the SingleChildScrollView but have the image take up the whole screen or available height.
I have two solutions. One of them covers the whole screen and is a little bit easier because of that and the other one is more robust and also accounts for padding or a scenario where you do not have the full screen for the image.
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Container(
child: LayoutBuilder(
builder: (context, constraints) => SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
height: constraints.biggest.height,
child: Image(
image: NetworkImage(widget.imPath),
fit: BoxFit.cover,
),
),
),
),
),
),
);
}
In this possible solution, I added a LayoutBuilder before the SingleChildScrollView and then use the biggest height constraints to surround the Image with a SizedBox that uses that height.
The simpler solution that only works in a full screen scenario uses the height from the MediaQuery:
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Container(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Image(
image: NetworkImage(widget.imPath),
fit: BoxFit.cover,
),
),
),
),
),
);
}

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 Stack with CircularProgressIndicator and GridView widgets, does nor work propertly

I have a Stack widget as follows:
Widget build(BuildContext context) {
final title = StringsES.title_meter_list_screen;
_context = context;
return Scaffold(
appBar: appbar,
body: Stack(
children: <Widget>[
GridView.count(
padding: EdgeInsets.all(10.0),
primary: false,
crossAxisCount: 2,
children: _buildWidgetList(),
),
Align(
child: _progressBarWidget(),
),
],
)
);
}
And the _progressBarWidget is the following:
Widget _progressBarWidget() {
return (_isProgressBarVisible)
? CircularProgressIndicator(strokeWidth: 4.0, )
: Container();
}
In this way, the CircularProgressIndicator Widget is displayed behind the GridView, and I want it to appear in front of the GridView.
If I change my code and replace the CircularProgressIndicator with a simple text widget, it works correctly.
What am I doing wrong?

Flutter Camera Preview

I'm new to both Flutter and Dart, so bear with me.
I'm trying to use Flutter to display a camera preview using the Camera Plugin, and have two problems. 1) The preview is stretched so things look weird. 2) I want to have a BottomNavigationBar displayed below the preview, but the Camera Preview uses all screen space.
I initialize the camera and open the preview:
#override
Widget build(BuildContext context) {
if (!_isReady) return new Container();
if (!controller.value.initialized) return new Container();
return new CameraPreview(controller);
}
1) This is the build method for a class I've called _CameraWidgetState. How can I make this preview not look stretched?
2) To make the CameraWidget not use all space, I've tried putting it inside a Scaffold with no luck:
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new CameraWidget(),
),
bottomNavigationBar: new BottomNavigationBar(
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.camera), title: new Text("Left")),
new BottomNavigationBarItem(
icon: new Icon(Icons.favorite),
title: new Text("Right"))
],
),
);
}
Any ideas or help appreciated!
This solves the problem, but there could be better solutions as well. (Thanks to #user1462442 from the comments above.)
#override
Widget build(BuildContext context) {
if (!_isReady) return new Container();
if (!controller.value.initialized) return new Container();
return new Scaffold(
body: new Container(
child: new AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: new CameraPreview(controller),
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _isReady ? capture : null,
child: const Icon(
Icons.camera,
color: Colors.white,
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}

Flutter: Banner Ad Overlapping The Main Screen

I am doing a Flutter app and managed to show the AdMob banner ad, however the ad overlaps the bottom of my app's main screen:
By following this article, I managed to make the app screen's bottom properly displayed, but the persistentFooterButtons is sacrificed, which I think is not an ideal solution.
I am thinking about putting the Scaffold object and a fixed height area into a column that is contained by a Center object, something similar to the following:
#override
Widget build(BuildContext context) {
return new Center(
child: new Column (
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Expanded (
child: _getScaffold(),
),
new Expanded (
child: new Container(height: 50.0,)
)
],
),
);
}
But in this way I get the exception "A RenderFlex overflowed by 228 pixels on the bottom":
Anybody can show me how to build such layout? I want every component of my scaffold properly displayed, with a fixed height dummy footer that is ok to be overlapped by the Admob's banner ad.
Any help is much welcome.
Jimmy
Also we can add some trick like bottomNavigationBar under the Scaffold
bottomNavigationBar: Container(
height: 50.0,
color: Colors.white,
),
This will take floating button up.
Finally I got it:
#override
Widget build(BuildContext context) {
return new Center(
child: new Column (
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Expanded (
child: _getScaffold(),
),
new Container(height: 50.0,
child: new Placeholder(color:Colors.blue))
],
),
);
}
The trick is Expanded here is for the Scaffold only, but for the dummy footer just a fixed height Container is required. Now I can display everything available from the Scaffold object.
Layout building of Flutter sometimes really confuses me...
If I understand your question well, I think you want to have your ad shown from the bottom while using a FAB. I think using a Stack widget here is a good solution, I created this example in a rush but should be enough to show you what I mean:
class AdBar extends StatefulWidget {
#override
_AdBarState createState() => new _AdBarState();
}
class _AdBarState extends State<AdBar> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(),
body: new ListView(
children: new List.generate(50, (int index) {
return new Text("widgets$index");
}),
),
persistentFooterButtons:
<Widget>[
new Stack(
children: <Widget>[
new Container (
color: Colors.transparent,
child: new Material(
color: Colors.cyanAccent,
child: new InkWell(
onTap: () {
},
child: new Container(
//color: Colors.cyanAccent,
width: MediaQuery
.of(context)
.size
.width * 0.90,
height: MediaQuery
.of(context)
.size
.height * 0.25,
),
),),),
new Positioned(
right: 0.0,
child: new FloatingActionButton(
onPressed: () {}, child: new Icon(Icons.fastfood)))
],
)
]
);
}
}

Resources