Flutter: Add result to Navigator when system back button is pressed - android

In my example app below, I have two routes: HomeRoute and OtherRoute. I want OtherRoute to always return a result when it is popped from the navigator. In this example, it does this when the RaisedButton in OtherRoute is pressed. However, I also want it to return a result when the back button is pressed. I know I can override the back button on the AppBar, but I also want to override the behavior of the "system" back button on Android.
I know I can use WillPopScope to prevent the system back button from popping the current route, but I do not want this. If possible, I want the system back button to still be able to pop the current route, just with a result included. Is this possible?
Here is my sample app:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: Scaffold(
appBar: AppBar(title: Text("Home"),),
body: HomeRoute(),
),
);
}
}
class HomeRoute extends StatelessWidget{
#override
Widget build(BuildContext context) {
return RaisedButton(onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context){
return OtherRoute();
})).then((result){
// I want result here to never be null
Scaffold.of(context).showSnackBar(SnackBar(content: Text("$result")));
});
});
}
}
class OtherRoute extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Other route"),),
body: RaisedButton(onPressed: (){
Navigator.pop(context, "Result!");
})
);
}
}

Wrap your scaffold with WillPopScope in OtherRoute and override onWillPop method as follows:
#override
Widget build(BuildContext context) {
return WillPopScope(
//....
),
onWillPop: () async {
Navigator.pop(context, "Result!");
return false;
},
);
}

Related

How to prevent android device back button from going back to splash screen in flutter?

I have made a logo screen for my app which redirects the user to the appropriate location (home screen if the user is logged in and the welcome/splash screen if he is not). After I reach the home screen I can go back to the previous screen by pressing the back button on the android phone. I want to prevent the user from going back to the logo screen.
Things I have tried:
WillPopScope() with onWillPop returning false
My logo screen code:
class LogoScreen extends StatefulWidget {
#override
_LogoScreenState createState() => _LogoScreenState();
}
class _LogoScreenState extends State<LogoScreen> {
var preferences;
#override
void initState() {
super.initState();
startTimer();
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"assets/images/g1177.png",
scale: 10.0,
),
],
),
),
),
);
}
startTimer() {
var _duration = Duration(milliseconds: 2000);
return Timer(_duration, navigate);
}
navigate() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
if (preferences.getBool("is_logged_in") == true) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return HomeScreen();
}));
} else {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return SplashScreen();
}));
}
}
}
Why this approach doesn't work on my device?
Use .pushReplacement(...) instead of .push(...)
pushReplacement Replace the current route of the navigator that most tightly encloses
the given context by pushing the given route and then disposing the
previous route once the new route has finished animating in.
I figured it out. If I apply WillPopScope() on the Logo Screen then when I am on the logo screen, if I press the back button then I can't go further back from that screen. Hence, if I use WillPopScope on the home screen then I can't use the back button on that screen. Here is my Home Screen build method:
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false;
},
child: Scaffold(
//add your UI here like your appBar, body, bottomNavBar etc..
) //Scaffold
); //WillPopScope

How do I make the build function wait until a button is pressed in an alert in init?

I am trying to display an Alert that shows a disclaimer to the user as soon as the app is opened. The build method will run, that is the app will start its processing only after the user presses okay on the alert.
I've managed to show the alert in init using
SchedulerBinding.instance.addPostFrameCallback((_) => AlertWindow().showAlert(context));
or
Future.delayed(Duration.zero, () => AlertWindows().showAlert(context));
This shows the alert, but the app starts building in the background. I want the app to run/build only after OKAY button is pressed, and after the alert is popped.
Hey I implemented some code, you can try this code directly on dartPad Paste the code in this Editor
I used setState, if it is for real time project you can use Providers or bloc, for performance.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Widget viewHolder;
void initState() {
viewHolder = Container();
WidgetsBinding.instance
.addPostFrameCallback((_) => afterPostFrameCallBack());
super.initState();
}
afterPostFrameCallBack() {
_showDialog();
}
#override
Widget build(BuildContext context) {
return viewHolder;
}
Widget _buildView() {
return Container(child: Text('This is after okay button'));
}
void _showDialog() {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: Text("App Update Available"),
content: Text(
"We have fixed some issues and added some cool features in this update"),
actions: <Widget>[
// usually buttons at the bottom of the dialog
FlatButton(
child: new Text("ok"),
onPressed: () {
Navigator.of(context).pop();
setState(() {
viewHolder = _buildView();
});
},
),
],
);
},
);
}
}

Flutter: close app on back press is not working

In the app, on starting splash screen appears after that I reach to the login screen where there is no app bar or back button but I'm able to back from device back button. I don't want to back on the splash screen when the back is pressed from login screen. I tried many solutions but they are not working.
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'xyz',
home: LoginPage(),
debugShowCheckedModeBanner: false,
);
}
}
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
if (Navigator.canPop(context)) {
Navigator.pop(context); // i tried both the way
Navigator.of(context).pop();
} else {
SystemNavigator.pop();
}
},
child:Scaffold(
body: Container(.........................//here is my desiging stuff
I also tried return Future.value(false); with true and false value –
Splash screen code
#override
void initState (){
super.initState();
// TODO initial state stuff
new Future.delayed(
const Duration(seconds: 2),
() => Navigator.push(
context,
MaterialPageRoute(builder: (context) => Login()),
));
}
Simply pass empty function to - WillPopScope widget.
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {}, // Empty Function.
child: Scaffold(
body: Container(), //here is my desiging stuff
));
}
}
OR
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false;
},
child: Scaffold(
body: Container(),
appBar: AppBar(),
),
);
}
If you never want to goto Splash Screen.
It's Better to use:
Navigator.of(context)
.pushReplacement(MaterialPageRoute(builder: (context) => Login()));
Just change your code to:
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()async => false,
child:Scaffold(
body: Container(.........................//here is my desiging stuff
I had the same problem, and it was because wrongly surrounding MaterialApp with WillPopScope (instead of surrounding Scaffold).
Appreciating #anmol-majhail, here is the correct solution in my case:
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
home: WillPopScope(
child: Scaffold(...),
onWillPop: () async {
return false;
}),
);
}

initState called multiple times

In my app I have a login screen and a home screen. When navigating from the login to the home screen I read data from a .txt file and show 4 random data points. I am getting the data from the file in my initState so that it isn't called multiple times when the state changes and then waiting on it with a future builder like...
class _HomeScreenState extends State<HomeScreen> {
Future<bool> _future;
#override
initState() {
super.initState();
print('in initState about to call _getData');
_future = _getData();
}
#override
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.active:
case ConnectionState.waiting:
return new Center(
child: new CircularProgressIndicator(),
);
case ConnectionState.done:
if (snapshot.hasError) {
return new Center(
child: Text('Error'),
);
} else {
return new ListView(
children: <Widget>[
//my view
],
);
}
}
}
);
return MaterialApp(
home: WillPopScope(
onWillPop: () async {
return Navigator.pop(context);
},
child: Scaffold(
body: futureBuilder,
),
)
);
Now when I navigate back to the login screen using the back button and then go back to the home screen initState will go off multiple times (this can be seen by the print statement I left in). As you go back and forth between these two screens (pop homescreen, push homescreen) initState will be called exponentially more times. I am so confused, any help is appreciated!
EDIT: Full code for both login and home screen can be found https://github.com/ViscousOx/Flutter-Stuff
Try that: It work
Use the default statement with the ConnectionState.waiting.
class _HomeScreenState extends State<HomeScreen> {
Future<bool> _future;
#override
initState() {
super.initState();
print('in initState about to call _getData');
_future = _getData();
}
#override
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Center(
child: new CircularProgressIndicator(),
);
default:
if (snapshot.hasError) {
return new Center(
child: Text('Error'),
);
} else {
return new ListView(
children: <Widget>[
//my view
],
);
}
}
}
);
return MaterialApp(
home: WillPopScope(
onWillPop: () async {
return Navigator.pop(context);
},
child: Scaffold(
body: futureBuilder,
),
)
);
Connection waiting state can be avoided. I used as:
FutureBuilder(future: _isUserLoggedIn(),
builder: (ctx, loginSnapshot) =>
loginSnapshot.data == true ? AppLandingScreen() : SignUpScreen()
),
As you go back and forth between these two screens (pop homescreen, push homescreen)
It sounds like you're creating your HomeScreen each time you enter the screen, so it'll be a brand new widget, and thus normal that initState() gets called.
If you want to keep the same screen (object) around for the lifetime of your app, there are a number of solutions such as Stack and Offstage so that your widget lives on, but isn't visible.

Flutter Android back button navigating back to main page

I would like to be able to use the back button to navigate back to the main page instead of closing the app, I have seen the WillPopScope widget option but that needs to show a dialog, is there a way to pop back using the android back button without a dialog?
You can Use Navigator.canPop() Method to avoid app from Exiting.
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return Navigator.canPop(context);
},
child: Scaffold(
appBar: AppBar(title: const Text('HomePage')),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
backgroundColor: Colors.red,
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => SecondPage()));
},
),
),
);
}
}
The onWillPop callback needs a Future of bool returned. You just can do
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
return Future.value(true); // or return Future.value(false);
},
child: Container()
);
}

Categories

Resources