Flow is: user clicks on a link from screen 1 to go to screen 2. On screen 2, user enters required data and taps on save button that saves data and navigates to screen 1 where I want to show a snackbar.
This sounds very similar to this and this post, but it's not working for me. So, I followed this code sample which works properly, but there's one issue.
If I press app bar back button or device back button from screen 2, it still shows the snackbar on screen 1. Wondering how could I avoid this behavior of not showing snackbar after hitting back buttons.
After user saves data on screen 2, I am simply using Navigator.pop(context) that takes user to screen 1. On screen 1, I've a method that navigates to screen 2 and triggers snackbar as below:
Navigator.push(
context, MaterialPageRoute(builder: (context) => Screen2())
);
scaffoldKey.currentState.showSnackBar(SnackBar(content: Text('Show me'),));
Although this works, I don't want to show the snackbar if user clicks back button.
Problem is, your snackBar has duration, within that duration if you navigate back to the screen, you'll see that old snackBar again.
Solution is add this method before Navigator:
Scaffold.of(context).removeCurrentSnackBar();
In your case, you're using scaffoldKey, so translate it as you need. But, be sure you are contacting with right Scaffold.
scaffoldKey.currentState.removeCurrentSnackBar();
Official link:
https://api.flutter.dev/flutter/material/ScaffoldState/removeCurrentSnackBar.html
Edit: About calling Screen 1 Scaffold inside Screen 2 Scaffold:
Your screen has Scaffolds. Scaffolds have keys in your situation as I see.
Let's say Screen1 has screen1Key ScaffoldState key. Same for Screen2.
Inside Screen2 you must call
screen1Key.currentState.removeCurrentSnackBar();
full example:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Screen1(),
));
}
GlobalKey<ScaffoldState> screen1Key = GlobalKey<ScaffoldState>();
GlobalKey<ScaffoldState> screen2Key = GlobalKey<ScaffoldState>();
class Screen1 extends StatefulWidget {
#override
_Screen1State createState() => _Screen1State();
}
class _Screen1State extends State<Screen1> {
#override
Widget build(BuildContext context) {
return Scaffold(
key: screen1Key,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
onPressed: () {
screen1Key.currentState
.showSnackBar(SnackBar(content: Text('Screen 1 SnackBar')));
},
child: Text('Show SnackBar'),
),
RaisedButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return Screen2();
}));
},
child: Text('Navigate forward'),
),
],
),
),
);
}
}
class Screen2 extends StatefulWidget {
#override
_Screen2State createState() => _Screen2State();
}
class _Screen2State extends State<Screen2> {
#override
Widget build(BuildContext context) {
return Scaffold(
key: screen2Key,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
onPressed: () {
screen2Key.currentState
.showSnackBar(SnackBar(content: Text('Screen 2 SnackBar')));
},
child: Text('Show SnackBar'),
),
RaisedButton(
onPressed: () {
screen1Key.currentState.removeCurrentSnackBar();
Navigator.pop(context);
},
child: Text('Navigate Back'),
),
],
),
),
);
}
}
I hope I understand well your problem.
Related
This issue is related with github #2502.
I am using GetMaterialApp from this package.
I'm not sure if this is a bug or not.
How to make the function in the first dialog useable by using Get.toNamed()?
It happened when using the Get.toNamed().
It works fine with Navigator.push() but I need Get.toNamed for the web app.
The first page has a button that will show the first dialog.
The first dialog will show the order type button list.
When pressing an order type button, the program will find a new order of this type and open the second page with a new order data.
The second page has some work to do and this work will open the second dialog.
After finishing this work, the user will click on the back button back to the first page and find a new order again.
The problem is when the second dialog works on the second page.
The first dialog on the first page will not work.
see video example.
web example.
code example:
import 'package:flutter/material.dart';
import 'package:flutter_test_exam_bug/config/path/page_path.dart';
import 'package:get/get.dart';
Future<void> _showMyDialog({required BuildContext context, required Widget child}) async {
return showDialog<void>(
context: context,
builder: (BuildContext context) => child,
);
}
class PageTest extends StatefulWidget {
const PageTest({Key? key}) : super(key: key);
#override
_PageTestState createState() => _PageTestState();
}
class _PageTestState extends State<PageTest> {
#override
Widget build(BuildContext context) {
Widget dialog_ = Center(
child: ElevatedButton(onPressed: () => Get.toNamed(PagePath.test2), child: const Text("Open second page"))),
openDialogButton_ = ElevatedButton(
onPressed: () => _showMyDialog(context: context, child: dialog_), child: const Text("Open first dialog"));
return Scaffold(body: SafeArea(child: Center(child: openDialogButton_)));
}
}
class PageTest2 extends StatefulWidget {
const PageTest2({Key? key}) : super(key: key);
#override
State<PageTest2> createState() => _PageTest2State();
}
class _PageTest2State extends State<PageTest2> {
ButtonStyle buttonStyle = ElevatedButton.styleFrom(primary: Colors.green);
#override
Widget build(BuildContext context) {
Widget dialog_ = Center(
child: ElevatedButton(
onPressed: () => Navigator.pop(context), child: const Text("I am second dialog"), style: buttonStyle)),
openDialogButton_ = ElevatedButton(
onPressed: () => _showMyDialog(context: context, child: dialog_),
child: const Text("Open second dialog"),
style: buttonStyle);
return Scaffold(appBar: AppBar(), body: SafeArea(child: Center(child: openDialogButton_)));
}
}
I think it is a bug.
When opening a dialog, the GETX ROUTE will change to the current page again.
Follow this in https://github.com/jonataslaw/getx/issues/2502
I want my page to hit the API always whenever it enters a page.
For example, I am having 2 screens i.e FirstSCreen and SecondScreen.
In FirstScreen I am calling an API to fetch some data. So if the user navigates from FirstScreen to SecondScreen and then comes back to FirstScreen by pressing the back button It should hit the API in FirstScreen again.
I want to know is there any inbuilt function in flutter where I should call my methods so that it can work every time it enters the screen. I have tried using didUpdateWidget() but it is not working the way I want.
initState() is also called only once the widget is loaded ..
Please explain me
You can use async await for it. Let's say you have a button that change the route.
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondScreen(),
),
);
ApiCall();
},
The ApiCall function will call only when user push the back on the second screen.
I have an example that might help. However, it might not cover all scenarios where your FirstScreen will always call a specific function whenever the page is displayed after coming from a different page/screen, or specifically resuming the app (coming from background -- not to be confused when coming from another screen or popping).
My example however, will always call a function when you come back from a specific screen, and you can re-implement it to other navigation functions to ensure that a specific function is always called when coming back to FirstScreen from other screens.
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
home_page.dart
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: RaisedButton(
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => SecondPage()))
.then((value) => _reload(value)),
child: Text('Navigate to Next Page'),
),
)
],
),
);
}
Future<void> _reload(var value) async {
print(
'Home Page resumed after popping/closing SecondPage with value {$value}. Do something.');
}
}
second_page.dart
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
Navigator.pop(context, 'Passed Value');
/// It's important that returned value (boolean) is false,
/// otherwise, it will pop the navigator stack twice;
/// since Navigator.pop is already called above ^
return false;
},
child: Scaffold(
appBar: AppBar(),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(child: Text('Second Page')),
],
),
),
);
}
}
#Rick's answer works like a charm even for Riverpod state management as I coul d not get the firstscreen to rebuild to show changes to the 'used' status of a coupon on the second screen where there is a button to use it.
So I did on the details page:
return WillPopScope(
onWillPop: () async {
//final dumbo = ref.read(userProvider.notifier).initializeUser();
Navigator.pop(context);
return false; //true;
},
Then on the coupons list page, each coupon card has:
child: Card(
elevation: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24.0),
side: BorderSide(
color: coupons[index].isUsed
? Colors.grey
: Colors.blue),
),
child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
//go to Details screen with Coupon instance
//https://api.flutter.dev/flutter/widgets/Navigator/pushNamed.html
Navigator.pushNamed(context, '/details',
arguments: coupons[index].couponId)
.then((_) => _reload());
/*Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => DetailsScreen(
coupon: coupons[index],
),
),
);*/
},
where I used _ since there is no parameter.
Then the function in class CouponScreen is:
void _reload() {
setState(() {});
}
I believe setState(){} is what triggers the rebuild. I apologize for the formatting but it's very difficult to copy and paste Flutter code that is nested deeply.
i'm brand new to Flutter.
I'm trying to open a panel pressing a button and than closing it by pressing a button on that panel.
I've managed to do it by writing the code in the same page.
What i can't do is splitting the code and keep everything working.
What I'm actually doing is calling a variable in the State of a widget that is initialized False and then with an if statement i'm calling: or an empty container or the panel i want.
When i press the button i call SetState(){} and the variable changes to true to let the panel appears, then in the panel there's button that do opposite thing.
Assuming that what i'm doing it is correct. How to i keep doing this with the panel refactored in a new page?
I've red something about streams and inherited widgets but i haven't completely understood
If I understand correctly, you want to notify a StatefullWidget from another StatefullWidget. There are several approaches on this one but since you've mentioned Streams, I'll try to post an example and explain a bit this scenario.
So basically, you can consider the streams like a pipe linked to a faucet in one end and the other end it's added into a cup (the end can be split in multiple ends and put in multiple cups, "broadcast streams").
Now, the cup is the listener (subscriber) and waits for water to drop trough the pipe.
The faucet is the emitter, and it will emit water droplets when the faucet is opened.
The faucet can be opened when the other end is put into a cup, this is a smart faucet with some cool sensors, (the emitter will start emitting events when a subscriber is "detected).
The droplets are actual events that are happening in the the app.
Also you must remember to close the faucet in order to avoid a massive leak from your cup into the kitchen floor. (you must cancel the subscribers when you've done handling events to avoid a leak).
Now for your particular case here's the code snippet that kind of illustrate the above metaphor:
class ThePannel extends StatefulWidget { // this is the cup
final Stream<bool> closeMeStream; // this is the pipe
const ThePannel({Key key, this.closeMeStream}) : super(key: key);
#override
_ThePannelState createState() => _ThePannelState(closeMeStream);
}
class _ThePannelState extends State<ThePannel> {
bool _closeMe = false;
final Stream<bool> closeMeStream;
StreamSubscription _streamSubscription;
_ThePannelState(this.closeMeStream);
#override
void initState() {
super.initState();
_streamSubscription = closeMeStream.listen((shouldClose) { // here we listen for new events coming down the pipe
setState(() {
_closeMe = shouldClose; // we got a new "droplet"
});
});
}
#override
void dispose() {
_streamSubscription.cancel(); // THIS IS QUITE IMPORTANT, we have to close the faucet
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
SomeWidgetHere(shouldClose: _closeMe),
RaisedButton(
onPressed: () {
setState(() {
_closeMe = true;
});
},
)
],
);
}
}
class SomeWidgetThatUseThePreviousOne extends StatefulWidget { // this one is the faucet, it will emit droplets
#override
_SomeWidgetThatUseThePreviousOneState createState() =>
_SomeWidgetThatUseThePreviousOneState();
}
class _SomeWidgetThatUseThePreviousOneState
extends State<SomeWidgetThatUseThePreviousOne> {
final StreamController<bool> thisStreamWillEmitEvents = StreamController(); // this is the end of the pipe linked to the faucet
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ThePannel(closeMeStream: thisStreamWillEmitEvents.stream), // we send the other end of the pipe to the cup
RaisedButton(
child: Text("THIS SHOULD CLOSE THE PANNEL"),
onPressed: () {
thisStreamWillEmitEvents.add(true); // we will emit one droplet here
},
),
RaisedButton(
child: Text("THIS SHOULD OPEN THE PANNEL"),
onPressed: () {
thisStreamWillEmitEvents.add(false); // we will emit another droplet here
},
)
],
);
}
#override
void dispose() {
thisStreamWillEmitEvents.close(); // close the faucet from this end.
super.dispose();
}
}
I hope that my analogy will help you understand a bit the streams concept.
If you want to open an dialog (instead of what you call a "panel") you can simply give the selected data back when you close the dialog again.
You can find a good tutorial here: https://medium.com/#nils.backe/flutter-alert-dialogs-9b0bb9b01d28
you can navigate and return data from another screen like that :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
));
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
body: Center(child: SelectionButton()),
);
}
}
class SelectionButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
// A method that launches the SelectionScreen and awaits the result from
// Navigator.pop!
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that will complete after we call
// Navigator.pop on the Selection Screen!
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result!
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
}
}
class SelectionScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Nope!" as the result
Navigator.pop(context, 'Nope.');
},
child: Text('Nope.'),
),
)
],
),
),
);
}
}
for more details about navigation:
https://flutter.dev/docs/cookbook/navigation/returning-data
This question already has answers here:
Flutter Navigation pop to index 1
(12 answers)
Closed 4 years ago.
So I have this kind of walkthrough that pushes the user from one screen to another (Six screens in total). In the last page, I would like the user to press "Done" and take him back to the first screen without being able to pop back to any of the previous screens.
Right now it will pop the last screen but not any of the other ones so it take me back to my original screen with the ability to pop back to the screen before the last screen (hopefully that last sentence makes sense to you, because the last screen was popped it takes me back to the screen before that one).
Any idea how to get around that?
Thanks!
To pop multiple screens from the navigation stack, like in your given scenario we can use Navigator.popUntil. It takes a BuildContextand a RoutePredicate as parameters. The Navigator calls pop until the returned value by the given RoutePredicate is true.
Here is very basic example. It uses ModalRoute.withName to create the RoutePredicate:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (BuildContext context) => Home(),
'/another': (BuildContext context) => Another()
},
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Home'),
RaisedButton(
child: Text('Take me to another screen!'),
onPressed: () => Navigator.pushNamed(context, '/another'),
)
],
),
),
);
}
}
class Another extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('Next screen.'),
onPressed: () => Navigator.pushNamed(context, '/another'),
),
RaisedButton(
child: Text('Pop em all.'),
onPressed: () => Navigator.popUntil(
context,
ModalRoute.withName('/'),
),
),
],
),
),
);
}
}
The method you are looking for is popUntil(), take a look at the implementation https://api.flutter.dev/flutter/widgets/Navigator/popUntil.html or https://api.flutter.dev/flutter/widgets/NavigatorState/popUntil.html
You will have to do probably something like
Navigator.of(context).popUntil(ModalRoute.withName('/my-target-screen'));
(take a look at https://api.flutter.dev/flutter/widgets/ModalRoute/withName.html)
or use some custom function (https://api.flutter.dev/flutter/widgets/RoutePredicate.html).
I am trying to set up a navigation when logged out (forgot password, signup/login) compared to when logged in (home, sign out, lots of stuff).
I am at a complete loss as to how to do this. All the suggestions I see drop out one part of the system, a single page to show a login, but that doesn't work here. If I make the navigation shared, then every page in the rest of the app would need a logged in check, which sounds a bit irritating. Is there an easy way to swap out the navigation setups? Add in navigation dynamically, maybe based on the user logged in/out status?
Could I just subclass the navigation class itself and handle it that way, maybe?
In React Native you can do this by swapping out the navigator you are using between a logged in one and a logged out one. Looking for something that has a similar outcome to that.
React-Native allows nesting navigators, but flutter doesn't. There are multiple ways of doing it though without nesting any navigators after all, a simple example of how it can be done with flutter is shown below.
Example:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
// Main Application
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Example',
// Routes
routes: <String, WidgetBuilder>{
'/': (_) => new Login(), // Login Page
'/home': (_) => new Home(), // Home Page
'/signUp': (_) => new SignUp(), // The SignUp page
'/forgotPassword': (_) => new ForgotPwd(), // Forgot Password Page
'/screen1':(_) => new Screen1(), // Any View to be navigated from home
},
);
}
}
// The login page
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text("Login Page"),
// The button on pressed, logs-in the user to and shows Home Page
new FlatButton(
onPressed: () =>
Navigator.of(context).pushReplacementNamed("/home"),
child: new Text("Login")),
// Takes user to sign up page
new FlatButton(
onPressed: () => Navigator.of(context).pushNamed("/signUp"),
child: new Text("SignUp")),
// Takes user to forgot password page
new FlatButton(
onPressed: () =>
Navigator.of(context).pushNamed("/forgotPassword"),
child: new Text("Forgot Password")),
],
),
),
);
}
}
// Home page
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text("Home Page"),
// Logs out user instantly from home
new FlatButton(
onPressed: () => Navigator.of(context).pushReplacementNamed("/"),
child: new Text("Logout")),
// Takes user to Screen1
new FlatButton(
onPressed: () => Navigator.of(context).pushNamed("/screen1"),
child: new Text("Screen 1")),
],
),
),
);
}
}
// Sign Up Page
class SignUp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text("Sign Up Page"),
// To make an api call with SignUp data and take back user to Login Page
new FlatButton(
onPressed: () {
//api call to sign up the user or whatever
Navigator.of(context).pop();
},
child: new Text("SignUp")),
],
),
),
);
}
}
// Forgot Password page
class ForgotPwd extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text("Sign Up"),
// Make api call to resend password and take user back to Login Page
new FlatButton(
onPressed: () {
//api call to reset password or whatever
Navigator.of(context).pop();
},
child: new Text("Resend Passcode")),
],
),
),
);
}
}
// Any Screen
class Screen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text("Screen 1"),
// Takes the user to the view from which the user had navigated to this view
new FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: new Text("Back")),
// Takes back the user to Home page and Logs out the user
new FlatButton(
onPressed: () async {
Navigator.of(context).popUntil(ModalRoute.withName("/home")); // Use popUntill if you want to reset all routes untill now and completely logout user
Navigator.of(context).pushReplacementNamed("/");
// Just to show login page and resume back after login
// Navigator.of(context).pushNamed('/Login');
// On login page after successful login Navigator.of(context).pop();
// the app will resume with its last route.
},
child: new Text("Logout")),
],
),
),
);
}
}
Note: I'm not saying this is the best approach, but the example shows one of the simple ways of doing it.
Hope that helps!
You can do this
import 'package:app/pages/home.page.dart';
import 'package:app/pages/login.page.dart';
import 'package:app/services/auth.service.dart';
import 'package:flutter/material.dart';
AuthService appAuth = new AuthService();
void main() async {
// add this, and it should be the first line in main method
WidgetsFlutterBinding.ensureInitialized();
// Set default home.
Widget _defaultHome = new LoginPage();
// Get result of the login function.
bool _result = await appAuth.login();
if (_result) {
_defaultHome = new HomePage();
}
// Run app!
runApp(new MaterialApp(
title: 'App',
home: _defaultHome,
routes: <String, WidgetBuilder>{
// Set routes for using the Navigator.
'/home': (BuildContext context) => new HomePage(),
'/login': (BuildContext context) => new LoginPage()
},
));
}
Detail Explanation here https://medium.com/#anilcan/how-to-use-dynamic-home-page-in-flutter-83080da07012
You can use this below code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
debugShowCheckedModeBanner: false,
home: Builder(
builder: (_) {
final _user = Hive.box<User>('user').get(0);
if (_user != null) {
if (_user.nameFamily.isNotEmpty) {
return Dashboard();
} else {
return Profile();
}
}
return SignUP();
},
),
...
);
}
}