How to pop the navigator two pages back? - flutter

I need to leave the current screen and navigate back to the second to last screen.
void leaveStatefulWidget3(){
Navigator.pop(context);
}
// StatefulWidget 2...
// StatefulWidget 3 {....}
// the current one (where the navigator is now)
My app has navigated from the first screen to 2, and then to 3.
I need to leave or Navigator.pop(context) the StatefulWidget 3 to stateFull 1.
NOTE : I have complete access to my leaveStatefulWidget3() method from StatefulWidget 3, but don't know how to specify the route to use Navigator.pop(context) in leaveStatefulWidget3().
When I use Navigator.pop(context) directly from the first widget it leaves stateFull 1.

just use Navigator.pop(context) twice

You could easily use 'popUntil' with 'pushNamed'
Add the route settings with route names when pushing,
like this.
Navigator.pushNamed(context, < StatefulWidget name >);
Then, at the recent widget ( in your case, its stateful_widget 3 )
Navigator.popUntil(context, ModalRoute.withName("/"));
*Here you can use widget name or '/'
*'/' will bring you back to the startup widget
Or , if this is always from certain widget to very first one
You could directly use
Navigator.of(context).popUntil((route) => route.isFirst);

Related

Is there a way to navigate to an specific child or row in flutter?

Is there a way to navigate from one dart "page" to a specific point in another? This will get me to a given page
Navigator.push(
context,
MaterialPageRoute(builder: (context) => WK3()),
);
But I want to navigate to a specific child or row within that page (which are unfortunately fairly long, and would otherwise require a lot of scrolling).
I am used to working with html, where you just have to indicate a position within a page using a hash tag:
#here
That should be possible to do in Flutter/Dart, right?
This is not possible by just using the flutter Navigator. What I would do to tackle that issue is that I would pass an argument which contains the scroll position to the Navigator for example:
Navigator.pushNamed(
context,
'/wk3',
arguments: {'scrollTo': elementId}, // or any other logic like half of the screen or so
);
To read more about Navigator and arguments you can check out the official documentation here. You can also do that for none named routes obviously.
Inside your target widget you could then do the following approach.
Take the argument and parse it to whatever you need.
Depending on your page and your scroll behavior you could use the initState to directly scroll to your desired location. What happens next is a bit dependend on your concrete implementation or where you want to scroll. In certain situations it might be more useful to add a postFrameCallBack for your scrolling instead of doing it in the initState. I'll add it for educational reasons in the snippet below.
Assuming we have a ScrollController of a ListView for example the widget we navigated to knows where we want it to scroll to due to our passed argument. If you use for instance a position value here and we have the ScrollController to do something like this:
controller.position.animateTo(
widget.args.scrollTo, //make sure it has the correct type
duration: const Duration(seconds: 1),
curve: Curves.easeInOut,
);
There are also ways you could scroll to a certain element in a list or a column (like for example the 100th element). Check this question for more information. You can find a slight implentation with a scroll controller below:
class ScreenArguments {
final String scrollTo;
ScreenArguments(this.scrollTo);
}
class Screen extends StatefulWidget {
final ScreenArguments args;
Screen(this.args, {Key key}) : super(key: key);
#override
ScreenState createState() => ScreenState();
}
class ScreenState extends State<Screen> {
#override
void initState() {
scrollMeTo = widget.args.scrollTo;
scrollController = ScrollController();
WidgetsBinding.instance
.addPostFrameCallback((_) => scrollTo(context)); // this is probably safer than doing scrollTo(context) directly in your initState
enter code here
// if you do not use addPostFrameCallback you can call scrollTo(context) directly.
//scrollTo could use scrollControler.animateTo() etc.
}
I dont have ScrollController / ListView implementation
If thats not the case and you do not have a ScrollController and you want just to scroll to any element on your widget things get a little bit more complicated. In that case I'd recommened you to use flutters Scrollable.ensureVisible. Taken from the documentation it does the following:
Scrolls the scrollables that enclose the given context so as to make
the given context visible.
Lets assume you have Column inside a SingleChildScrollView to have a foundation for your scrolling behavior. You would then define a GlobalKey for each section of your widget you would like to scroll to. This key would be the identifier which we pass in as an argument. Assuming we have a GlobalKey in the widget which is called second we could do the following:
Scrollable.ensureVisible(
GlobalObjectKey(widget.args.scrollTo).currentContext, //this would reference second
alignment: 0.5, //
duration: Duration(seconds: 2),
curve: Curves.easeInOut);
You can read more about Scrollable.ensureVisible here.
What approach to take is dependended on your needs and on your implementation.

Pop a screen in between in Flutter

I pushed three screens: ScreenOne > ScreenTwo(1) > ScreenTwo(2)
I'm at the second instance of ScreenTwo now, but I want to remove the first instance of ScreenTwo from the stack, so it should be ScreenOne > ScreenTwo(2).
When launching ScreenTwo(2) I know I shouldn't remove ScreenTwo(1) from the stack yet, so I can't just call Navigator.replace(). I really need to have ScreenOne > ScreenTwo(1) > ScreenTwo(2) for some time, and then remove the first instance of ScreenTwo(1).
How I can handle it? Navigator.pop() and similars only take into account the screen or screens on top of the stack.
If someone needs more context, this is for a phone app. Not an app for phones, but an app that mimics the behavior of a phone. So in reality, we have HomeScreen > CallScreen(Caller1) > CallScreen(Caller2). As the app can handle different calls at a time the first approach has been to map every call to a CallScreen and let every screen handle their own call events, so the first call can finish while the user is talking in the second one.
As discussed in this post:
How can I pop to specific screen in flutter
I quote:
If you didn't define any route in the MaterialApp then you need to define at the time of push.
Navigator.of(context).push(
MaterialPageRoute(builder: (_) {
return SecondPage();
},
settings: RouteSettings(name: 'SecondPage',),
));
You need to define the same route name
Navigator.of(context).popUntil((route){
return route.settings.name == 'SecondPage';
})
;
Or as an alternative if you did define the routes you can use that:
pushNamedAndRemoveUntil(
'/BottomNavigation', (Route<dynamic> route) => false);
In this case it is most suitable that you go from screen1 > Screen2 > replace previous with Screen3
Use this navigation from screen 2 to screen 3.
Navigator.pushReplacementNamed(context,"Third Screen");

Flutter ListView resetting to top of list

I'm working on an app that gets the user to take a list of measurements. I use ListView to display a list of measurements. When the the user clicks on a list item it takes them to a new page, they enter the measurement, hit save and then in the save method I do Navigator.pop(context) back to the list. It all works but there is a usability problem.
If you tap a list tile and then use the app bar to go back it returns to the same scroll position in the ListView. If you enter some data then hit Save it returns to the top of the list. Even though I'm using Navigator.pop(context) in the save method. You can imagine returning to the top each time is pretty painful when the list of requirement measurements is quite long.
I guess is maybe its something to do with the fact that in the Save method I also update the model with which the list is built on so its kind of no longer the same list??
EDIT
I'm still not getting there and now I have an issue where the itemScrollController is not attached when I want to call it. Some code will hopefully help:
class ListContents extends StatefulWidget {
ListContents({
Key key,
}) : super(key: key);
#override
_ListContentsState createState() => _ListContentsState();
}
class _ListContentsState extends State<ListContents> {
ItemScrollController itemScrollController;
#override
void initState() {
super.initState();
itemScrollController = ItemScrollController();
}
I set up the scroll controller in init state. I set up a button to test this. I can run this when the page loads:
void jump(jumpIndex) {
itemScrollController.jumpTo(index: jumpIndex);
}
When I click that button it will jump to what ever index is passed.
I need to do this jumpTo when popping back from the previous page. I have this code in the list tiles. This loads an input page. - I was hoping to run the jumpTo method after it's popped.
onTapped: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
ChangeNotifierProvider<MeasureInstance>.value(
value: _instance,
child: InputOne(
index: index,
),
),
),
);
await _database.setInstance(_instance);
print(itemScrollController.isAttached);
itemScrollController.jumpTo(index: index);
},
When I pop back to the list page I see 'false' printed in the console and then an error that _jumpTo was called on null

Function from previous screen still calling when pushed to new Screen in Flutter

I am new to flutter. I have two screens. My first screen is Stateful, it consists of a list view, which is developed using FutureBuilder, when I select an item, I am pushing the app to a new screen which is also of Stateful type.
When I am moving to the new screen, the functions from previous screen are still calling and I do not know why it is happening.
This is what I have done:
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuizDetail(
quizID: featuredContests.id,
),
),
);
},
child: Card(...))// InkWell Closed
Next Screen
class QuizDetail extends StatefulWidget {
final int quizID;
// In the constructor, require a QuizID.
QuizDetail({Key key, #required this.quizID}) : super(key: key);
#override
_QuizDetailState createState() => _QuizDetailState();
}
class _QuizDetailState extends State<QuizDetail> {
#override
void initState() {
super.initState();
getQuizDetail(quizID: widget.quizID);
}
It calls this function, but then also it calls the function from the previous screen which is used to fetch data from 'A' Network and the initState consists of a function which is used to fetch data from API 'B', but the data is not received from 'B', but comes from 'A' and entire process is dismissed. Can anyone help?
If you are not going to go back to the first screen after pushing the new screen, try using Navigator.pushReplacement() instead of Navigator.push().
Navigator.push() only pushes the new screen above the current one. It does not discard the screen underneath and hence if you're rebuilding the widget tree at some point in your code, all the screens in the call stack will get rebuilt.
Navigator.pushReplacement() pops the current topmost screen and pushes the new one in its place.
Also, if you plan on going back to the first screen, you can mark the onTap method as async and use await to force your program to wait till the QuizDetail screen is popped using Navigator.pop().
More info about navigation in flutter can be found here.
Example -
onTap: () async{
await Navigator.push(
//Rest of the code is same.
);
}

Something like conditional routing in Flutter?

Consider I have 3 screens namely screen 1, screen 2, screen 3 and screen 4.
I want to achieve the following.
Screen 3 is opened by Screen 1 -> BackButton -> Screen 2
Screen 3 is opened by Screen 2 -> BackButton -> Screen 3
Screen 3 is opened by Screen 4 -> BackButton -> Screen 1
Moreover, iOS automatically sets a swipe back option. I want to overwrite it that a swipe back in iOS does the same as described above.
Is there something like conditional routing in Flutter which helps me to adjust the BackButton-behaviour in accordance to 'from which Screen was my current Screen opened (navigator.push)'?
Wrap your widget tree in a WillPopScope() widget. This widget has an onWillPop property that you can override to whatever you want - in this case, depending on the screen you're on you'll probably want to override it to
onWillPop: () => Navigator.pushReplacement(<correctScreenWidget>)
This should catch any attempts to go back and instead do whatever you override it to. Be sparing with it, overriding default back button behaviour can make for a weird user experience if done poorly.
As for the conditional part of it, unfortunately it's a bit tricky as Navigator._history is private, so we can't just check the previous route that way. Best bet is to set up a NavigatorObserver to keep track of previous routes, and set the name in the RouteSettings of each of your routes to keep track.
Step one is to create an observer and provide it to your Navigator, something like this:
class PreviousRouteObserver extends NavigatorObserver {
Route _previousRoute;
Route get previousRoute => _previousRoute;
String get previousRouteName => _previousRoute.settings.name;
#override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
_previousRoute = previousRoute;
}
#override
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
_previousRoute = oldRoute;
}
}
class MyApp extends StatelessWidget {
final PreviousRouteObserver observer = PreviousRouteObserver();
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Flutter Demo Home Page', observer: observer),
navigatorObservers: [
observer,
],
);
}
}
Note that MyHomePage above needs to accept the observer as an argument so you can access it. Alternatively you could set up an InheritedWidget or something to maintain access to it, but this answer is getting a little long already so I'll leave that for a later question.
Then, when providing Routes to your Navigator, ensure you've got a name in the RouteSettings:
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => NextScreen(observer: widget.observer),
settings: RouteSettings(name: "nextScreen"),
),
);
Finally, do conditional routing based on the current value of widget.observer.previousRouteName in any widget that has access to it. This is just a simple matter of a switch or something in your onWillPop, so I'll leave that to you.
Kind of unfortunate that it's so roundabout, but it looks like this might be your best option at the moment. Hope it helps!