Flutter remove all routes - dart

I want to develop a logout button that will send me to the log in route and remove all other routes from the Navigator. The documentation doesn't seem to explain how to make a RoutePredicate or have any sort of removeAll function.

I was able to accomplish this with the following code:
Navigator.of(context)
.pushNamedAndRemoveUntil('/login', (Route<dynamic> route) => false);
The secret here is using a RoutePredicate that always returns false (Route<dynamic> route) => false. In this situation it removes all of the routes except for the new /login route I pushed.

i can done with the following code snippet :
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) =>
LoginScreen()), (Route<dynamic> route) => false);
if you want to remove all the route below the pushed route, RoutePredicate always return false, e.g (Route route) => false.

Another alternative is popUntil()
Navigator.of(context).popUntil(ModalRoute.withName('/root'));
This will pop all routes off until you are back at the named route.

Another solution is to use pushAndRemoveUntil(). To remove all other routes use ModalRoute.withName('/')
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (BuildContext context) => Login()),
ModalRoute.withName('/')
);
Reference: https://api.flutter.dev/flutter/widgets/NavigatorState/pushAndRemoveUntil.html

In case you want to go back to the particular screen and you don't use named router can use the next approach
Example:
Navigator.pushAndRemoveUntil(context,
MaterialPageRoute(builder: (BuildContext context) => SingleShowPage()),
(Route<dynamic> route) => route is HomePage
);
With route is HomePage you check the name of your widget.

If you are using namedRoutes, you can do this by simply :
Navigator.pushNamedAndRemoveUntil(context, "/login", (Route<dynamic> route) => false);
Where "/login" is the route you want to push on the route stack.
Note That :
This statement removes all the routes in the stack and makes the pushed one the root.

use popUntil like following
Navigator.popUntil(context, (route) => route.isFirst);

I don't know why no one mentioned the solution using SchedularBindingInstance, A little late to the party though, I think this would be the right way to do it originally answered here
SchedulerBinding.instance.addPostFrameCallback((_) async {
Navigator.of(context).pushNamedAndRemoveUntil(
'/login',
(Route<dynamic> route) => false);
});
The above code removes all the routes and naviagtes to '/login' this also make sures that all the frames are rendered before navigating to new route by scheduling a callback

Not sure if I'm doing this right
but this suits my use-case of popping until by root widget
void popUntilRoot({Object result}) {
if (Navigator.of(context).canPop()) {
pop();
popUntilRoot();
}
}

In my case this solution works:
Navigator.pushNamedAndRemoveUntil(" The Route you want to go to " , (Route route) => false);

In my case I had this painting
Page 1 (Main) -> Page 2 -> Page 3 -> Page 4.
When I had to go to Page 4, the Page 2 and Page 3 going back did not have to appear, but I had to go to Page 1 again. At this point going to Page 4 I did:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
Workout()),
(Route<dynamic> route) => route.isFirst);
The instructions are: go to page 4 (Workout) and remove all previous pages up to 1, that is (Main).
In your case that can be to switch from anything to a Login, then:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
Login()),
(Route<dynamic> route) => false);
That is, go to Login and remove all previous pages, because there is a false.

This is working for me. Actually, I was working with bloc but my issue was login screen bloc. It was not updating after logout. It was holding the previous model data. Even, I entered the wrong entry It was going to Home Screen.
Step 1:
Navigator.of(context).pushNamedAndRemoveUntil(
UIData.initialRoute, (Route<dynamic> route) => false);
where,
UIData.initialRoute = "/" or "/login"
Step 2:
It's working to refresh the screen. If you are working with Bloc then It will very helpful.
runApp(MyApp());
where,
MyApp() is the root class.
Root class (i.e. MyApp) code
class MyApp extends StatelessWidget {
final materialApp = Provider(
child: MaterialApp(
title: UIData.appName,
theme: ThemeData(accentColor: UIColor().getAppbarColor(),
fontFamily: UIData.quickFont,
),
debugShowCheckedModeBanner: false,
//home: SplashScreen(),
initialRoute: UIData.initialRoute,
routes: {
UIData.initialRoute: (context) => SplashScreen(),
UIData.loginRoute: (context) => LoginScreen(),
UIData.homeRoute: (context) => HomeScreen(),
},
onUnknownRoute: (RouteSettings rs) => new MaterialPageRoute(
builder: (context) => new NotFoundPage(
appTitle: UIData.coming_soon,
icon: FontAwesomeIcons.solidSmile,
title: UIData.coming_soon,
message: "Under Development",
iconColor: Colors.green,
)
)));
#override
Widget build(BuildContext context) {
return materialApp;
}
}
void main() => runApp(MyApp());
Here is My Logout method,
void logout() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
preferences.clear();
// TODO: we can use UIData.loginRoute instead of UIData.initialRoute
Navigator.of(context).pushNamedAndRemoveUntil(
UIData.initialRoute, (Route<dynamic> route) => false);
//TODO: It's working as refresh the screen
runApp(MyApp());
}

Use this, it worked perfectly for me:
Navigator.pushNamedAndRemoveUntil(
context, '/loginscreen', (Route<dynamic> route) => false);
Make sure you add the last line, parameter, and you're good to go.

First see chrislondon answer, and then know that you can also do this, if you do not have access to the (context).
navigatorKey.currentState.pushNamedAndRemoveUntil('/login', (Route<dynamic> route) => false);

to clear route -
onTap: () {
//todo to clear route -
Navigator.of(context).pop();
Navigator.push(context, MaterialPageRoute(builder: (context) => UpdateEmployeeUpdateDateActivity(_token),));
widget.listener.onEmployeeDateClick(_day,_month, _year);
}

Related

Generic type check - keep type and not dynamic

I have these classes
class CustomPopupAction<T> extends CustomAction {
final Icon icon;
final List<CustomPopupActionItem<T>> actions;
final void Function(T) onActionSelected;
CustomPopupAction({
required this.icon,
required this.actions,
required this.onActionSelected,
});
}
class CustomPopupActionItem<T> {
final T value;
final Widget Function(T) itemBuilder;
CustomPopupActionItem({
required this.value,
required this.itemBuilder,
});
}
and I am trying to create overflow menu which will work like this:
if the button is visible, I will create PopupMenuButton
if the button is overflown, I will create ListTile which will open dialog
it can hold multiple different types like CustomAction, CustomPopupAction<Locale>, CustomPopupAction<String>...
I am building that row like this
if (a is CustomPopupAction) {
return PopupMenuButton(
icon: a.icon,
onSelected: (i) => a.onActionSelected(i),
itemBuilder: (context) {
return a.actions.map((i) => PopupMenuItem(
value: i.value,
child: i.itemBuilder(i.value),
)).toList();
},
);
} else {
return IconButton(...);
}
and finally my main code:
...
return OverflowMenu(
actions: [
CustomPopupAction<Locale>(
icon: Icon(Icons.translate),
actions: [
CustomPopupActionItem<Locale>(
value: Locale('en'),
itemBuilder: (l) => ListTile(title: Text(l.toString()),
),
],
onActionSelected: (l) => print(l),
],
);
But this doesn't work for me, I am getting an exception Expected a value of type '(dynamic) => Widget', but got one of type '(Locale) => ListTile'.
I know it's because if (a is CustomPopupAction) is actually getting CustomPopupAction<dynamic>.
can I somehow convince Dart that a nas not dynamic type and that it should work with it's real type?
if not, why am I getting that exception? Locale can be assigned to dynamic variable and ListTile is clearly a Widget.
can I do this without going through dynamics at all?

Flutter: give Map to Routes option from other file

I'm a bit at loss here.
return MaterialApp(
title: 'App Title',
theme: ThemeData(brightness: Brightness.dark),
initialRoute: '/',
routes: SOMETHING_HERE,
);
I want to push SOMETHING_HERE from a different file, but I can't seem to push a correct value there.
Other file (attempt):
import '../screens/home.dart';
import '../screens/charts.dart';
class Routes {
factory Routes(context) {
Map<String, Widget Function(BuildContext)> _routes;
_routes = {
'/': (context) => ScreenHome(),
'/charts': (context) => ScreenCharts(),
};
return _routes;
}
}
This doesn't work cause it says:
The argument type 'Routes' can't be assigned to the parameter type 'Map<String, (BuildContext) → Widget>'
OF course I can just pass a Map to this argument but I want to define my routes in a separate file.
Any suggestions on how to accomplish this?
I just had the same problem and found the solution.
You don't need to create a class, just create a var that equals your routes Map
main.dart:
import 'package:flutter/material.dart';
import './custom_routes.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(brightness: Brightness.dark),
initialRoute: '/',
routes: customRoutes,
);
}
}
custom_routes.dart:
import 'package:flutter/material.dart';
import '../screens/home.dart';
import '../screens/charts.dart';
var customRoutes = <String, WidgetBuilder>{
'/': (context) => ScreenHome(),
'/charts': (context) => ScreenCharts(),
};
**
There is another way you can try if you wish
**
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: LoginScreen.id,
routes: route,
);
},
}
Create your route dart file. No need to create a class
var route = <String, WidgetBuilder>{
LoginScreen.id: (_) => const LoginScreen(),
// call the classes
Dashboard.id: (_) => const Dashboard(),
// with value
Dashboard.id: (_) => const Dashboard(value: ''),
};
If you don't use call by id. You can do that as well. Also you can pass values shown example
Just create any function with return of Map<String, WidgetBuilder>, here i will show how to do that with pass data to your routes class:
1- Create new file routes.dart, this full code (i used my custom variables like serverToken, notifierThemeMode) to fully explain the process:
import 'package:rxdart/rxdart.dart';
import 'package:flutter/material.dart';
import 'package:path/to/home_screen.dart';
import 'package:path/to/login_screen.dart';
class AppRoutes{
// get initial route
static getInitialRoute({String? serverToken}){
return serverToken == null
? LoginScreen.routeName
: HomeScreen.routeName;
}
// get all app routes
static Map<String, WidgetBuilder> getRoutes({
required BehaviorSubject<ThemeMode?> notifierThemeMode,
required BehaviorSubject<Locale?> notifierLocale,
}){
return {
HomeScreen.routeName: (BuildContext context) => HomeScreen(
notifierThemeMode: notifierThemeMode,
),
LoginScreen.routeName: (BuildContext context) => LoginScreen(
notifierLocale: notifierLocale,
),
}
}
2- In MaterialApp widget call the previous functions:
MaterialApp(
...
routes: AppRoutes.getRoutes(
notifierThemeMode: _notifierThemeMode,
notifierLocale: _notifierLocale
),
initialRoute: AppRoutes.getInitialRoute(
serverToken: _appServerToken
),
);

Multiuser application using flutter

I am working on one application which has multiple users. I have a list of resources, this list of resources are like a list of chocolates (only one and unique). Now, I am showing this chocolates on the home screen of all active users. Now, user can click on chocolate and it will be given to them. But, when this happens i want to refresh all logged in users so to ensure that no two users are having same chocolate.
I am using database trigger to monitor the change in DB. I am able to do that but my concern is how to refresh listView.
My Algorithm is as below:
1) Monitor changes in Database.
2) Get Fresh set of data.
3) Update View
I tried creating syncDatabaseFunction as below:
Future syncDatabaseFunction() async {
CollectionReference reference = Firestore.instance.collection('Chocolates');
reference.snapshots().listen((querySnapshot){
querySnapshot.documentChanges.forEach((change){
print("Changed Chocolate");
BackendOperations.getAllChocolates().then((value){
var chocolateTemp = (value as List<ChocolateModel>)
.where((element) => (element.chocolateColor == "Brown"))
.toList();
print("Count is ");
return chocolateTemp;
});
});
});
}
For listview I am using futureBuilder.
I think that if you use StreamBuilder you will solve the problem.
When a user remove or add a new Comment it show for all users.
StreamBuilder was made to do this, be a Observer of the Stream.
This is my code:
Widget getListComment() {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('comments')
.where('post', isEqualTo: postRef)
.orderBy('createdAt', descending: true)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Column(
children: <Widget>[
CircularProgressIndicator(),
],
);
default:
return new ListView(
children:
snapshot.data.documents.map((DocumentSnapshot document) {
return CommentItem(
key: Key(document.documentID),
comment: Comment.fromDoc(document),
myUser: widget.myUser,
);
}).toList(),
);
}
},
);
}
I receive comments from Firebase and show in a ListView, I think that is like your chocolates.

How to go back to first page of app in flutter app?

So I've created an app that has like 4 pages and then a webview so whenever a user logout from webview I push the starting screen.
I want my app to go to the first page. I've tried:
Navigator.of(context).pushNamed('/welcomeScreen');
Navigator.popUntil(context,ModalRoute.withName('/welcomeScreen'));
but no luck. I think this may be because of webview.
This is my routes.
final routes = {
'/login': (BuildContext context) => new LoginScreen(),
'/home': (BuildContext context) => new HomeScreen(),
'/welcomeScreen':(BuildContext context) => new WelcomeScreen(),
'/email': (BuildContext context) => new EmailScreen(),
'/webview': (BuildContext context) => new WebviewScreen()
};
This function will set the page root page so it should work for logout case.
Navigator.pushAndRemoveUnitl is what are you looking for I guess.
void makeRoutePage({BuildContext context, Widget pageRef}) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => pageRef),
(Route<dynamic> route) => false);
}
how to use it :
makeRoutePage(context: context, pageRef: YourFirstPage());
You want to go back through all screens until the first screen in Stack, so best statement you can use is:
Navigator.popUntil(context, (Route<dynamic> predicate) => predicate.isFirst);
Simply use this code in any button action, it will redirect U to MainScreen/Homepage from any other page
Navigator.of(context).pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false);
An alternative solution can be Navigator.popUntil(context, ModalRoute.withName('/')); which will pop all screens from the stack except the initial screen.

Flutter: Observable.combineLatest2 is not streaming when page is loaded through navigation

I am creating a flutter app with blocs.
I followed the code available in Flutter login with blocs
It works as expected,
if my app has no routes defined
class App extends StatelessWidget {
Widget build(BuildContext context) {
return Provider(
child: MaterialApp(
title: 'Log Me In!',
home: Scaffold(
body: LoginScreen(),
),
),
);
}
}
but when I change my app to use routes
class App extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Log Me In!',
routes: {
'/':(context) => Provider(
child: Scaffold(
body: LoginScreen(),
),
)
},
);
}
}
bloc code
class Bloc extends Object with Validators {
final _email = BehaviorSubject<String>();
final _password = BehaviorSubject<String>();
// retrieve data from stream
Stream<String> get email => _email.stream.transform(validateEmail);
Stream<String> get password => _password.stream.transform(validatePassword);
Stream<bool> get submitValid => Observable.combineLatest2(email, password, (e, p) => true);
// add data to stream
Function(String) get changeEmail => _email.sink.add;
Function(String) get changePassword => _password.sink.add;
submit() {
final validEmail = _email.value;
final validPassword = _password.value;
print('$validEmail and $validPassword');
}
dispose() {
_email.close();
_password.close();
}
}
Observable.combileLatest2 is not streaming the data (but it streams error though).
Using Rxdart version 0.19.0 and
Flutter 1.0.0 • channel beta •https://github.com/flutter/flutter.git
Framework • revision 5391447fae (6 days ago) • 2018-11-29 19:41:26-0800
Engine • revision 7375a0f414Tools • Dart 2.1.0 (build 2.1.0-dev.9.4 f9ebf21297)
Am I doing something wrong here?
thanks in advance
After lot of trial, I found that when I use routes for the navigation, flutter will build the page multiple times and thats the expected behavior refer here for detailed answer
So when it builds the page multiple times, it was creating multiple Observables on the bloc as it was creating new instance of Bloc every time it creates the Page route.
So when I modify the code
class App extends StatelessWidget {
final login = Provider(
child: Scaffold(
body: LoginScreen(),
),
);
Widget build(BuildContext context) {
return MaterialApp(
title: 'Log Me In!',
routes: {
'/':(context) => login,
},
);
}
}
it worked perfectly.
The other way is to achieve is to create a stateful widget and do the initialization in the init method.

Resources