I'm new in flutter and trying to understand flutter state management concept using provider. This the image scenario what I'm trying to do
I have created a file called auth_provider.dart file under the folder called Providers
class AuthProvider with ChangeNotifier{
bool isLogin = false;
Future createUser() async
{
isLogin = true;
notifyListeners();
}
Future login() async
{
isLogin = true;
notifyListeners();
}
void logout()
{
isLogin = false;
notifyListeners();
}
}
This the Signup button that I have created in the login page
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SignupPage()
),
);
},
child: const Text(
'Signup Button',
),
)
This is the signUp button in signup screen
child: ElevatedButton(
onPressed: () => signUpSubmit(),
child: const Text(
'Sign Up',
),
),
I have written a signUpSubmit future like below
Future<void> signUpSubmit() async {
Provider.of<AuthProvider>(context, listen: false).createUser();
}
I have used AuthProvider consumer in main.dart page
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => AuthProvider(),
),
],
child: Consumer<AuthProvider>(
builder: (ctx,auth,child){
print(auth.isLogin);
return MaterialApp(
home: auth.isLogin ? const HomeScreen():const LoginPage(),
routes: {
HomeScreen.routeName: (ctx) => const HomeScreen(),
SignupPage.routeName: (ctx) => const SignupPage(),
LoginPage.routeName: (ctx) => const LoginPage(),
},
);
}
),
);
}
}
After click on signup button I'm getting true in main page , which I have given a print under Consumer builder in main.dart page. So according to MaterialApp widget home condition page should redirect to HomeScreen but it's not moving. Why it's not moving ? What is the main cause and what it the best way to solve this problem ?
Note : If I try it from login screen redirection is working fine. But according to my image flow (Login -> signup) it's not working.
here is the code you are looking for, but bear in mind with the implementation you have right now, if the user opens the app again, it will redirect them to the signin page. because the boolean value will disappear once the user closes the app.
change your main.dart file like the following..
main function
void main() {
// you just need to add the multiprovider and the change notifier provider class
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthProvider()),
],
child: const MyApp(),
),
);
}
here is the MyApp class as i understand it.
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 Consumer<AuthProvider>(builder: (ctx, auth, child) {
print(auth.isLogin);
return MaterialApp(
home: auth.isLogin ? MyHomePage() : LoginPage(),
routes: {
MyHomePage.routeName: (ctx) => MyHomePage(),
LoginPage.routeName: (ctx) => LoginPage(),
//NavScreen.routeName: (ctx) => const NavScreen(),
},
);
});
}
}
Change the signup button in the register page to the following.
ElevatedButton(
onPressed: () {
signUpSubmit(context);
Navigator.of(context).pushNamed(HomeScreen.routeName);
},
and the signupsubmit function like this..
signUpSubmit(BuildContext context) {
Provider.of<AuthProvider>(context, listen: false).createUser();
}
The main cause of your problem is that you are pushing a new route (screen) from login page and the best way to solve problem is to pop that route (screen) from sigupPage.
On click of Signup button from login page you are pushing a new route, so in order to redirect to HomeScreen from SignupPage first you need to pop that route so that you can see the updated changes.
Future<void> signUpSubmit() async {
Navigator.of(context).pop();
Provider.of<AuthProvider>(context, listen: false).createUser();
}
https://docs.flutter.dev/cookbook/navigation/navigation-basics
Related
I want to show this login screen only on the first start up. After that it s possible to come to login screen by pressing "Account" button in another widget, I already did that. Only the shared prefs bool is my problem: I dont know where do I have to implement the bool, and how do i sent it to the root widget. Let me show the code:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await init();
SharedPreferences prefs = await SharedPreferences.getInstance();
return runApp(ChangeNotifierProvider(
child: const MyApp(),
create: (BuildContext context) => ThemeService(
isSysModeOn: prefs.getBool("isSystemMode") ?? true, isDarkOn: prefs.getBool("isDarkModeOn") ?? true)));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => NavbarCubit()),
BlocProvider(create: (context) => sl<SignupBloc>()),
BlocProvider(create: (context) => DrinkCubit()),
],
child: Consumer<ThemeService>(builder: (context, themeService, child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: themeService.getSysMode
? ThemeMode.system
: (themeService.getDarkMode ? ThemeMode.dark : ThemeMode.light),
home: first_time ? const SignUpPage() : RootWidget());
}),
);
}
}
the if question in the home: is manuall added, not working. I know i already did nearly the same thing with the Theme Data as you can see in the void main :D But how do I ask
bool first_time = true;
if(user_firstime_started_app == true){
first_time = false;
prefs.setBool(.....
....
}
something like this.
You can show a splash screen while you check if the user is logged in. If the user is logged in then replace and navigate to the home page or the login page.
Refer to this answer and replace the logic of seen with the logic of is logged in: https://stackoverflow.com/a/50655196/2008962.
I have been working on an app, here the basic structure looks like.
Having a MultiblocProvider. With two routes.
Route generateRoute(RouteSettings routeSettings) {
switch (routeSettings.name) {
case BASE_ROUTE:
return MaterialPageRoute(
builder: (_) => BlocProvider(
create: (context) => SignupCubit(),
child: SignUp(),
),
);
case OTP_VERIFY:
return MaterialPageRoute(
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => VerifyCubit(),
),
BlocProvider(
create: (context) => SignupCubit(),
),
],
child: Verify(),
),
);
default:
return MaterialPageRoute(builder: (_) => Broken());
}
}
In OTP_Verify route I am giving access to two Cubit, VerifyCubit() and SignupCubit().
Now, what i am doing is,
There is two Screen, one is SignUp and the other is Verify. In SignUp Screen, if the state is SignUpSuccess, I am navigating to verify OTP screen.
class SignUp extends StatelessWidget {
const SignUp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
double deviceHeight = MediaQuery.of(context).size.height;
return Scaffold(
body: BlocListener<SignupCubit, SignupState>(
listener: (context, state) {
if (state is SignUpError) {
showToast("Please try again");
} else if (state is SignupSuccess) {
print(state.email);
Navigator.pushNamed(context, OTP_VERIFY); <--- Here
} else if (state is EmailValidationError) {
showToast("Not valid email");
}
},
child: SafeArea(
bottom: false,
child: CustomScrollView(
slivers: [
.... rest of code....
In VerifyOTP screen, i am trying to read state of current SignUpCubit
....other code....
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(45),
primary: Theme.of(context).primaryColor),
onPressed: () {
final signUpState = BlocProvider.of<SignupCubit>(context).state; <--- Here
if (signUpState is SignupSuccess) {
print(signUpState.email);
}
BlocProvider.of<VerifyCubit>(context).setOtp(otp);
},
child: const Text('Verify'),
),
.....other code.....
This is my SignUpState
part of 'signup_cubit.dart';
#immutable
abstract class SignupState {}
class SignupIntial extends SignupState {}
class SignUpError extends SignupState {}
class SignupSuccess extends SignupState {
final String email;
SignupSuccess({required this.email});
}
class EmailValidationError extends SignupState {}
Now what I am assuming is I already emitted SignupSuccess in first page and I could read it in second page if I have provided that state by MultiBlocProvider.
But its not happening. Insted I am getting SignUpIntial.
Can someone please help, what i could be doing wrong, or is my method even valid ?
that's because you provide a new instance of the SignupCubit while routing to Verify Screen. thus BlocProvider.of<SignupCubit>(context).state will return the state of the cubit above it which is still in the initial state.
I don't know why you need to check the state of the SignupCubit in the Verify Since you only navigate to it when it's SignupSuccess but anyway, a quick workaround is that you declare and initialize an instance of SignupCubit and use it in the provider around the SignUp and Verify Screens.
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 am trying to create a simple authentication flow using Provider. I have three pages :
LoginPage
OnboardingPage
HomePage
The flow of this app is:
if a user opens the app for the first time, he/she will be redirected to the onboarding then to login to home.
For the second time user, the app first checks the login status and redirected to either log in -> home or straight to home page.
Here is my setup in code :
main.dart
void main() {
runApp(MultiProvider(providers: [
ChangeNotifierProvider<StorageHelper>(create: (_) => StorageHelper()),
ChangeNotifierProvider<AuthProvider>(create: (_) => AuthProvider()),
], child: MyApp()));
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (final BuildContext context,
final AuthProvider authProvider, final Widget child) {
print(authProvider.isAuthenticated); // this is false whenever I //click the logout from category(or other pushed pages) but the below ternary //operation is not executing
return MaterialApp(
title: 'My Poor App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Color(0xff29c17e),
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: authProvider.isAuthenticated ? HomeScreen() : LoginScreen(),
onGenerateRoute: Router.onGenerateRoute,
);
});
}
}
LoginScreen.dart
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final authProvider = Provider.of<AuthProvider>(context, listen: false);
return Scaffold(
body: Center(
child: MaterialButton(
onPressed: () async {
await authProvider.emailLogin('user#email.com', 'pass');
},
child: Text('Login'))),
);
}
}
HomeScreen.dart
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final auth = Provider.of<AuthProvider>(context, listen: false);
return Scaffold(
body: Center(
child: MaterialButton(
elevation: 2,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CategoryScreen()));
},
child: Text('Reset')),
),
);
}
}
AuthProvider.dart
class AuthProvider extends ChangeNotifier {
bool _isAuthenticated = false;
bool get isAuthenticated => _isAuthenticated;
set isAuthenticated(bool isAuth) {
_isAuthenticated = isAuth;
notifyListeners();
}
Future emailLogin(String email, String password) async {
isAuthenticated = true;
}
Future logout() async {
isAuthenticated = false;
}
}
If i logout from home page using Provider.of<AuthProvider>(context).logout() it works fine. But if I push or pushReplacement a new route and try to logout from the new route (just say I navigated from home to category page and try to logout from there), I am not redirected to LoginPage. If I print the value of isAuthenticated it prints false but the consumer is not listening or at least not reacting to the variable change.
Please don't mark this question as duplicate, I have searched many other similar questions and none of them worked for my case.
Edit:
CategoryScreen.dart
class CategoryScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () {
final auth = Provider.of<AuthProvider>(context, listen: false);
auth.logout();
// print(auth.isAuthenticated);
},
child: Text('Category Logout'),
),
),
);
}
}
I guess your problem is that you did not use Consumer for the logout, in your home in the MaterialApp. Just see, that if it works out for you
main.dart
// needs to listen to the changes, to make changes
home: Consumer<AuthProvider>(
builder: (context, authProvider, child){
return authProvider.isAuthenticated ? HomeScreen() : LoginScreen();
}
)
Since, Consumer was not there for your home, even if the value was being changed, it was not able to work on updating the view for you as per the Provider.
I am creating a loading screen for an app. This loading screen is the first screen to be shown to the user. After 3 seconds the page will navigate to the HomePage. everything is working fine. But when the user taps back button the loading screen will be shown again.
FIRST PAGE CODE
import 'dart:async';
import 'package:flutter/material.dart';
import 'home_page.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
Future.delayed(
Duration(
seconds: 3,
), () {
// Navigator.of(context).pop(); // THIS IS NOT WORKING
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlutterLogo(
size: 400,
),
),
);
}
}
HOMEPAGE CODE
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text('HomePage'),
),
),
);
}
}
I tried to add Navigator.of(context).pop(); before calling the HomePage but that is not working. This will show a blank black screen.
Any ideas??
You need to use pushReplacement rather than just push method. You can read about it from here: https://docs.flutter.io/flutter/widgets/Navigator/pushReplacement.html
And to solve your problem just do as explain below.
Simply replace your this code:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
with this:
Navigator. pushReplacement(
context,
MaterialPageRoute(
builder: (context) => HomePage(),
),
);
Yes, I found the same problem as you. The problem with replace is that it only works once, but I don't know why it doesn't work as it should. For this after a few attempts, I read the official guide and this method exists: pushAndRemoveUntil (). In fact, push on another widget and at the same time remove all the widgets behind, including the current one. You must only create a one Class to management your root atrough the string. This is the example:
class RouteGenerator {
static const main_home= "/main";
static Route<dynamic> generatorRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case main_home:
return MaterialPageRoute(builder: (_) => MainHome());
break;
}
}
}
This class must be add to the Main in:
MaterialApp( onGenerateRoute: ->RouteGenerator.generatorRoute)
Now to use this method, just write:
Navigator.of(context).pushNamedAndRemoveUntil(
RouteGenerator.main_home,
(Route<dynamic> route) => false
);