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.
Related
I'm using flutter webview to present the payment url in my app using the following class:
class YourWebView extends StatelessWidget {
String url;
bool isFinshed = false;
YourWebView(this.url);
final Completer<WebViewController> _controller =
Completer<WebViewController>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('اكمال عملية الدفع..'),
leading: new IconButton(
icon: new Icon(Icons.close),
onPressed: () {
if(isFinshed) {
Provider.of<MatchProvider>(context, listen: false)
.getMyComingMatches();
Navigator.of(context).popUntil((route) => route.isFirst);
} else {
Navigator.pop(context);
}
}),
),
body: Builder(builder: (BuildContext context) {
return WebView(
initialUrl: Uri.encodeFull(url),
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
debuggingEnabled: true,
onPageFinished: (String url) {
SystemChannels.textInput.invokeMethod('TextInput.hide');
if (url.contains("tap/check?tap_id")) {
isFinshed = true;
}
print('Page finished loading: $url');
},
gestureRecognizers: null,
// gestureNavigationEnabled: false
);
}));
}
The url looks like:
https://xxxxxx.com/tap/check?tap_id=chg_TS05162021120xxxxxxx
Everything is working on Android, but on IOS i get a blank screen and i see this error in xcode debug logs :
WebPageProxy::didFailProvisionalLoadForFrame
I have tried to run another urls on the webview and it was working, but the payment url isn't, even though it's working on Android or other browsers.
I think you maybe tried to load a http link in webview instead of https. In that case you should add the following in your info.plist file.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>
I am not familiar with Flutter, but on iOS, if the system's process for rendering the web content terminated, the webview would be blank, the flutter plugin is handling the situation in here, you may have to check the error and do a reload.
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
),
);
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.
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.
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);
}