I want to stay on the same page(such as otpPage)until and unless a condition is verified.
Condition networkutil.verify == "Y".
This is my main.dart
final routes = {
'/':(context)=> SignIn() ,
'/SignIn': (context)=> SignIn(),
'/Home': (context)=> Home(),
'/Register': (context) => Register(),
'/OtpPage': (context) => OtpPage()
};
class A extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: './',
routes: routes,
);
}
}
You can use BLoC Pattern, where you will put a condition :
if the user is registered, then Welcome page else, SignIn Page.
Hope you can get some idea from this repo:
https://github.com/samrat19/login_bloc
Related
I wonder how to manage screen navigation when your app is connected to Firebase.
When my app was offline I used a routes table, but Im not sure how to do now. Could I do as I show with my code below; use a streambuilder that switches between the AuthScreen when logged out and HomeScreen when logged in, and a routes table to switch with the following screens also when signed in.
I tried this approach but when im signing out from another screen than the HomeScreen the user stays signed in.
How can I set up my routes so that the user always signs out independent from which screen the user's currently on.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlutterApp',
theme: ThemeData(
primarySwatch: Colors.orange,
),
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (
ctx,
userSnapshot,
) {
if (userSnapshot.hasData) {
return HomeScreen();
}
return AuthScreen();
}),
routes: {
Screen1.routeName: (ctx) => Screen1(),
Screen2.routeName: (ctx) => Screen2(),
Screen3.routeName: (ctx) => Screen3(),
Screen4.routeName: (ctx) => Screen4(),
});
}
}
I don't think you're going about this the right way.
They are various ways of doing this. Here's mine.
Firstly, I think you should define an initial route that checks if the user is signed in.
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlutterApp',
theme: ThemeData(
primarySwatch: Colors.orange,
),
initialRoute:
FirebaseAuth.instance.currentUser == null ? "/login" : "/home",
routes: {
"/home": (ctx) => HomeScreen(),
"/login": (ctx) => AuthScreen(),
Screen1.routeName: (ctx) => Screen1(),
Screen2.routeName: (ctx) => Screen2(),
Screen3.routeName: (ctx) => Screen3(),
Screen4.routeName: (ctx) => Screen4(),
});
}
Note: "/home" & "/login" could be whatever you want as long as it's a string.
For logout, you need to replace the current screen with the login page, something like this should do.
Navigator.pushNamedAndRemoveUntil(context, '/login', (route) => false);
I'm using both BlocProvider & ChangeNotifierProvider in my app. The flow of the app goes here:-
first time user opens the app: InstructionPage() -> WelcomePage() -> HomePage() //getting error
second time user opens the app: HomePage() //working fine
I'm using sharedPreference to store the value of isInstructionPageLoaded.
But navigating from WelcomePage() to HomePage() getting error Could not find the correct Provider above this ChangeLocation Widget
here is my code:-
//main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageUtil.getInstance();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: Theme.of(context).copyWith(primaryColor: kBgColorGreen),
home: MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>
RestaurantBloc()..add(RestaurantPageFetched())),
],
child: MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => LocationServiceProvider()),
],
child: StorageUtil.getBoolValue(
SharedPrefsKeys.isInstructionPageLoaded)
? HomePage()
: InstructionScreen(),
)),
routes: Routes.getRoutes(),
);
}
}
//routes.dart
class Routes {
static const String instruction = '/instruction';
static const String welcome = '/welcome';
static const String home = '/home';
static const String change_location = '/change_location';
static Map<String, WidgetBuilder> getRoutes() {
return {
Routes.instruction: (context) => InstructionScreen(),
Routes.welcome: (context) => WelcomePage(),
Routes.home: (context) => HomePage(),
Routes.change_location: (context) => ChangeLocation(),
};
}
}
//location_service.dart
class LocationServiceProvider extends ChangeNotifier {
void toogleLocation(LocationService location) {
location.isLocationUpdated = !location.isLocationUpdated;
notifyListeners();
}
}
class LocationService {
bool isLocationUpdated = false;
}
//welcome_page.dart -
on button pressed calling below method
void _navigateToHomePage() async {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return BlocProvider(
create: (context) => RestaurantBloc()..add(RestaurantPageFetched()),
child: ChangeNotifierProvider(create: (context) => LocationServiceProvider(),
child: HomePage(),),
);
}));
}
I have added BlocProvider in above method becoz before it was giving me error
blocprovider.of() called with a context that does not contain a bloc navigating from other screen from navigating from WelcomePage() to HomePage().
Thanks in advance!!!
To make sure the blocs are exposed to new routes, you need to follow the documentation and add BlocProvider.value() to provide the value of the bloc to new routes. This will carry the bloc's state and make your life easier.
Check the Official Documentations for a clear step-by-step guide ;).
I've looked over several other posts with this same error about the Navigator and either their code looks different, it fails in totally different places, or other reasons and I must be missing something important. Where this fails for me is only from resuming from background or sleep. The app lifecycle detects "resume" and I want to navigate to the login page for the user to select a profile or login. The error below shows any way I try to use a Navigator in that function didChangeAppLifecycleState(AppLifecycleState state). Actually if I use Navigator anywhere in main.dart it gives the error. Outside of main.dart Navigator works great.
Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
The code that causes the error in main.dart :
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print("State changed! ${state}");
setState(() {
_notification = state;
});
if(state == AppLifecycleState.resumed){
NavService().navigateTo(context, '/login');
}
}
The main.dart build looks like this:
#override
Widget build(BuildContext context) {
return
MaterialApp(
theme: new ThemeData(
primarySwatch: themeSwatchColor,
brightness: Brightness.light,
primaryColor: themePrimaryColor,
accentColor: themeAccentColor,
),
initialRoute: '/',
navigatorObservers: <NavigatorObserver>[
NavService(), // this will listen all changes
],
onGenerateRoute: (routeSettings) {
switch (routeSettings.name) {
case '/':
return MaterialPageRoute(builder: (_) => LoginPage());
case '/login':
return MaterialPageRoute(builder: (_) => LoginPage());
case '/home':
return MaterialPageRoute(builder: (_) => HomePage());
case '/items':
return MaterialPageRoute(builder: (_) => ItemLookupPage());
case '/settings':
return MaterialPageRoute(builder: (_) => SettingsPage());
case '/oldsettings':
return MaterialPageRoute(builder: (_) => SecondPage());
case '/pickorders':
return MaterialPageRoute(builder: (_) => ReceivedOrdersPage());
case '/orders':
return MaterialPageRoute(builder: (_) => OrdersPage());
case '/receiving':
return MaterialPageRoute(builder: (_) => ReceivingPage());
case '/inventory':
return MaterialPageRoute(builder: (_) => InventoryPage());
default:
return MaterialPageRoute(builder: (_) => LoginPage());
}
},
home: (noAccount == true)
? LoginPage()
: HomePage(),
);
}
NavService.dart:
class NavService extends RouteObserver {
void saveLastRoute(String lastRoute) async {
if(lastRoute != "/login" && lastRoute != "/error"){
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('last_route', lastRoute);
}
}
Future<dynamic> navigateTo(BuildContext context, String routeName, {Map data}) async {
saveLastRoute(routeName);
return Navigator.pushNamed(context, routeName, arguments: data);
}
}
I also tried skipping my NavService and used Navigator directly, but the same error shows.
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LoginPage(),
),
);
I tried using a GlobalKey as other posts have suggested, but the NavService() using the RouteObserver breaks when I do that.
The NavService and page routing works very well anywhere in the app. Its only while navigating in main.dart I'm having the issue. I just noticed if I place the above Navigator.of().push in initState() I get the same error. Maybe my MaterialApp is setup wrong? Or am I using the NavService incorrectly?
Thanks for any help!
The didChangeAppLifecycleState method does not provide any context unlike the build method. You would have to navigate without using context by setting a global key for your navigation:
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Pass it to MaterialApp:
MaterialApp(
title: 'MyApp',
onGenerateRoute: generateRoute,
navigatorKey: navigatorKey,
);
Push routes:
navigatorKey.currentState.pushNamed('/someRoute');
Credits to this answer
New to Flutter.
I'm making an app that has a splash screen that initially shows up when the user opens the app. After 3 seconds, the app will show the login or the dashboard screen, depending on the authentication state.
Here's my code.
main.dart
void main() {
runApp(myApp);
}
MaterialApp myApp = MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => SplashScreen(),
"/signin": (context) => SignInScreen(),
"/notes": (context) => NotesScreen(),
},
);
splash_screen.dart
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_goToNextScreen();
}
void _goToNextScreen() {
Future.delayed(
Duration(seconds:3),
() async {
AuthState authState = await Auth.getAuthState();
String route = authState == AuthState.SIGNED_IN ? "/notes" : "/signin";
Navigator.pushReplacementNamed(context, route);
}
);
}
// build() override goes here...
}
I've been debugging the app with a web-server. When the app launches with the url localhost:8000/, everything seems fine. However, if the app started with the url localhost:8000/notes, the splash screen, I think, still gets initiated. What happens is the app will show the notes screen, then after 3 seconds, the app will open another notes screen.
Any ideas?
Because first render always started at root '/', it's preferable to use your own path for splash screen, like
initialRoute: '/splash'.
To hide this path in the address bar, replace routes map with route generator:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (RouteSettings settings) {
// print current route for clarity.
print('>>> ${settings.name} <<<');
switch (settings.name) {
case '/splash':
return MaterialPageRoute(
builder: (context) => SplashScreen(),
// settings omitted to hide route name
);
case '/signin':
return MaterialPageRoute(
builder: (context) => SignInScreen(),
settings: settings,
);
case '/notes':
return MaterialPageRoute(
builder: (context) => NotesScreen(),
settings: settings,
);
case '/':
// don't generate route on start-up
return null;
default:
return MaterialPageRoute(
builder: (context) => FallbackScreen(),
);
}
},
initialRoute: '/splash',
);
}
}
See since the main logic is we cannot have await in the init state so the page will build irrespective of the any logic you provide. I have a solution to this, there may be some advance or other good solutions too, so this is what I would use.
I would use a concept of future builder. What it will do is wait for my server and then build the whole app.
So process is
In your main.dart
use
Future<void> main() async {
try {
WidgetsFlutterBinding.ensureInitialized();
//await for my server code and according to the variable I get I will take action
//I would have a global parameter lets say int InternetOff
await checkServer();
runApp(MyApp());
} catch (error) {
print(error);
print('Locator setup has failed');
//I can handle the error here
}
}
Now MyApp stateless Widget that will help us choose our path
class MyApp extends Stateless Widget{
Widget build(BuildContext context) {
//Using this FutureBuilder
return FutureBuilder<String>(
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
// AsyncSnapshot<Your object type>
// Now if InternetOff is equal to one I would make it go to home
if(InternetOff==1) return MaterialApp(
theme: ThemeData.light(),
home: CheckInternet(),
debugShowCheckedModeBanner: false,
);
//else go to Home similarly with these if and else you can add more conditions
else {
return MaterialApp(
theme: ThemeData.dark(),
home: UserHome(),
debugShowCheckedModeBanner: false,
);
}
}
}
},
);
}
}
First of all, flutter-web like any other Single Page Application supports hash based routing. As a result if you want to access
localhost:8000/notes
you have to access it as
localhost:8000/#/notes
Cleaner way to handle auth state
Call getAuthState function before runApp() to make sure that the auth state is set before app is initialized. And pass authState to SplashScreen widget as parameter.
void main() {
WidgetsFlutterBinding.ensureInitialized();
AuthState authState = await Auth.getAuthState();
runApp(MaterialApp myApp = MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => SplashScreen(authState: authState),
"/signin": (context) => SignInScreen(),
"/notes": (context) => NotesScreen(),
},
));
}
splash_screen.dart
class SplashScreen extends StatefulWidget {
final AuthState authState;
SplashScreen({Key key, this.authState}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_goToNextScreen();
}
void _goToNextScreen() {
Future.delayed(
Duration(seconds:3),
() async {
String route = widget.authState == AuthState.SIGNED_IN ? "/notes" : "/signin";
Navigator.pushReplacementNamed(context, route);
}
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
And if you want even more cleaner way to handle auth state, you have to use state management solution like Provider.
still a beginner in flutter. below is a sample chat apps i tried to redirect user depending on their login status.
so far tested with emulator, the outputs is what i expected. my questions are:
1.is this the correct approach for user redirect, or is there a better way as in better refactored code?
2.any refactoring can be done for the 'return materialApp', as it is very repetitive. (only changing initialRoute)
3.any implication to runApp a StatefulWidget? because all tutorial normally starts runApp a StatelessWidget
import 'package:flutter/material.dart';
import 'package:chatting/screens/login_screen.dart';
import 'package:chatting/screens/registration_screen.dart';
import 'package:chatting/screens/chat_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:async';
void main() => runApp(LoadPage());
class LoadPage extends StatefulWidget {
#override
_LoadPageState createState() => _LoadPageState();
}
class _LoadPageState extends State<LoadPage> {
Future checkIfLoggedIn;
#override
void initState() {
super.initState();
checkIfLoggedIn = FirebaseAuth.instance.currentUser();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<FirebaseUser>(
future: checkIfLoggedIn,
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent,
),
);
default:
if (snapshot.hasData)
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: ChatScreen.id,
routes: {
ChatScreen.id: (context) => ChatScreen(),
LoginScreen.id: (context) => LoginScreen(),
RegistrationScreen.id: (context) => RegistrationScreen(),
},
);
else
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: LoginScreen.id,
routes: {
ChatScreen.id: (context) => ChatScreen(),
LoginScreen.id: (context) => LoginScreen(),
RegistrationScreen.id: (context) => RegistrationScreen(),
},
);
}
});
}
}
Yeah your code looks good to me. There's no problem using a StatefulWidget in runApp.
The only additional tip I'd give is that, typically, for larger applications, you'll want to use the BLoC pattern to manage state. If you added that pattern to this code sample, it would abstract the logic you're doing away from this component, and you could manage the future in the bloc. You could then use a stateless widget for your loading screen. The Flutter Bloc library provides useful, straightforward abstractions that show how to implement the bloc pattern.