How can I implement Sign Up/Login logic with Provider. Flutter, Dart, Mobile Development - flutter

Let' say I have three files: main.dart, LoginScreen.dart, HomePageScreen.dart. I am utilizing Provider architecture for state management and wrapping MaterialApp.
When the app startup, the main.dart file checks whether the user is already Logged in (FirebaseAuth.currentUser to check this). If it is logged in, navigate to HomePageScreen, if not then navigate to SignUpScreen. Something like this on code:
// Inside of main
AppUser user = await database.getCurrentUser(); // user == null if no fbAuth.currentUser
runApp(MultiProvider(
providers: [,
ChangeNotifierProvider(create: (context) => user),
// some other notifier providers,
],
child: MyApp(),
));
// Inside of MyApp
#override
Widget build(BuildContext context) {
return MaterialApp(
...
home: user == null ? SignupScreen() : HomePageScreen();
);
}
This is great, it is working as I expected. If currentUser is null, it navigates to SignupScreen, if not then to HomePageScreen.
Problem:
In HomepageScreen, it listens to the User provider and updates UI components accordingly. Therefore, when the user successfully signed up for a new account or log in, I want to change/update the content of the user in the provider, so that when the app screen shift from SignupScreen to HomePageScreen, it displays the correct information on HomePageScreen. However, I cannot find a way to change the reference of the user inside of the provider.
Things that I tried:
In AppUser.dart, I created the method for changing the user reference:
class AppUser with ChangeNotifier {
String username, email, password;
void changeReference(AppUser newlyCreatedUser) {
this.username = newlyCreatedUser.username;
this.password = newlyCreatedUser.password;
this.email = newlyCreatedUser.email;
}
}
This is really just changing the content of the user. This way I can update the user to a newly created user. However, my instinct tells me that this is a very bad way of solving this problem. I feel like the problem is laying in the logic, the way I am trying to implement the sign up/login feature.
Summary:
Is there a way to change the reference of the value in the provider?
If this is not possible, or a bad way of using provider architecture, do you have any suggestion of how to implement Sign up feature

Related

Firebase Authorization is not checking if the user is logged in properly

in my flutter app I want to check if a user is logged in before or not and based on this navigate them to either HomePage or SignIn page. The code I am using is not working fine, it is not navigating to the SignIn page after I've done registration and deleted the account in Firebase Console. In short, when I delete a user, who registered well before, in Firebase Console the application is keeping to show the HomePage and all the posts the user made. How can I handle it?
"In short, I want to navigate the user to SignIn page after I delete his account in Firebase Console"
Here is the code in my main.dart file:
_check(){
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
if(user == null){
HiveDB.deleteUser();
return SignInPage();
}
HiveDB.putUser(id: user.uid);
return HomePage();
}
#override
void initState() {
// TODO: implement initState
super.initState();
_check();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: _check(),
routes: {
SignInPage.id:(context) => SignInPage(),
SignUpPage.id:(context) => SignUpPage(),
HomePage.id:(context) => HomePage(),
},
);
}
The _check() function is not working as it's desired...
While Firebase persists the user info in local storage and automatically restores it when the app/page reloads, this requires that it makes an asynchronous call to the server and thus may not be ready by the time your check runs.
Instead of depending on currentUser, you'll want to listen for changes to the authentication state through the authStateChanges stream. Most apps wrap this in a StreamBuilder and then show the correct screen based on the stream state and last event.
This is something that can be resolved by using async . the problem is you are getting response in future but the action is performed before. Please check the following documentation for detailed overview:
https://dart.dev/codelabs/async-await

What's the best way to implement requests with tokens clearly?

I have screen with tabs and each screen implements AutomaticKeepAliveClientMixin. When I navigate to this screen(with tabs), each tab in initState fetches data from server like that:
fetchData()async{
final token = await getToken();//refresh if it is expired.
return fetchData(token);
}
I think it'd better if I initialize data for all the tabs in one request, because I can catch only one refresh token expired and socket exception in single place.
fetchAllData()async{
final token = await getToken();//refresh if it is expired.
return fetchAllData(token);
}
How would you build logic for screen and requests like that? Is my approach is something similar to what you use?
I would recommend you to use a Provider (https://pub.dev/packages/provider). By subscribing to the same Provider, you will be able to reuse the data you've once fetched. For instance, I've used this approach to provide to my App (at different places) the current user:
class UserModel extends ChangeNotifier {
User _currentUser;
void setUser(User user) {
_currentUser = user;
notifyListeners();
}
Future<User> getUser(BuildContext context) async {
if (_currentUser == null) {
_currentUser = await getUserRequest(context, hasRedirect: false);
}
return _currentUser;
}
}
Hope it will fit your needs !
You can add your fetchAllData method to the initState of the widget that holds all of the tabbed widgets. Then, you can you can pass the relevant data to the contructors of each of the tabbed widgets. Not the best solution, but it should work.
I'd still recommend Provider. State management systems are not all inclusive, nor are exclusive. Depending on how your state is presented your could use more than one state management system. Helll, the bloc library already includes the provider library.

Navigating away when a Flutter FutureProvider resolves it's future

I'm basically looking for a way to either start loading data, or navigate to the Login Screen.
The FutureProvider gets it's value from SharedPreferences. The default homescreen is just a logo with a spinner.
If the userID resolves to null, the app should Navigate to the Login Screen, otherwise it should call a method that will start loading data and then on completion navigate to the main page.
Can this be achieved with FutureProvider?
I add it to the page build to ensure the page widget will subscribe to the Provider:
Widget build(BuildContext context) {
userInfo = Provider.of<UserInfo>(context);
print('Building with $userInfo');
return PageWithLoadingIndicator();
....
I added it to didChangeDependencies to react to the change:
#override
void didChangeDependencies() {
print('Deps changed: $userInfo');
super.didChangeDependencies();
// userInfo = Provider.of<UserInfo>(context); // Already building, can't do this.
// print('And now: $userInfo');
if (userInfo == null) return;
if (userInfo.userId != null) {
startUp(); // Run when user is logged in
} else {
tryLogin(); // Navigate to Login
}
}
And since I can't use Provider.of in initState I added a PostFrameCallback
void postFrame(BuildContext context) {
print('PostFrame...');
userInfo = Provider.of<UserInfo>(context);
}
Main is very simple - it just sets up the MultiProvider with a single FutureProvider for now.
class MyApp extends StatelessWidget {
Future<UserInfo> getUserInfo() async {
String token = await UserPrefs.token;
return UserInfo.fromToken(
token,
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App One',
theme: ThemeData(
primarySwatch: Colors.purple,
),
home: MultiProvider(
providers: [FutureProvider<UserInfo>(builder: (_) => getUserInfo())],
child: LoadingScreen(),
),
);
}
}
The problem is that as it is now I can see from the print statements that "didChangeDependencies" gets called twice, but the value of userInfo is always null, even though build() eventually gets an instance of UserInfo as evident by the print statement in the build() method.
I'm guessing I can add some logic into the build method but that screams against my sensibilities as the wrong place to do it... perhaps it isn't as bad as I think?
I've decided that this is conceptually the wrong approach.
In my case I wanted to use a FutureProvider to take the results from an Async function which create a "Config" object using SharedPreferences. The FutureProvider would then allow the rest of the app to access the user's config settings obtained from sharepreferences.
This still feels to me like a valid approach. But there are problems with this from an app flow perspective.
Mainly that the values from the shared preferences includes the logged in user session token and username.
The app starts by showing a Loading screen with a Circular Progress bar. The app then reads the shared preferences and connects online to check that the session is valid. If there is no session, or if it is not valid, the app navigates to the Login "wizzard" which asks username, then on the next page for the password and then on the next page for 2-factor login. After that it navigates to the landing page. If the loading page found a valid session, the login wizzard is skipped.
The thing is that the two things - app state and app flow are tangenially different. The app flow can result in changes being store in the app state, but the app state should not affect the app flow, at least not in this way, conceptually.
In practical terms I don't think calling Navigator.push() from a FutureProvider's build function is valid, even if context is available. I could be wrong about this, but I felt the flowing approach is more Flutteronic.
#override
void initState() {
super.initState();
_loadSharedPrefs().then((_) {
if(this.session.isValid()) _navToLandingPage();
else _navToLoginStepOne();
}
}
I'm open to better suggestions / guidance

Provider not accesable when Navigate to new screen

have a problem that I'm sitting on couple of days now.
have an app where:
depending of AUTH state, 'LoginScreen' or 'MainScreen' is Shown.
in MainScreen I setUp bottomNavigation with screens (HomeScreen, ShoppingScreen,MyFavorites)
I set up there as well my StreamProviders(those depend on Auth) by using MultiProvider
on HomeScreen when I User Provider.of(context) it works like it should
but when I use :
`Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ProfileScreen(),
),
);
` and use Provider.of(context) there I get "Could not find correct Provider....above this...widget"
I read some issues on that and solution there was to decler providers above MaterailApp which in my case I can not do because I can set up thoese only after Auth is successfull.
Tryed passing context(from HomeScreen) to ProfileScreen(through constructor) and that work but when value changed of UserData it did not update the screen (guessing beacause of diffrent 'contexts')
What am I doing wrong in here,any Ideas?:S
Providers are "scoped".
This means that if they are placed inside a screen, they aren't accessible outside that screen.
Which means that if a provider is scoped but needs to be accessed outside of the route it was created in, we have two solutions:
un-scope the provider. This involves moving the provider to a common ancestor of both widgets that needs to obtain the value.
If those two widgets are on two different Routes, then it basically mean "move the provider above MaterialApp/CupertinoApp.
manually pass the provider to the new screen (needed when using Navigator.push)
The idea is, instead of having one provider, we have two of them, both using the same value as explained here See How to scope a ChangeNotifier to some routes using Provider? for a practical example.
For Navigator.push, this can look like:
final myModel = Provider.of<MyModel>(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
ChangeNotifierProvider.value(
value: myModel,
child: MyScreen(),
),
),
);
Please make sure that you application's root widget is Provider Widget, it should event be the parent of MaterialWidget. If this is already the case I will need your code to look into. Something like this
class AppState {
User loggedInUser;
bool get isLoggedIn {
return loggedInUser != null;
}
// Other states as per the requirements
// ...
}

How can I test if the user is signed in and if not route to another page in my Flutter app?

I want to navigate to another page if the user is not signed in. I've tried to add this code to the initState but I can't use navigation at this stage of the lifecycle. Is there another option?
This won't work in the constructors of the StatefulWidget or State either. From what it looks like I have to control the routing.
// Where can I apply this code.
if (auth.currentUser == null) {
Navigator.of(context).pushNamed(SignInPage.routeName);
}
A similar question was asked here.
While it's technically possible to do what you want with a post-frame callback, you probably don't want to push a new route onto the Navigator here, because you don't want the user to be able to press the back button on the sign in page and go back to the home page. Also, you probably want to handle the case of the user logging out by taking them back to the sign in screen.
Consider having something like this in your main screen's build function.
Widget build() {
return auth.currentUser == null ? new SignInPage() : new HomePage();
}
If you are using Google Sign In
Widget build() {
return new StreamBuilder(
stream: googleSignIn.onCurrentUserChanged,
builder: (context, user) {
return user.data != null ? new HomePage() : new SignInPage();
}
);
}