I have been working on a project which requires me to change the on-screen widget based on the mobile device's orientation
.
For example, in the case of portrait
orientation, I need to show a widget (let's say portrait widget) and in case of landscape
orientation, I need to show another widget (let's call it landscape widget).
I have used OrientationBuilder
Actual problem:
On orientation change, all the Dialogs
and OptionMenu
or any other Popup kind of Widget is not closed. How do I close them on orientation change?
Steps to reproduce the problem:
- Run the app code given below [example app for reproducing the issue]
- Do a long press on app body to see a dialog or click on
OptionMenu
on top-left - Change device orientation, you'll see that the widget body has changed according to the Orientation but the
Popuped
widget is still visible.
Note: Please note that I need a global solution for this issue. Providing a solution around only this code specifically will not be of any use for me. This is an Example code which I have provided for better understanding of the problem, I don't use this code at all.
example app code:
// main.dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return OrientationBuilder(builder: (context, orientation) {
bool isLandscape = orientation == Orientation.landscape;
return isLandscape ? Landscape() : Portrait();
});
}
}
class Portrait extends StatefulWidget {
@override
_PortraitState createState() => _PortraitState();
}
class _PortraitState extends State<Portrait> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: buildTitle(),
actions: <Widget>[_buildOptionMenu(context)],
),
body: GestureDetector(
onLongPress: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: buildTitle(),
),
);
},
child: Container(
color: Colors.blue.withOpacity(0.4),
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Center(
child: buildTitle(),
),
],
),
),
),
);
}
Widget _buildOptionMenu(BuildContext context) {
return PopupMenuButton(itemBuilder: (context) {
var list = <String>['Portrait-Item-1', 'Portrait-Item-2'];
return list
.map<PopupMenuEntry<String>>(
(e) => PopupMenuItem<String>(
child: Text(e),
),
)
.toList();
});
}
Text buildTitle() => Text('Portrait');
}
class Landscape extends StatefulWidget {
@override
_LandscapeState createState() => _LandscapeState();
}
class _LandscapeState extends State<Landscape> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: buildTitle(),
actions: <Widget>[_buildOptionMenu(context)],
),
body: GestureDetector(
onLongPress: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: buildTitle(),
),
);
},
child: Container(
color: Colors.orange.withOpacity(0.3),
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Center(
child: buildTitle(),
),
],
),
),
),
);
}
Text buildTitle() => Text('Landscape');
Widget _buildOptionMenu(BuildContext context) {
return PopupMenuButton(itemBuilder: (context) {
var list = <String>[
'Landscape-Item-1',
'Landscape-Item-2',
'Landscape-Item-3',
];
return list
.map<PopupMenuEntry<String>>(
(e) => PopupMenuItem<String>(
child: Text(e),
),
)
.toList();
});
}
}
I couldn't find any feasible solution. There are some solutions which require to listen for orientation changes and push/pop widgets based on new orientation.
But It is a bit too much to work with and requires to add the same type of code in both portrait and landscape widget. Which is also not scalable in case I need to handle addition orientations like, reverse-portrait and reverse-landscape.
Update
One of the solution which is feasible to do is pop all the widgets until the root widget and then push new widget based on orientation. This works but comes with a side effect.
For example, if I push some new widget from portrait (let's say a login page).
Then if I rotate my device to landscape, it should inflate the login page UI in landscape mode but according to the code which pops all the widget until the root.
What I will see is the Landscape widget instead of the Login Page in landscape mode.
For clarity:
I am looking for an answer to close all open dialogs/pop-ups before it's parent widget is disposed
. The solution should not be dependent on popping out the whole widget from the navigator.
Something that I found out from the widget tree representation is that the widgets that are shown as a popup (popup menu or dialog) are at a different branch of widget directly from the MaterialApp
.
Check out these screenshots:
Visible Pop up menu in Landscape widget:
Visible Dialog in Landscape widget:
Visible Pop up menu in Portrait widget:
Visible Dialog in Portrait widget:
So, basically I am looking for a way to find and pop all these types of widgets that we can pop before disposing of the parent widget. I guess this should be applicable to all the widget screen and should not be altering the existing widget tree above the current widget.
Update 2
I got an answer which successfully removes the dialogs and popups but it has a side effect, which is that while removing all the dialogs/popups it also removes any widgets that are added on top of the root widget which showed the popups.
For example:
in this example, consider a details page that I visit from the portrait
widget. So when the portrait
widget rebuilds the detail page is disposed and only portrait
is shown again even though I do not have any dialog/popup open.