How should I manage login page in flutter app - flutter

I am making an app which starts with a splash screen.
What I want is if the user signing for the first time it should redirect to overboarding screen.For that I have used shared preferences and stored a value to check if the user is new or not.And from overbearing it should go to login page after login it will check the user data exists or not. And in normal case after splash it should redirect to home screen.My code is working. But the problem is I don't know how to arrange it.
Main.dart -> (check logged in or not ) -> (if already logged in)Home.dart -> (else check first time login or not)-> (if first time login then)Onboarding Screen From there Login screen ->else Login Screen
If you need any more information just ask me
This is the code from main.dart
return SplashScreen.navigate(
name: 'assets/splash.flr',
next: (context) {
return AuthService().handleAuth();
},
startAnimation: 'Untitled',
until: () => Future.delayed(Duration(seconds: 4)),
backgroundColor: Colors.white,
);
This is AuthService().handleAuth() code
handleAuth() {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.hasData) {
Navigator.maybePop(context);
SharedPrefFunction().saveLoginPreference();
return CheckUser();
}
else{
return LoginScreen();}
});
}
This is the onboarding code
onTap: () {
Navigator.of(context)
.pushReplacementNamed(LoginScreen.loginRoute);
},
I want to go to AuthService().handleAuth() from onboarding.
How can I reach there from Onboarding screen or suggest me something better.

In your main.dart
bool loggedIn = false;
#override
void initState() {
super.initState();
isUserLoggedIn();
}
void isUserLoggedIn() async {
_loggedIn = await SharedPrefFunction().getLoginPreference()
setState(() => loggedIn = _loggedIn);
}
Widget build(BuildContext context) {
loggedIn ? LoginScreen() : SplashScreen()
}

Related

How to navigate to the Home Page after a successful Login using Flutter (Dart)

I have a Login Controller that does the action of loging in (with Google)
class LoginController extends GetxController {
final _googleSignin = GoogleSignIn();
var googleAccount = Rx<GoogleSignInAccount?>(null);
login() async {
googleAccount.value = await _googleSignin.signIn();
}
logout() async {
googleAccount.value = await _googleSignin.signOut();
}
}
Then I have a Log In UI that shows everything
final controller = Get.put(LoginController());
LoginIn({
super.key,
required String title,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sign-In Page')),
body: Center(
child: Obx(() {
if (controller.googleAccount.value == null) {
return loginButton(); //the Login button to log in with google
} else {
return seeProfile(); //after Login you are then able to see your profile
}
}),
));
}
And then ofcourse I have a HomePage
How do I go to the Home Page after a successful log in
I tried to use ChatGPT and I did watch a couple of Youtube Videos but their code is ofcourse different then my and that makes it hard to implement their solution
A cleaner method would be to use Stream builder and subscribe to the auth stream in the main.dart file. Based on the value, navigate the user.
'''
StreamBuilder<User?>(
initialData: null,
// stream: RepositoryProvider.of(context).authStream(),
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData &&
snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData) {
return HomePage();
}
return Container();
});
I think checking the example in the google_sign_in's package on pub.dev might help you achieving what you want.
In summary, I would suggest you to listen to changes to the user in the initState method and push a new page after signIn is successful:
#override
void initState() {
super.initState();
StreamSubscription<GoogleSignInAccount?> subscription = _googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount? account) {
if (account != null) {
//push new screen
Navigator.of(context).popAndPushNamed(...)
subscription.cancel()
}
});
}
I couldn't test this code, but it should give you a general idea on how to proceed. Again, check the link above for more information.

Go back to login when logged out from the drawer, no matter what

I need to redirect user to login page when he clicks on logout button from drawer (wherever he is). The problem is that when I click on the logout button, the screen remains the same.
According to this post: Flutter provider state management, logout concept
I have:
void main() async {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<Profile>(
create: (final BuildContext context) {
return Profile();
},
)
],
child: MyApp(),
),
);
}
MyApp:
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
initPlatformState();
}
/// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
if (!mounted) return;
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
initialRoute: '/',
navigatorKey: navigatorKey,
// ...
home: Consumer<Profile>(
builder: (context, profile, child){
return profile.isAuthenticated ? SplashScreen() : AuthScreen();
}
)
);
}
}
The part of the drawer where there is the logout button:
ListTile(
leading: Icon(Icons.logout),
title: Text(AppLocalizations.of(context)!.logout),
onTap: () async {
SharedPreferences preferences =
await SharedPreferences.getInstance();
await preferences.clear();
final Profile profile =
Provider.of<Profile>(context, listen: false);
profile.isAuthenticated = false;
}),
As I said, when I click on the logout button from the drawer, the user is correctly logged out, but the screen remains the same.
UPDATE
This is the profile class:
class Profile with ChangeNotifier {
bool _isAuthenticated = false;
bool get isAuthenticated {
return this._isAuthenticated;
}
set isAuthenticated(bool newVal) {
this._isAuthenticated = newVal;
this.notifyListeners();
}
}
I think you are using provider class incorrectly.
use your profile class like this.
class Profile with ChangeNotifier {
bool _isAuthenticated = true;
bool get getIsAuthenticated => _isAuthenticated;
set setIsAuthenticated(bool isAuthenticated) {
_isAuthenticated = isAuthenticated;
notifyListeners();//you must call this method to inform lisners
}
}
in set method call notifyListners();
in your listTile
replace profile.isAuthenticated = false to profile.isAuthenticated = false;
Always use getters and setters for best practice.
I hope this is what you were looking for.
Add Navigator.of(context).pushReplacementNamed("/routeName") in LogOut onTap() Section.
For more information : https://api.flutter.dev/flutter/widgets/Navigator/pushReplacementNamed.html
Make sure to have logout route set in MyApp file, and i'd edit logout button file as such:
ListTile(
leading: Icon(Icons.logout),
title: Text(AppLocalizations.of(context)!.logout),
onTap: () async {
SharedPreferences preferences =
await SharedPreferences.getInstance();
await preferences.clear();
final Profile profile =
Provider.of<Profile>(context, listen: false);
profile.isAuthenticated = false;
// add login file route here using Navigator.pushReplacementNamed() ;
}),
Navigator push named -> logout route?

authStateChanges only work if in debug mode

I have a flutter app and everything was fine until i want to release it.
I'm using firebase for auth.
I'm using:
firebase_core: ^0.7.0
firebase_auth: ^0.20.0
In debug mode or in release, my firebase auth login works fine. My problem is after that.
I have a decentralized 'listener' to firebaseAuth.authStateChanges. Here is where i control my app authentication. This is my buildSelf$ function in my auth repository(where i build the auth state listener):
ReplayConnectableStream<AuthState> buildSelf$() {
return Rx.concat([
Stream.value(AuthInit()),
firebaseAuth.authStateChanges().switchMap((firebaseUser) {
print('[AuthRepository][buildSelf][authStateChanges] firebaseUser');
print(firebaseUser);
if (firebaseUser == null) {
return Stream.value(Unauthenticated());
}
/* continue function */
return Authenticated(firebaseUser: firebaseUser);
})
]).publishReplay();
}
buildSelf$ is a method for my AuthRepository. And i initialize it on:
AuthRepository._() {
Firebase.initializeApp().then((app) {
_firebaseAuth = FirebaseAuth.instance;
state$ = buildSelf$();
state$.connect();
});
setPackageInfo();
}
static final AuthRepository instance = AuthRepository._();
All this code is inside my AuthRepository.
The problem is:
When i'm running my app in debug mode. Every thing works fine. I can login and my app (my navigator observer uses the auth repository state$) and i'm redirected to home page. [Here a printscreen from terminal in debug mode. success authStateChanges emit
But when i'm running im release mode, my login response shows in my terminal 'success' but my listener does not show state changes. Here a printscreen from terminal in release mode. authStateChanges emit only 1 time(when opening)
I'm realy lost about whats going on. I tried even to call this authStateChanges directly in my app.dart but still the same way(in release, only checking auth state once).
Solution:
After 3 days of headache, i finally found out my problem:
My app does not initialize firebase app on root (main). So i have to initialize it every time i need to use some firebase package.
I believe that every time one app was initialized, the others failed.
I move my firebase.initializeApp() to my main function and remove every other firebase initialize. Then, everything works fine.
This is my main:
void main() async {
/* ... main code ... */
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(App());
}
I did implement a project with the presented methods in release mode in my iPhone but wasn't able to reproduce the problem, the implementation I did to reproduce the project was:
class _TestFirebaseState extends State<TestFirebase> {
bool userLogado = false;
_loginLogout() {
if (FirebaseAuth.instance.currentUser == null) {
FirebaseAuth.instance.signInWithEmailAndPassword(
email: 'teste1#teste.com', password: '123456');
} else {
FirebaseAuth.instance.signOut();
}
}
#override
void initState() {
super.initState();
final stream = Rx.concat(
[
Stream.value('começou'),
FirebaseAuth.instance.authStateChanges().switchMap<String>(
(user) {
debugPrint('user: ' + user.toString());
if (user == null) {
return Stream.value('unauth');
} else {
return Stream.value('auth');
}
},
),
],
).publishReplay();
stream.listen((value) {
debugPrint('value: ' + value);
this.setState(() {
this.userLogado = value == 'auth';
});
});
stream.connect();
}
#override
Widget build(BuildContext context) {
String text = "sair";
if (!userLogado) {
text = "entrar";
}
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
child: Center(
child: RaisedButton(
onPressed: _loginLogout,
child: Text(text),
),
),
),
);
}
}
the only detail is that the main component that loads everything in the app has a future builder that awaits the firebase to init:
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder(
// Initialize FlutterFire:
future: Firebase.initializeApp(),
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return Scaffold(
body: Center(
child: Text(
'ocorreu um problema inicializando o aplicativo...',
),
),
);
}
// Once complete, show your application
if (snapshot.connectionState == ConnectionState.done) {
// return LoginPage();
// return VerifySignIn(homePage: Home());
return TestFirebase();
}
// Otherwise, show something whilst waiting for initialization to complete
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
),
debugShowCheckedModeBanner: false,
);
}
And another detail is that the firebase configuration is implemented following the flutter tutorial for the platform used.
So in order to evaluate if the app configuration is just fine, I would implement that button app inside the same project so you can evaluate it properly.

How to use shared preferences to have a one-time login in flutter?

I searched google/stackoverflow alot and tried but nothing worked. This is the flow i want:
User launches the app. Splash screen appears.
Login screen appears after splash screen. User logs in.
User kills(closes) the app.
When user relaunches the app, it should show the splash screen followed by the homepage, as user has already logged in once before.
User will only see login page if he/she logs out.
So far 1 and 2 works. But when user kills/closes the app and relaunches it again, instead of being directed to the home page, they are directed to the login page again.
The code for the splash screen:
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
startTimer();}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
height: 150,
width: 150,
child: new SvgPicture.asset(
'assets/logo.png'
),
),
),
);
}
void startTimer() {
Timer(Duration(seconds: 3), () {
navigateUser(); //It will redirect after 3 seconds
});
}
void navigateUser() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
var status = prefs.getBool('isLoggedIn');
print(status);
if (status == true) {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context) => HomePage());
} else {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context) => LoginScreen()));
}
}}
The code for the log out button:
void logoutUser() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs?.clear();
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (BuildContext context) => SplashScreen()),
ModalRoute.withName("/login"),
);
}
Sorry for the lengthy post, and really appreciate the help if someone could point out where i've gone wrong. Or if there's any other way to achieve a one-time login in flutter. Thanks!
I know my answer is late now. But, If you are using FirebaseAuth, this will automatically cache your login, logout log. So you will not need to store it to pref. Just nee to make additional step when you lauch screen to check if user's last status was login or log out by the following. And this information can be used to rediret to the desired screen.
Code:
Future<bool> checkIfAlreadySignedIn () async {
late bool _isAlreadySignedIn;
await FirebaseAuth.instance
.authStateChanges()
.listen((event) async {
if (event == null) {
print('Currentyl signed out');
_isAlreadySignedIn = false;
} else {
_isAlreadySignedIn = true;
}
});
return _isAlreadySignedIn;
}
Where do you set 'isLoggedIn' pref to true?

How to perform async calls inside streambuilder?

I have an flutter app, which uses FirebaseAuth for authentication and Firestore for storing data. To store user profile (name, photo etc), I've created a separate collection in my firestore database.
So, once a user registers he is redirected to a screen where he can add his profile data. This data is again stored as a document.
I want to implement the same checks when the app is starting:
So, I display a splash screen and in the backend it checks,
Is the user logged in? If yes, proceed, else redirect him to the terms and conditions page.
Does the collection have user profile? If yes, proceed, else redirect him to a page where he can add his photo etc.
I was able to accomplish point 1, but I am not able to do the 2nd check.
Here's the code:
class SplashScreen extends StatelessWidget {
static final String id = 'splash_screen';
final Firestore _firestore = Firestore.instance;
#override
Widget build(BuildContext context) {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
FirebaseUser user = snapshot.data;
if (user == null) {
return TermsAndConditions();
}
// Check if user profile has been created
return ChatsScreen();
} else {
return SplashScreenContent();
}
},
);
}
}
The commented line is where the code for checking user profile should go.
I tried the following:
_firestore.collection('users').where('id', isEqualTo: user.uid).snapshots().first.then( (value) {
if(value.documents.isEmpty) {
return ProfileScreen();
}
});
As I understand I cannot return the value to outer function from the callback. How can I achieve this
What you want to do is to show splash screen every time.
Then, inside splash screen, you can create the check.
call this function inside initState
void bootstrap() async {
var user = await FirebaseAuth.instance.currentUser();
if (user == null) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => TermsAndConditions()));
return;
}
DocumentSnapshot userDoc =
await Firestore.instance.collection("users").document(user.uid).get();
if (!userDoc.exists) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => ProfilePage()));
return;
}
Navigator.push(
context, MaterialPageRoute(builder: (context) => ChatsScreen()));
}
You can also add initial 2-3 seconds delay, else the splash screen can be too abruptly changed.
You can find many splash screen libraries in pub.dev that allow you to do this bootstraping.