How to pop two screens without using named routing? - flutter

For example, my current routing is like this:
Login -> Screen1 -> Screen2 -> Screen3 -> Screen4
I'd like to go back to Screen2 from Screen4.
I can't use named routing, because I have to pass a parameter to Screen2.
Push Screen2 in Screen4 is not a good solution.

Use popUntil method of Navigator class.
e.g.
int count = 0;
Navigator.of(context).popUntil((_) => count++ >= 2);
However, I would recommend defining names for your routes and using popUntil as it is designed as per docs.

You can just pop it two times;
nav = Navigator.of(context);
nav.pop();
nav.pop();

The class from which the transition will be made as StatefulWidget. To press action add the pushNamed navigator with then, which will trigger after returning to this screen. Pass setState to update the widget:
onTap: () {
Navigator.pushNamed(
context,
RouteNames.viewExercises,
).then((value) {
setState(() {});
});
},
Screen from which to return to be used:
Navigator.of(context)
..pop()
..pop()
..pop();
where ..pop() is used as many times as needed to back.

if you would like to pop until three times, You can use the code in below.
int count = 3;
Navigator.of(context).popUntil((_) => count-- <= 0);

Related

Navigate to another tab from within MaterialPageRoute

I expected this issue to have a simple solution but I didn't find yet any...
I have few tabs in my app, in one of them I open another screen using
Navigator.push(context, MaterialPageRoute(...
Once user clicks on a button in that screen I want to pop it and navigate to another tab.
I tried to pass TabController to the relevant tab and its child screen, but this doesn't seem like the simplest solution, and also not easy to accomplish since the controller is not yet defined:
tabController = DefaultTabController(
body: TabBarView(
children: [
FirstTab(
tabController: tabController // <- tabController is not defined yet at this point:(
Is there any "global" function to reset the app's "entire" route so it will both pop MaterialPageRoute and navigate to specific tab ?
You can use Navigator.of(context).pushReplacement
The solution I found is to call Navigator's push synchronously and check for its returned value. Then when I want to navigate to another tab I simply send true indication in Navigator's pop.
This is how my navigation method looks like, notice I had to add a short delay before navigating to another tab, not sure why, but it didn't work without it:
_navigateToDetailsScreen() async {
bool shouldNavigateToHomeTab = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailsScreen()),
));
if (shouldNavigateToHomeTab) {
Future.delayed(const Duration(milliseconds: 500), () {
DefaultTabController.of(context)!.animateTo(0);
});
}
}
And this is how I call pop:
Navigator.of(context).pop(true);
This looks like the simplest solution for me, and so far I didn't find any issues with it.

How to pop 2 screen at once in flutter

I have not created any routes to navigate between screens. I use Navigator to navigate:
Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage()));
what I have done is navigate to four screens from homePage to success screen:
HomePage => CreatePostScreen => CreateImagePost => SuccessfulScreen
when I reach to successfulscreen I would like to pop two screens and get back to CreatePostScreen.
I do not want to write Navigator.pop(context) two times.
I tried to use this, but it will come up with a black screen:
Navigator.popUntil(context, (route) => route is CreatePostScreen);
but this is not working. I would like to learn how flutter handles widget navigation not by route names and solution to this.
I know something about how navigator class handles with route name but I would like to know how to solve it if I push widgets and its working.
What you're trying to do :
Navigator.popUntil(context, (route) => route is CreatePostScreen);
Doesn't work because route is of type Route, not a widget. This leads to all the routes being popped since no Route satisfies your predicate.
What you should do is push your route with a setting, e.g. :
Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(), settings: RouteSettings(name: "/home")));
And then use that in your predicate. E.g. :
Navigator.popUntil(context, (route) => route.settings.name == "/home");
Hope this will help you. You can use popUntil method of Navigation Class.
int count = 0;
Navigator.of(context).popUntil((_) => count++ >= 2);
You would try with the below code:
onPressed: () async {int count = 0; Navigator.of(context).popUtil((_)=> count++>= 2);}
The code you would refer from is that, you would implement the logic to let the system indicate whether pop continues if it returns false it will keep popping until it the logic returns true
void popUntil(bool Function(Route<dynamic>) predicate)
If you want to pop two screens you can use cascade operator like this:
Navigator.of(context)..pop()..pop();

pop and push the same route back with different params in Futter (GET X)

I have 2 screens,
Screen one contains a list view with onPressed action on every item
screen two contains the detail of the pressed item as well as a drawer with the same list view as screen one.
What I want to do here is when the user goes to the detail screen and click on an item from the drawer the detail screen should pop and push back with new params.
Code so far,
Route
GetPage(
name: '/market-detail',
page: () => MarketDetail(),
binding: MarketDetailBinding(),
),
Binding
class MarketDetailBinding extends Bindings {
#override
void dependencies() {
Get.lazyPut(() => MarketDetailController());
}
}
Click action in screen one
onTap: () {
Get.toNamed('market-detail',
arguments: {'market': market});
},
Detail Screen Class
class MarketDetail extends GetView<MarketDetailController> {
final Market market = Get.arguments['market'];
}
Click action in detail screen sidebar
onTap: () {
Get.back();
Get.back();
Get.toNamed('market-detail',
arguments: {'market': market});
},
First Get.back() is to close the drawer, then remove the route and push the same route back again,
Expected behaviour,
MarketDetailController should be deleted from memory and placed again,
What actually happening
The controller only got delete and not getting back in memoery on drawer click action until I hot restart the app(By clicking save).
If anybody understands it, please help I am stuck here.
As I can see, you're trying to pop and push the same route with a different parameter in order to update a certain element on that route. Well, if that's the case then just let me show you a much better way.
In your MarketDetailController class you should add those:
class MarketDetailsController extends GetxController {
// A reactive variable that stores the
// instance of the market you're currently
// showing the details of.....
Rx<Market> currentMarket;
// this method will be called once a new instance
// of this controller gets created
// we will use it to initialize the controller
// with the required values
#override
void onInit() {
// some code here....
// .......
// intializing the variable with the default value
currentMarket = Market().obs;
super.onInit();
}
void updateCurrentMarket(Market market) {
// some code here if you need....
// updating the reative variable value
// this will get detected then by the Obx widgets
// and they will rebuild whatever depends on this variable
currentMarket.value = market;
}
}
Now inside your page UI, you can wrap the widget that will display the market details with the Obx widget like this:
Obx(
() {
final Market currentMarket = controller.currentMarket.value;
// now you have the market details you need
// use it and return your widget
return MyAwesomeMarketDetailsWidget();
},
)
Now for your click action, it can just be like this:
onTap: () => controller.updateCurrentMarket(myNewMarketValue)
This should be it. Also, I advise you to change GetView to GetWidget and Get.lazyPut() to Get.put()

Wait for Navigator.pop ignoring Navigator.pushReplacement

I have the following setup:
class FirstScreen {
// ...
Future<void> doSomething() async {
final bool isCool = await Navigator.of(context).pushNamed('/second-screen');
print(isCool ? 'Cool.' : 'Not cool.');
}
// ...
}
class SecondScreen {
// ...
Future<void> replace() async {
await Navigator.of(context).pushReplacementNamed('/third-screen');
}
// ...
}
class ThirdScreen {
// ...
Future<void> goBack() async {
await Navigator.of(context).pop(true);
}
// ...
}
However, this would crash, since the pushReplacement procs the await and my application won't wait until the pop is used.
How can I wait for pop 's value to be returned?
UPDATE:
The problem here is a little bit more complex than I told.
#Alok suggested to not pop the route but push it after the sequence, however, this is a very trivial version of my code.
I currently have a HomeScreen with a nested Navigator that pushes to a list of questions. Then, using Navigator.of(context, rootNavigator: true), I navigate to the examLoadingScreen, etc. (You can read about this in the comments)
If I push the HomeScreen when the exam is completed, I would lose all the navigation done in the mentioned nested Navigator.
I seriously need to pop in this scenario. I have multiple workarounds such as pop chaining but it doesn't seem very performant or convenient.
See, Zeswen, as far this documentation on pushReplacementNamed is concerned. It states that:
Replace the current route of the navigator that most tightly encloses the given context by pushing the route named routeName and then disposing the previous route once the new route has finished animating in.
Can you see that, it clearly mentions that it removes the previous route after you are done animating it.
Now, what are you trying to achieve is, or how Navigator.pop() value retrieval works, is it is mandatory to have that PrevoiusPage there when you move from one page to another
//What you're doing with pushReplacementNamed
1 -> SeconPage => ThidPage
2 -> SecondPage [Removed]
3 -> ThirdPage is trying to come to the previous page, that is SecondPage to return it's value, but SecondPage has been removed HENCE CRASHES!!
//What is needs to be done to use something like push() or pushNamed(), which used named route
1 -> SecondPage => ThirdPage
2 -> SecondPage is there in the stack
3 -> ThirdPage => SecondPage [Returns Value]
REMEMBER pop() always need the immediate precedence to accept it's value, not any page. So, if you remove the SecondPage, it will always crash.
Now, if you want to go to the page MainPage or in this case, FirstPage. Use pushAndRemoveUntil. It basically removes all the routes in the stack, and go to the page
SOLUTION: Pass the result score to the MainPage, via ResultPage. Make the MainPage accepts the Result Score too
class ThirdScreen(){
// ...
Future<void> goBack() async {
await Navigator.pushAndRemoveUntil(context,
MaterialPageRoute( builder: (context) => FirstPage(result: result),
(_) => false
);
}
}
And do your operation in your FirstPage accordingly, if you have result != 0 || result != null, and show it to the user. Let me know if that works out for you.
UPDATED ANSWER WITH A BEST POSSIBLE WORKAROUND
I have just added this answer, because, I feel like the above would be helpful in future as well.
Now, my idea is basic, and is workable according to the trivial information available for me.
THEORY: According to the theory, pop() value can be accessed by the predecessor only, immediate one.
SOLUTION
1. First Page -> Second Page
2. Second Page -> Third Page
3. Third Page -> Second Page with value
// Now following 3. step
1. Value check, if the value is true, pop immediately
2. Return the value to the first page
3. Print the value in the first page
Just follow your trivial data, and I hope you would understand the logic. After that implementation is just a cakewalk.
class FirstScreen {
Future<void> doSomething() async {
// We get the value from second page, which is technically passing
// the third page's value, and doesn't appear to us in UI
// So serving the purpose
final bool isCool = await Navigator.pushNamed(context, '/second-screen');
print(isCool ? 'Cool.' : 'Not cool.');
}
}
class SecondScreen {
Future<void> replace() async {
// No need of pushReplacementNamed, since we're are popping
// based upon our values, so it won't appear eventually
// and pass the value as well for the First Page
final bool value = await Navigator.pushNamed(context, '/third-screen');
// Now we check, whether what value we got from third page,
// If that is true, then immediately pop and return the value for first page
if(value == true){
Navigator.pop(context, value);
}
}
}
class ThirdScreen {
// async not required for performing pop()
// void is fine
void goBack() {
Navigator.pop(context, true);
}
}
Just check it. This logic will help you achieve the purpose, and it is safe and error free.

How to return data when popping multiple screens?

I know I can return data to the previous screen by using
Navigator.pop(context, 'Value');
But in my case I need to pop multiple screens by using
Navigator.popUntil(context, ModalRoute.withName('/login'));
I wonder in this case how do I pass the data back to the corresponding widget?
Thanks in advance.
you can send DATA in few ways
as a Parameter
using Shared_Preferences
using Static Variables
Only for Current Session
if you just need the DATA for Current Session you can go for Static Variables
step 1 : Create a Class and have Static Variable in it.
class Globaldata{
static String value;
}
step 2 : Initialise variable by
Globaldata.value="some_value";
step 3 : use of variable
String assigned_value = Globaldata.value;
The flutter API does not have that feature and from this https://github.com/flutter/flutter/issues/30112 discussion, that feature is not on the table yet. A walkaround was suggested though using the Page API.
However, in my opinion, it is cleaner to use the provider package https://pub.dev/packages/provider as part of your app state management to keep the data you want and make it available to any screen of interest. Follow these steps to achieve that.
Add the provider to your pubspec.yaml. Check the link above to see detailed instructions.
Create a notifier class that extends ChangeNotifier class as shown below. ChangeNotifier class is part of the flutter API.
class MyDataProvider extends ChangeNotifier {
//define your private data field(s). I'm using int here.
int _mydata;
//define a getter
int get myData => _myData;
// define a setter
set myData(newData){
_myData = newData;
notifyListeners();
}
}
Wrap your uppermost widget (or the parent of the screens where you want to pass the data) with the provider and instantiate it. I'm using main here.
void main(){
runApp(
ChangeNotifierProvider(create: (context) => MyDataProvider()),
child: MyApp(),
)
}
Assuming you have five screens: Screen1, Screen2, ..., Screen5 and you want to navigate to screen5, do some operations and return to screen1 with some data. On 1st screen, define a local variable for myData and create an instance of your myDataProvider. When a button is pressed to start the navigation, wrap up the push navigation in an asynchronous call.
//Screen1
int currentLocalData = 78;
MyDataProvider myProvider = Provider.of<MyDataProvider>(context);
onPressed: () async {
//Assign localData to myData in the provider
myProvider.myData = currentLocalData; //calls the setter define in the provider.
await Navigator.push(context, MaterialPageRoute(
builder: (context) => Screen5()
));
//Retrieve myData from the provider and assign it to currentLocalData.
//This executes after navigating back from Screen5
currentLocalData = myProvider.myData;
}
Let assume in Screen5 you retrieved the data and added 100 to it. Your aim is to return to Screen1 and make use of the new data, i.e 178. Here you will instantiate the provider again in Screen5 and assign the value 178 to myData.
//Screen5
MyDataProvider myProvider = Provider.of<MyDataProvider>(context);
myProvider.myData += 100;
//Use navigation.popUntil
Navigation.popUntil(context, ModalRoute.withName('/Screen1'));
Say you have Screen A,Screen B, Screen C. If you want to pop till Screen A and pass some data. Here is what you have to do.
1. Navigate from Screen A to Screen B
Navigator.pushNamed(context, '/screenb')
.then((value) {
//you will get return value here
});
2. To pop till Screen A.
//add thi code in Screen C
var nav = Navigator.of(context);
nav.pop('refresh');
nav.pop('refresh');