Is there any way I can catch the onBackPressed event from Android back button?
I've tried the WillPopScope but my onWillPop function only triggered when I tap on the Material back arrow button
I put it like this:
class MyView extends StatelessWidget{
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async {
debugPrint("Will pop");
return true;
},
child: ScopedModel<AppModel>(
model: new AppModel(),
child: new Scaffold(......
I need to catch it because somehow my screen behaved incorrectly when it came to back button pressed, it pops the screen and the screen below it, but somehow, using material back arrow button works normal.
Update:
The code works, my problem was not in the pop of this screen, but on the previous screen, I use 2 MaterialApp widgets, and somehow it gave a weird behavior.
In order to prevent navigating back WillPopScope is the correct way and should be used as follow:
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new WillPopScope(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Page 2'),
),
body: new Center(
child: new Text('PAGE 2'),
),
),
onWillPop: () async {
return false;
},
);
}
}
Future<T> pushPage<T>(BuildContext context, Widget page) {
return Navigator.of(context)
.push<T>(MaterialPageRoute(builder: (context) => page));
}
Can call the page like:
pushPage(context, Page2());
This is should be helpful.
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
_moveToScreen2(context, );
},
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
_moveToScreen2(context);
}),
title: Text("Screen 1"),
),
),
);
}
/**
* This is probably too thin to be in its own method - consider using
* `Navigator.pushReplacementNamed(context, "screen2")` directly
*/
void _moveToScreen2(BuildContext context) =>
Navigator.pushReplacementNamed(context, "screen2");
Use WillPopScope method and return false
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Do something here
print("After clicking the Android Back Button");
return false;
},
child: Scaffold(
appBar: AppBar(
title: Text("Handling the back button"),
),
body: Center(
child: Text("Body"),
),
),
);
}
Just adding an important point here.
Please note that by using WillPopScope, we will lose the back swipe gesture on iOS.
Reference: https://github.com/flutter/flutter/issues/14203
This code work for me.
I think there may be two reasons.
Child of WillPopScope is Scaffold
No return in onWillPop
return new WillPopScope(
onWillPop: () {
if (!_isOpened) Navigator.pop(context);
},
child: new Scaffold(
key: SharedService.orderScaffoldKey,
appBar: appBar,
body: new Builder(
builder: (BuildContext context) {
return page;
},
),
),
);
Another way todo this is to implement a NavigatorObserver and link it to the MaterialApp:
https://api.flutter.dev/flutter/widgets/RouteObserver-class.html
You don't have to use RouteAware, you can also implement your own NavigatorObserver.
This is for example how Flutter analytics works to automatically track screen opens/closes:
MaterialApp(
...
navigatorObservers: [
FirebaseAnalyticsObserver(analytics: analytics),
],
)
FirebaseAnalyticsObserver extends the RouteObserver which itself implements NavigatorObserver.
However WillPopScope is often the easier solution
You can use back_button_interceptor
it detects hardware back button & will be so useful specially in case of using persistent_bottom_nav_bar
#override
void initState() {
super.initState();
BackButtonInterceptor.add(myInterceptor);
}
#override
void dispose() {
BackButtonInterceptor.remove(myInterceptor);
super.dispose();
}
bool myInterceptor(bool stopDefaultButtonEvent, RouteInfo info) {
print("BACK BUTTON!"); // Do some stuff.
return false;// return true if u want to stop back
}
Following the documentation of BackButtonListener:
/// It can be useful for scenarios, in which you create a different state in your
/// screen but don't want to use a new page for that.
https://github.com/flutter/flutter/pull/79642
e.g.
#override
Widget build(BuildContext context) {
return BackButtonListener(
onBackButtonPressed: () {
/// todo: close search widget
if(searchBarController.isClose()){
return false;
}else{
searchBarController.close();
return Future.value(true);
}
},
child: SearchBar(controller: searchBarController),
);
}
This is the updated code
basically, WillPopScope -> onWillPop works on the future argument
we can say as when it happens then ????
so as soon the back button is pressed WillPopScope -> onWillPop gets activated and listens to the argument more specific the back button event to pop it (replace it)
Most of the time I use it to show a DialogBox of Future type because it will only appear when it is needed same can be used to navigate to a new screen as well (hope so) preferred to do MaterialPage routing or named routing techniques for navigation,
use WillPopScope for the hardware back button event listening (hardware = Android/IOs) to show the exit popup
code is already been given above but does not work for me so I change a little bit
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async{
return _moveToScreen2(context);
},
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
_moveToScreen2(context);
}),
title: Text("Screen 1"),
),
),
);
}
Future<bool>_moveToScreen2(BuildContext context) =>
Navigator.pushReplacementNamed(context, "screen2");
===============================================
What I do on Exit
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()=> showExitPopup(context)
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text("Screen 1"),
),
),
);
}
=========================================
On Back button Press
created a dart file with the name showExitPopup
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:vexpositions/Servises/ConstantManager.dart';
Future<bool> showExitPopup(context) async{
return await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: SizedBox(
height: 90,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Want to Exit the app!"),
const SizedBox(height:20),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
print('yes selected');
exit(0);
},
style: ElevatedButton.styleFrom(
primary: Colors.white),
child: const Text("Yes", style: TextStyle(color:
Colors.black)),
),
),
const SizedBox(width: 15),
Expanded(
child: ElevatedButton(
onPressed: () {
print('no selected');
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
primary: Colors.red.shade800,
),
child: const Text("No", style: TextStyle(color:
Colors.white)),
))
],
)
],
),
),
);
});
}
Related
I have a simple code
class A_View extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ViewModelBuilder<A_ViewModel>.reactive(
viewModelBuilder: () => A_ViewModel(),
builder: (context, model, child) => Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context, model.value)),
),
body: model.isBusy
? CircularProgressIndicator()
: ListView.builder(
.............
),
),
);
}
}
class A_ViewModel extends FutureViewModel {
int value = 10;
}
when i click button back in appbar . i was get value form pop
but when i click back button on my phone -> value is null
how to back button on my phone return value same button in appbar ?, please help me
To get the value from the back button pressed on your phone, you can use the willPopScope widget. Have a look into the below code:
return ViewModelBuilder<A_ViewModel>.reactive(
viewModelBuilder: () => A_ViewModel(),
builder: (context, model, child) => WillPopScope(
//here you can do the magic
onWillPop: ()async{
Navigator.pop(context, model.value);
return Future<bool>.value(false);
},
child:Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context, model.value)),
),
body: model.isBusy
? CircularProgressIndicator()
: ListView.builder(
.............
),
)
)
);
Hi I am pretty new to flutter and software development in general. I can't seem to understand how do I navigate from one activity to another.
What I really want is when I press the login button it should navigate me to a new activity
void main() {
runApp(MaterialApp(
title: 'button navigation',
home: HomeActivity(),
));
}
class HomeActivity extends StatelessWidget{
gotoSecondActivity(BuildContext context){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondActivity()),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Go To Second Screen'),
color: Colors.red,
textColor: Colors.white,
onPressed: () {
gotoSecondActivity(context);
},
),
),
);
}
}
class SecondActivity extends StatelessWidget {
goBack(BuildContext context){
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
goBack(context);
},
color: Colors.green,
textColor: Colors.white,
child: Text('Go Back To Previous Screen'),
),
),
);
}
}
Here is the code for second activity which i want to navigate to:
class SecondActivity extends StatelessWidget {
goBack(BuildContext context){
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
goBack(context);
},
color: Colors.green,
textColor: Colors.white,
child: Text('Go Back To Previous Screen'),
),
),
);
}
}
How does onclick function works on flutter and how do I implement this on my button
For you to understand the concept of navigation, think of a stack of papers stacked.
Every time you use:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => AnotherPage(),));
you are "putting" a paper on that pile.
push is used to "put" a new paper in that pile.
pop is used to "pull" a paper out of that pile,in other words, you will go back to the previous paper(page)
Ex: Navigator.pop(context);
And there are other types of navigation, I suggest you see the documentation after, as #dm_tr said above.
To navigate from one screen to another, you must use the Navigator. Here is an example to navigate on another page which class name is AnotherPage as you may see.
Navigator.of(context).push(MaterialPageRoute(builder: (context) => AnotherPage(),));
But, while this may answer your question, consider learning flutter basics first before trying to make an App. Here is an example of a good explanation of Navigation
You have to use navigator.
Create a class and put that inside:
Future push(BuildContext context, Widget page, {bool replace = false}) {
if(replace) {
return Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context) {
return page;
}));
}
return Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return page;
}));
}
bool pop<T extends Object>(BuildContext context, [ T result ]) {
if(Navigator.canPop(context)) {
Navigator.pop(context);
return true;
}
return false;
}
After that you can navigate between the pages with this:
push(context, yourActivity());
pop(context);
I have Flutter App with nested Navigator and I want to override "onBackPressed" using WillPopScope in my nested screen.
Let's say I have MainScreen, PrimaryScreen, and SecondaryScreen.
PrimaryScreen is nested Navigator, it have several screen like PrimaryOneScreen, PrimaryTwoScreen, and PrimaryThreeScreen.
SecondaryScreen is also a nested Navigator, just like PrimaryScreen, it have 3 screens.
Here the illustration of what i want to achieve
MainScreen -> PrimaryScreen(PrimaryOneScreen -> PrimaryTwoScreen -> PrimaryThreeScreen)
When my position on PrimaryTwoScreen, I want to get back to PrimaryOneScreen with overriding "onBackPressed" using WillPopScope Widget. But onWillPop never called when I press back button, and my screen go back directly to MainScreen.
Here are my codes
MainScreen.dart
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MainApp(),
onGenerateRoute: (settings){
WidgetBuilder builder;
switch(settings.name){
case 'primary' :
builder = (BuildContext _) => PrimaryScreen();
break;
case 'secondary' :
builder = (BuildContext _) => SecondaryScreen();
break;
case 'main' :
builder = (BuildContext _) => MainApp();
break;
default :
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings
);
},
);
}
}
class MainApp extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary');
},
child: Text('To Page Primary'),
),
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'secondary');
},
child: Text('To Page Secondary'),
)
],
),
);
}
}
PrimaryScreen.dart
class PrimaryScreen extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Navigator(
initialRoute: 'primary/pageone',
onGenerateRoute: (RouteSettings settings){
WidgetBuilder builder;
switch(settings.name){
case 'primary/pageone' :
builder = (BuildContext _) => PrimaryOneScreen();
break;
case 'primary/pagetwo' :
builder = (BuildContext _) => PrimaryTwoScreen();
break;
case 'primary/pagethree' :
builder = (BuildContext _) => PrimaryThreeScreen();
break;
default :
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings
);
},
);
}
}
PrimaryOneScreen.dart
class PrimaryOneScreen extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Primary Page'),
),
body: Center(
child: Column(
children: <Widget>[
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pagetwo');
},
child: Text('To Page Two'),
),
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pagethree');
},
child: Text('To Page Three'),
),
],
),
),
);
}
}
PrimaryTwoScreen.dart
class PrimaryTwoScreen extends StatelessWidget{
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()async{
print('willPopScope');
Navigator.of(context, rootNavigator: false).pop();
return true;
},
child: Scaffold(
appBar: AppBar(
title: Text('Secondary Page'),
),
body: Center(
child: Column(
children: <Widget>[
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pageone');
},
child: Text('To Page One'),
),
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pagethree');
},
child: Text('To Page Three'),
),
],
),
),
),
);
}
}
PrimaryThreeScreen.dart
class PrimaryThreeScreen extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Primary Page'),
),
body: Center(
child: Column(
children: <Widget>[
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pageone');
},
child: Text('To Page One'),
),
FlatButton(
onPressed: (){
Navigator.pushNamed(context, 'primary/pagetwo');
},
child: Text('To Page Two'),
)
],
),
),
);
}
}
EDIT
I added an image to illustrate what I want to achieve.
How to pop only on nested navigator?
Thanks!
When pressing the back button, I think it will use the main navigator of your app, because it doesn't know the nested navigator what you want to interact with.
To solve it, you have to assign a key for your navigator to be able to use it later. And adding a WillPopScope at widget which you use your nested navigator to handle user action.
Based on your example, I added some code. Noting, the maybePop() function will call the WillPopScope of your widget of the nested navigator. If you call pop(), it will pop directly, ignoring the WillPopScope of your widget.
class PrimaryScreen extends StatelessWidget{
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_navigatorKey.currentState != null) {
_navigatorKey.currentState!.maybePop();
return false;
}
return true;
},
child: Navigator(
key: _navigatorKey,
initialRoute: 'primary/pageone',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch(settings.name){
case 'primary/pageone' :
builder = (BuildContext _) => PrimaryOneScreen();
break;
case 'primary/pagetwo' :
builder = (BuildContext _) => PrimaryTwoScreen();
break;
case 'primary/pagethree' :
builder = (BuildContext _) => PrimaryThreeScreen();
break;
default :
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings
);
},
),
);
}
}
Try with this link https://medium.com/flutter-community/flutter-push-pop-push-1bb718b13c31
Check Use-case : pushReplacementNamed tag description
I have a material designed Navigation Drawer in my very first flutter app. This work's fine but I did't find any way to close the Navigation Drawer on Back Button Press if it's open when use WillPopScope to show AlertDialog. The application just show AlertDialog instead of close the Drawer when back press. I want Drawer should close if already open and show AlertDialog otherwise.
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: onBackPressed,
child: Scaffold(
appBar: AppBar(
title: Text("Home"),
),
drawer: Drawer(
),
body: Center(
child: Text("Home"),
),
),
);
}
onBackPressed shows dialog to close the app.
Future<bool> onBackPressed() {
return showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
title: Text("Do you want to exit?"),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.pop(context, false),
child: Text("No")),
FlatButton(
onPressed: () => Navigator.pop(context, true),
child: Text("Yes"))
],
));
}
Can anybody guide me how can I achieve this?
You can copy paste run full code below
Step 1: You need Scaffold key to controll Drawer so you need GlobalKey<ScaffoldState> _key = new GlobalKey<ScaffoldState>();
Step 2: In onWillPop You can check isDrawerOpen and do Navigator.pop
code snippet
GlobalKey<ScaffoldState> _key = new GlobalKey<ScaffoldState>();
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_key.currentState.isDrawerOpen) {
Navigator.of(context).pop();
return false;
}
return true;
},
child: Scaffold(
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
print("My App Page");
//return false;
},
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: "test",),
),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
GlobalKey<ScaffoldState> _key = new GlobalKey<ScaffoldState>();
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_key.currentState.isDrawerOpen) {
Navigator.of(context).pop();
return false;
}
return true;
},
child: Scaffold(
key: _key,
appBar: AppBar(title: Text(title)),
body: Center(child: Text('My Page!')),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('page 2'),
onTap: () {
Navigator.push(
context, new MaterialPageRoute(builder: (context) => Page2()));
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
print("Page2");
_popNavigationWithResult(context, 'from_back');
return false;
},
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
_popNavigationWithResult(context, 'from_button');
},
),
body: Container(
child: Center(
child: Text('Page 2',
style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold)),
),
),
),
);
}
void _popNavigationWithResult(BuildContext context, dynamic result) {
Navigator.pop(context, result);
}
}
my Back button was not working on drawer, because of my home screen. Remove WillPopScope from drawer and put following code in the home screen instead.
WillPopScope(
onWillPop: () async {
return Navigator.of(context).canPop();
},
child: Scaffold(
Simply add Navigator.pop(context);
ListTile(
title: const Text('Item 1'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
I am still looking a better statement to fully express my problem here, so in detail here it is
Using Provider allows rebuilding widgets on changes which will affect them. Great!.
Suppose on the main screen(say HomePage) I toggle the theme between dark and light via a switch. The widgets are rebuilt on the main screen.
Now I navigate to some other screen(say NextScreen).
Here a button allows me to toggle the theme again. I toggle the theme then the widgets on the current screen(which is NextScreen) are rebuilt. But the Flutter Performance tab on Android Studio shows that the widgets on the previous screen(HomePage) are also redrawn.
So, my questions are
Why does this happen?
Is there a way to prevent rebuilding widgets which are not on the current screen and rebuild them only when we go back to the particular screen?
My code is a simple example of 90 lines
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ThemeState>(
child: MaterialApp(
home: HomePage(),
routes: {
"next": (context) => NextScreen(),
},
),
builder: (BuildContext context) => ThemeState(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<ThemeState>(builder: (BuildContext context, ThemeState value, Widget child) {
return Theme(
data: value.getTheme(),
child: Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
SwitchListTile(
title: Text("Enable dark mode"),
value: value.isDarkModeOn,
onChanged: (_) => value.toggleTheme(),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.pushNamed(context, "next"),
child: Icon(Icons.navigate_next),
),
),
);
});
}
}
class NextScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<ThemeState>(
builder: (context, value, child) {
return Theme(
child: Scaffold(
appBar: AppBar(
title: Text("Next Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () => value.toggleTheme(),
child: Text("Toggle theme"),
),
),
),
data: value.getTheme(),
);
},
);
}
}
class ThemeState with ChangeNotifier {
bool isDark = true;
static ThemeData darkTheme = ThemeData.dark();
static ThemeData lightTheme = ThemeData.light();
ThemeData _currentTheme = darkTheme;
ThemeData getTheme() => _currentTheme;
get isDarkModeOn => isDark;
toggleTheme() {
_currentTheme = isDark ? lightTheme : darkTheme;
isDark = !isDark;
notifyListeners();
}
}
As the your consumer provider widget is the parent of other widgets, so when the provider is updating on toggle, consumer child widget will also update.