Flutter :- How manage the backstack like in Android "SingleTask"? - flutter

I am trying to manage the backstack in Flutter like in Android we manage it from launchMode (eg. SingleTask,SingleTop , Standard etc..) , For it i have tried the Routes in flutter, but did not get the success, please check the below code, which i have tried to achieve the backstack.
Widget makeRoute(
{#required BuildContext context,
#required String routeName,
Object arguments}) {
final Widget child =
_buildRoute(context: context, routeName: routeName, arguments: arguments);
return child;
}
Widget _buildRoute({
#required BuildContext context,
#required String routeName,
Object arguments,
}) {
switch (routeName) {
case '/':
return SplashScreen();
case '/A': //// NAME OF SCREEN IS A
return A();
case '/B': //// NAME OF SCREEN IS B
MyBean docs = arguments as MyBean;
return B(dataToShow: docs);
case '/C': //// NAME OF SCREEN IS C
MyBean docs = arguments as MyBean;
return C(dataToShow: docs);
case '/D': //// NAME OF SCREEN IS D
return D();
}
}
I am jumping the screens from A->B->C->D are as follow,
From A->B , I navigate it like below.
Navigator.of(context).pushNamed('/B');
From B->C , I navigate it like below.
Navigator.of(context).pushNamed('/C', arguments: myList[index]);
And finally, From C->D , I navigate it like below.
Navigator.of(context).pushNamed('/D');
As from the above code, I successfully navigated to the A------>D screens and also carrying data successfully.
But my main concern is that i want to navigate from the D->A or D->B screen using the backstack without opening another screen, So i have tried the below code but it is not working , please check the below code.
From D->A, I have tried like
Navigator.popUntil(context, ModalRoute.withName('/A'));
And even tried in this manner like below.
Navigator.of(context)
.popUntil(ModalRoute.withName("/A"));
I even this way to manage the flow like below
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator.popUntil(context, ModalRoute.withName('/A'));
});
But both are not working properly
Please check my main() class
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: '',
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.grey,
primaryColor: ColorConst.PRIMARY_COLOR,
accentColor: ColorConst.ACCENT_COLOR,
primaryColorBrightness: Brightness.light,
accentColorBrightness: Brightness.light,
),
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) => makeRoute(
context: context,
routeName: settings.name,
arguments: settings.arguments,
),
maintainState: true,
fullscreenDialog: false,
);
}
)
);
}
And getting the following exception from above code like below.
═ (2) Exception caught by widgets library ═══════════════════════════════════════════════════
'package:flutter/src/widgets/navigator.dart': Failed assertion: line 2330 pos 12: '!_debugLocked': is not true.
The relevant error-causing widget was:
MaterialApp **file:///Users/apple/Documents/BitBucket%20Projects/loyalityapp_android/lib/main.dart:10:7**
════════════════════════════════════════════════════════════════════════════════════════════════════

If you are using the following code to pop until the route "/A" using Navigator.popUntil method then you should have to set your opening screen with the help of "initialRoute" property in your MaterialApp Widget instead of "home" property.
Navigator.popUntil(context, ModalRoute.withName('/A'));
Because, here you are navigating to the route "A" with the help of "push navigator routes" and want to pop using "named routes"
Also, you can set it with "/" route in your routes property as Follow,
initialRoute : "A",
OR
routes:{
"/": (context)=> A(),
}
Every thing other is fine in your code.

I have solved the problem by using the MaterialPageRoute and referred this link to get the solution Click here
From A->B->C->D screen navigation, I have used this approach
From A->B screen
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(name: "B"),
builder: (context) => SCREEN_B(),
),
);
And from B->C, I haved used this along with parameters like below
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(name: "C"),
builder: (context) => SCREEN_C(dataToShow:myList[index]),
),
);
From C->D screen
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(name: "D"),
builder: (context) => SCREEN_D(),
),
);
And main logic is here navigations from D->A or D->B, from below liens of code
Navigator.popUntil(context, ModalRoute.withName("A")); //YOU CAN USE "B" TO NAVIGATE
At the last one more thing which I need to add here is that when I want to perform any action like refresh activity and anything else from screen D->A
Then inside the A screen, we need to use then method like below
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(
name: "B"),
builder: (context) =>
B(dataToShow: MYLIST[index]),
),
).then((value) {
//// THIS METHOD IS ENVOKE WHEN SCREEN COME FROM D->A OR B->A, YOU CAN PERFROM CAN TASK HERE
});

Related

how do i use provider in this situation

I want to create a change app theme mode and I saw a way of creating it with Provider but I'm new to Provider. For Example, I want to add some codes like this
(the highlighted code)
in my main which consists of many routes
You want to change the theme of the app, then you need to move provider up so it can cover the widget (App in this case) state,
You could do something like this in your main method :
runApp(ChangeNotifierProvider(
create: (context) => ThemeProvider(),
child:MyApp()
);
now in the case of children you could simply call provider in the build method like this
Widget build(){
var themeProvider = Provider.of<ThemeProvider>(context);
}
or you could use the consumer widget
Consumer<ThemeProvider>(
builder: (context, provider, child) {
//return something
}
)
I suggest you to move your ChangeNotifierProvider to your runApp() method
runApp(
ChangeNotifierProvider<ThemeProvider>(
create: (_) => ThemeProvider(),
child: MyApp(),
),
),
Where your MyApp() is just all of your app extracted to its own widget.
Then you can actually easily access it as you wish with a Consumer widget on your build method.
return Consumer<ThemeProvider>(
builder: (BuildContext context, ThemeProvider provider, _) {
return MaterialApp(
theme: provider.myTheme,
...
);
}
)

Advantage from static routing to dynamic routing on Flutter

I can see that MaterialApp app can receive routes.
1. Static routing
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/page1': (context) => Page1(title: "Main page"),
...
And show them from the widgets like:
myKey.currentState.pushNamed("/page1");
There are other parameters like onGenerateRoute and initialRoute which confuse me more.
2. Dynamic Pages
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
Question
Im wondering what is the implication of this parameters and letting this "responsibility" to the MaterialApp, and why we should do it, maybe something related to memory management or how the Widget lifecycle works, or what?
What are the differences between 1. and 2.?
The answer lies more in your architecture than anything.
1. Static Routing is the better of the two in terms of managing a projects complexity. Routes are clearly defined for multiple developers to understand, and the navigation code is much easier, Navigator.of(context).pushNamed('your-route'); vs
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
2. Dynamic Pages is commonly in tutorials and such to reduce boilerplate code. It is merely a shortcut to navigate. The downside of this is it becomes hard to manage routes, and so should be limited to short tutorials.
3. Generated Routes There is a third option though, that in my opinion is the best of the two, and that is a Generated Routes. This is the cleanest and easiest to mantain structure. There is a great tutorial here about it. Here is the rundown:
Declare Routes:
class RoutePaths {
static const Start = '/';
static const SecondScreen = 'second'
}
Declare your router:
class Router {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case RoutePaths.Start:
return MaterialPageRoute(builder: (_) => YourFirstScreenWidget());
case RoutePaths.SecondScreen:
// you can do things like pass arguments to screens
final event = settings.arguments as Event;
return MaterialPageRoute(
builder: (_) => YourSecondScreenWidget(event: event));
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}'),
),
));
}
}
}
Declare it in main.dart
initialRoute: RoutePaths.Start,
onGenerateRoute: Router.generateRoute,
Navigate
// arguments: event is an optional parameter to send to secondScreen
Navigator.of(context).pushNamed(RoutePaths.SecondScreen, arguments: event);

How to navigate from one widget to another like fragments in android(flutter)?

I have such a structure of my UI:
TabBarView(children:[_Fragment1,_Fragment2...])
_Fragment1 shows files of my user and I need to implement navigation between folders with backstack. I have listview in my _Fragment1 and when item clicked and item is a folder folder's files should be shown(only files fragment is changed). I tried make it like this but it fails:
onTap: () => (_filesList[index].isFolder)
? Navigator.push(context, MaterialPageRoute(builder: (_) => _FilesListView(_filesList[index].children)))
: null,
Is there a solution as easy as fragments in android? Maybe there are some packages that can help to make something like this.
I found the solution here https://stackoverflow.com/a/52247870/10260006 and here Use nested Navigator with WillPopScope in Flutter but what I want to add that I needed to specify only root named route and ongenerateroute for it like this
WillPopScope(
onWillPop: () async {
navigatorKey.currentState.maybePop();
return false;
},
child: Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (settings) {
return (settings.name == '/')
? MaterialPageRoute(
builder: (context) => _FilesListView(filesList.where((file) => file.isShared == false).toList()),
settings: settings)
: null;
},
),
),
and further you can use just Navigator.of(context).push

flutter call a function after moving back to screen from another screen

How to call a function in flutter ,after moving back to a screen from another screen?
For Example:
Screen 1
function1(){
}
Screen2
function2(){
//Go back to screen 1 and then call function1()
}
It's simple.
Navigator.push(context, MaterialPageRoute(builder: (context)=> SecondScreen())).then((_){
// This method gets callback after your SecondScreen is popped from the stack or finished.
function1();
});
You should also refer the Flutter Navigation & Routing.
Here is the solution!
Second Screen
Navigator.pop(context, [1]);
or, if you don't want to send back any data, you can only call
Navigator.pop(context);
First Screen
Navigator.push( context, MaterialPageRoute( builder: (context) => SecondScreen(), ), ).then((value) { //do something after resuming screen
});
Imho the solutions provided here aren't valid solutions.
If you use a routes Future it may be called multiple times and will even be called in case of a forward navigation.
Instead use a NavigatorObserver:
class AppNavigationObserver extends NavigatorObserver {
#override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
print("AppNavigationObserver: ${route.settings.name}");
print("AppNavigationObserver: ${previousRoute?.settings.name}");
}
}
You can then use it for example like this:
MaterialApp(
navigatorObservers: [
AppNavigationObserver()
],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder(
maintainState: true,
settings: settings,
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (context, animation, secondaryAnimation) {
// Your route builder according to settings
},
);
},
)
The important part is passing onGenerateRoute's settings paramter to the PageRouteBuilder settings. Otherwise settings.arguments and settings.name will be null in the didPop handler.

How to write the syntax for onTap() event to direct to other/new pages dynamically in a dynamic ListView in flutter?

I have created a code in which multiple ListTile widgets are created dynamically based on the list of items i store. I want to add onTap() functionality to it so that it redirects to the respective pages i have created.
Here i need how to redirect to a particular page based on the onTap() which is contained in a list view.
from the above code i don't know how to direct to a respective page based on the onTap() event.
I can suggest the following:
1. Create a class to handle only the routes of your application, as you can see below:
class Router {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => HomeScreen());
case 'screen_1':
return MaterialPageRoute(builder: (_) => Screen1());
case 'screen_x':
return MaterialPageRoute(builder: (_) => ScreenX());
default:
return MaterialPageRoute(builder: (_) {
return Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}'),
),
);
});
}}}
Use the onGenerateRoute property of the MaterialApp widget, to tell you to call that generator when the app tries to navigate to a named path, also you can use the initialRoute property if you want:
MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: '/',
onGenerateRoute: Router.generateRoute,
)
In the dynamic list you are generating, use the following:
Navigator.pushNamed (context, "screen_x");
where "screen_x" can be a property of the dynamic list object.