I'm Trying to know if the user is logged in or not using firebase google Auth and send him to different pages depending the case.
If the user is Logged in, Streambuilder returns profile() and if not, signUp() is returned. So far, so good. But what I need is to Navigate to another page using Navigator instead of returning widgets.
I need to do this:
Navigator.push( context, MaterialPageRoute(builder: (context) => profile()),
Instead of:
return profile();
The code I'm working on is:
body: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasData) {
return profile();
} else if (snapshot.hasError) {
return Center(child: Text("Something went wrong"));
} else {
return signUp();
}
},
),
Any idea on how to do this? Should I use other approach instead of a Streambuilder? Thanks in advance!
the FirebaseAuth.instance.authStateChanges() returns a Stream, so you can listen to it inside your app and make acts based on it, instead of using it in a StreamBuilder, you can listen to it like this:
FirebaseAuth.instance.authStateChanges().listen((user) {
if(user != null) {
Navigator.push( context, MaterialPageRoute(builder: (context) => profile()),
} else {
Navigator.push( context, MaterialPageRoute(builder: (context) => Login()),
}
});
you need just to find a place where you're going to call it, once it listens to the authStateChanges(), by authenticating a new user or logging him out, this stream will trigger the change and execute the code inside of it.
So I have an app that Pops the current screen once an event happens. The code works fine on iOS and Android. But on the web I get the following error:
Assertion failed:
..\…\widgets\navigator.dart:5083
_history.isNotEmpty
is not true
The debug dialog shows how points the CupertinoTabView of the current tab in the stack. The navigation is to the root page of the Tab view. The tabBuilder:
(context, index) {
switch (index) {
case 0:
return CupertinoTabView(
navigatorKey: tabOneKey,
builder: (context) {
return tabs[index];
},
);
break;
case 1:
return CupertinoTabView(
navigatorKey: tabTwoKey,
builder: (context) {
return tabs[index];
},
);
break;
case 2:
return CupertinoTabView(
navigatorKey: tabThreeKey,
builder: (context) {
return tabs[index];
},
);
break;
case 3:
return CupertinoTabView(
navigatorKey: tabFourKey,
builder: (context) {
return tabs[index];
},
);
break;
default:
return Container();
}
}
I have tried Wrapping the Navigator.pop() in WidgetsBinding.instance.addPostFrameCallback. The same error happens on web. It changes nothing on mobile. What may be causing this error?
the navigator work like stack, pop will remove the last page , push will add to the top , this error mean you are trying to remove the last page, but there is none , this can happen if you pop the previous pages after push like the following
Navigator.of(context).pushNamedAndRemoveUntil( "pageName", (Route<dynamic> route) => false) ;
Navigator.of(context).pop();
Solution :
first thing to do is don't wrap pop , use this instead if you are not sure if the navigator is empty or not .
Navigator.of(context).maybePop();
and make sure you use the following before any .pop() not pushNamedAndRemoveUntil
Navigator.of(context).pushNamed ("pageName") ;
I am trying to get the name of the screen to navigate to as a string(in a custom class), when clicking a button.
class myButton extends StatelessWidget {
String button_txt = '';
String nextPage = '';
double height_ = 39;
myButton(button_txt) {
this.button_txt = button_txt;
this.nextPage = nextPage;
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.pushReplacement(context, '/$nextPage');
},
If you want to use named routes you should use it like this:
Navigator.pushReplacementNamed(context, 'nextPage');
but if you don't want to use named routes there is different way to navigate:
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) {
return routewidget;
},
),
);
There are two ways to do this:
use Navigator.pushNamedReplacement(context, '/$nextPage')
use Navigator.pushReplacement(context, route); with a Route Parameter
If you want to use named routes, then use
Navigator.pushNamedReplacement(context, '/name/of/route).
If it still doesn't work, then you might not have set up navigation properly.
If you want to use non-named routes, then the approach will be different:
Navigator.pushReplacement(
context,
MaterialPageRoute( // Material page route makes it
// slide from the bottom to the top
//
// If you want it to slide from the right to the left, use
// `CupertinoPageRoute()` from the cupertino library.
//
// If you want something else, then create your own route
// https://flutter.dev/docs/cookbook/animation/page-route-animation
builder: (context) {
return NextPageWidget();
},
),
);
I am trying to navigate to my home page or login page on the basis of authStatus. While using Navigator.of(context)... it returns null on the build method for a second and then screen refreshes and navigate to the given page successfully. I am relatively new to mobile development and flutter. Any leads will help! Thanks.
Here is the code:
#override
Widget build(BuildContext context) {
switch (authStatus) {
case AuthStatus.NOT_LOGGED_IN:
WidgetsBinding.instance.addPostFrameCallback((_) {
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginSignUpPage(
auth: widget.auth,
onSignedIn: _onLoggedIn,
params: widget.params,
)),
);
}
});
break;
case AuthStatus.LOGGED_IN:
if (_userId.length > 0 && _userId != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomePage(
userId: _userId,
auth: widget.auth,
onSignedOut: _onSignedOut,
params: widget.params,
)),
);
});
} else
return widget.waitingScreen;
break;
default:
return widget.waitingScreen;
}
Here is the error :
A build function returned null.
The offending widget is:
RootPage
Build functions must never return null.
To return an empty space that causes the building widget to fill available room, return
"Container()". To return an empty space that takes as little room as possible, return
"Container(width: 0.0, height: 0.0)".
on the code line following WidgetsBinding, add
return Container();
You could also include a color parameter to match your design.
I'm building a web-app which needs to have a route that gets a post ID and then it will fetch the post using the ID.
How can I have URL arguments let's say /post/:id so id is the argument
My app looks like that currently:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
// title: "Paste",
initialRoute: "/",
theme: ThemeData(
primarySwatch: Colors.green,
primaryColor: Colors.blue
),
routes: {
"/": (context) => HomePage(),
"/post": (context) => PastieRoute()
},
debugShowCheckedModeBanner: false
);
}
}
EDIT:
This is what I tried according to #BloodLoss and for some reason I don't get anything to the console when accessing localhost:8080/post?id=123
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => HomePage(),
"/post": (context) => PastieRoute()
},
onGenerateRoute: (settings) {
if (settings.name == "/post") {
print(settings.arguments); // Doesn't fire :(
return MaterialPageRoute(
builder: (context) {
// TODO
}
);
}
},
debugShowCheckedModeBanner: false
);
}
}
tl;dr
//in your example: settings.name = "/post?id=123"
final settingsUri = Uri.parse(settings.name);
//settingsUri.queryParameters is a map of all the query keys and values
final postID = settingsUri.queryParameters['id'];
print(postID); //will print "123"
Drilldown
In a perfect world you would access queryParameters with Uri.base.queryParameters because:
Uri.base
Returns the natural base URI for the current platform.
When running in a browser this is the current URL of the current page (from window.location.href).
When not running in a browser this is the file URI referencing the current working directory.
But currently there is an issue in flutter where you have #/ in your path which messes the Uri.base interpretation of the Uri.
Follow the issue #33245 until this matter is addressed and you will be able to use Uri.base.queryParameters
please follow this link further information https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments
on your MaterialApp
onGenerateRoute: (settings) {
// If you push the PassArguments route
if (settings.name == PassArgumentsScreen.routeName) {
// Cast the arguments to the correct type: ScreenArguments.
final ScreenArguments args = settings.arguments;
// Then, extract the required data from the arguments and
// pass the data to the correct screen.
return MaterialPageRoute(
builder: (context) {
return PassArgumentsScreen(
title: args.title,
message: args.message,
);
},
or you can nativate like web using this plugin fluro
This is how I did it. You can edit it as per your requirements. If you want to use ?q= then use the split by or regex accordingly
Here is the example of both passing in argument as well as passing in url as /topic/:id
Route<dynamic> generateRoute(RouteSettings settings) {
List<String> pathComponents = settings.name.split('/');
final Map<String, dynamic> arguments = settings.arguments;
switch ("/"+pathComponents[1]) {
case shareTopicView:
return MaterialPageRoute(
builder: (context) => TopicPageLayout(topicID: pathComponents[2]));
case internalTopicView:
return MaterialPageRoute(
builder: (context) => TopicPageLayout(topicID: arguments['topicID']));
default:
return MaterialPageRoute(builder: (context) => LandingPage());
}
}
I'm new to Flutter, and I found a quirky workaround,...
import 'dart:html';
String itemID;
//My url looks like this,... http://localhost:57887/#item_screen/12345
//Counted 13 characters for '#item_screen/' then got the substring as below
itemID = window.location.hash.substring(13);
print(itemID) //12345
Not very sophisticated, but worked :-D
Add flutter_modular to your flutter web project.
current version: flutter_modular: ^3.1.1
Read dynamic routes section in: https://pub.dev/packages/flutter_modular#dynamic-routes
Example for the URL /post?id=123
Create your main widget with a MaterialApp and call the ´´´MaterialApp().modular()´´´ method.
// app_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/",
).modular();
}
}
Create your project module file extending Module:
// app_module.dart
class AppModule extends Module {
#override
final List<Bind> binds = [];
#override
final List<ModularRoute> routes = [
ChildRoute('/', child: (_, __) => HomePage()),
ChildRoute('/post', child: (_, args) => PostPage(id: args.queryParams['id'])),
];
}
3.In main.dart file, wrap the main module in ModularApp to initialize it with Modular:
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'app/app_module.dart';
void main() => runApp(ModularApp(module: AppModule(), child: AppWidget()));
And here is another way to do it:
My url pattern: www.app.com/#/xLZppqzSiSxaFu4PB7Ui
onGenerateRoute: (settings) {
List<String> pathComponents = settings.name.split('/');
if (pathComponents[1] == 'invoice') {
return MaterialPageRoute(
builder: (context) {
return Invoice(arguments: pathComponents.last);
},
);
} else
return MaterialPageRoute(
builder: (context) {
return LandingPage();
},
);
;
},
Here's a workaround that uses the 'default' route as my main route.
I did this because it seems to be the only way that Flutter will allow me to open a URL with an ID in it, that doesn't return a 404.
E.g. Flutter does not seem to respect the '?' separator. So a URL with an ID in it, is read by flutter as an unknown URL. E.g. site.com/invoice?id=999 will return a 404, even in /invoice is set up as route.
My goal: I have a 1-page web app that simply displays a single invoice at a time, which corresponds to the ID in the URL.
My URL
app.com/#/xLZppqzSiSxaFu4PB7Ui
The number at the end of the URL is a FireStore Doc ID.
Here's the code in MyApp:
onGenerateRoute: (settings) {
List<String> pathComponents = settings.name.split('/');
switch (settings.name) {
case '/':
return MaterialPageRoute(
builder: (context) => Invoice(),
);
break;
default:
return MaterialPageRoute(
builder: (context) => Invoice(
arguments: pathComponents.last,
),
);
}
},
This sends 'xLZppqzSiSxaFu4PB7Ui' to the 'Invoice' widget.
Try onGenerateRoute with below sample
final info = settings.arguments as Mediainfo?;
settings = settings.copyWith(
name: settings.name! + "?info=" + info!.name, arguments: info);
return MaterialPageRoute(
builder: (_) => MediaDetails(info: info), settings: settings);
This was my solution:
First, kind of seperate, I have an abstract class, AppRoutes, which is just a collection of string-routes, that way they're easily maintainable and switchable.
abstract class AppRoutes {
static const String guestGetMember = "/guest_getMember";
...
static render(String url, {Map<String, dynamic>? params}) {
return Uri(path: url, queryParameters: params ?? {}).toString();
}
}
Now for the code:
Route<dynamic> generateRoute(RouteSettings settings) {
Uri uri = Uri.parse(settings.name ?? "");
Map<String, dynamic> params = {};
// Convert numeric values to numbers. This is optional.
// You can instead `int.parse` where needed.
uri.queryParameters.forEach((key, value) {
params[key] = int.tryParse(value) ?? value;
});
final Map<dynamic, dynamic> arguments = (settings.arguments ?? {}) as Map<dynamic, dynamic>;
return MaterialPageRoute(builder: (context) {
switch (uri.path) {
case AppRoutes.guestGetMember:
return CardGuestViewProfile(memberID: params['memberID']!);
case AppRoutes...:
return...;
default:
return AppScreen();
}
// Navigator routes update web URLs by default,
// while `onGeneratedRoute` does not. That last
// line forces it to. The whole of using url
// variables for me was so that certainly URLs
// were easily copiable for sharing.
}, settings: (RouteSettings(name: settings.name)));
}
And then I call it with
Navigator.pushNamed(context,
AppRoutes.render(AppRoutes.guestGetMember,
params: {'memberID': memberID.toString()}),
arguments: {}));
params will be easily visible to web-users because it's a URL variable, while arguments will not be. This of course doesn't mean that arguments is by any means secure, it just means that non-essential information can be passed through this.