Keeping user logged in, in the app with setState() - flutter

I am trying to keep the user of my app logged in until they logout. This is an app that accesses an api for authentication. I want to keep the user logged in using the setState method and not the token authentication method. I am able to successfully login but when I close and open the app again, I have to re-login.
Below are the files with the related code using the setState function. I am unable to find my error.
main.dart:
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Splash and Token Authentication",
routes: <String, WidgetBuilder>{
"/HomeScreen": (BuildContext context) => HomeScreen(),
"/LoginScreen": (BuildContext context) => LoginScreen(),
},
//state variable is in loginScreen.dart file
home: ((state == loginState.LOGGED_IN)? HomeScreen():LoginScreen())
);
}
#override
void initState() {
super.initState();
}
}
loginScreen.dart:
import ...
enum loginState{ LOGGED_IN, LOGGED_OUT}
loginState state;
const URL = "http://www.google.com";
class LoginScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new LoginScreenState();
}
}
class LoginScreenState extends State<LoginScreen> {
final TextEditingController _userNameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
String _welcomeString = "";
Future launchURL(String url) async {
if(await canLaunch(url)) {
await launch(url, forceSafariVC: true, forceWebView: true);
} else {
showDialogSingleButton(context, "Unable to reach your website.", "Currently unable to reach the website $URL. Please try again at a later time.", "OK");
}
}
#override
void initState() {
super.initState();
_saveCurrentRoute("/LoginScreen");
}
_saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastScreenRoute', lastRoute);
}
}
#override
Widget build(BuildContext context) {
//LoginScreen UI
}
}
homeScreen.dart:
import ...
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
super.initState();
_saveLoginState();
_saveCurrentRoute("/HomeScreen");
}
_saveCurrentRoute(String lastRoute) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
await preferences.setString('LastScreenRoute', lastRoute);
}
Future<void> _saveLoginState() async{
setState(() {
state = loginState.LOGGED_IN;
});
}
#override
Widget build(BuildContext context) {
//HomeScreen UI
}

use onGenerateRoute inside your MaterialApp
Here you can set initialRoute, after log in change SharedPreferences to your loginScreen route. and then you can get route when app is started.
#override
Widget build(BuildContext context) {
String initialRoute = '/';
return MaterialApp(
theme: _themeData,
onGenerateRoute: (s) => _getCurrentRoute(s),
initialRoute: initialRoute,
);
}
_getCurrentRoute(RouteSettings settings) {
print(settings.arguments);
switch (settings.name) {
case '/':
return MaterialPageRoute(
builder: (context) => PageRouter(
child: LogInPage(),
isSearchBar: false,
));
case '/home':
return MaterialPageRoute(
builder: (context) => PageRouter(
child: Home(),
isSearchBar: true,
));
case '/plp_page':
return MaterialPageRoute(
builder: (context) => PageRouter(
child: PlpPage(),
isSearchBar: false,
));
case '/pdp_page':
return MaterialPageRoute(builder: (context) => PdpPage());
case '/cart_page':
return MaterialPageRoute(
builder: (context) => PageRouter(
child: CartPage(),
isSearchBar: false,
));
}
}

You are looking for the shared_preferences package.
You need to Save your Data into Shared Preference when user logs in, and when user log out you need to clear all the Shared Preference.
While When user opens up app again you need to check if the Shared Preference has data of user or not.
Sample Code:
//Initialise SharedPreferences
SharedPreferences prefs = await SharedPreferences.getInstance();
//set some value in it.
//In your case your user data like some id, email or anything
await prefs.setInt('key', value);
//Getting data from preference.
final someName = prefs.getInt('key')

Related

Provider on Firebase Auth Login State does not redirect to Home page after successful login. but restarting the app navigates to home

-This is the Login Firebase Function used
Simple Login Function from firebase that signs in first and checks for email verification, but when the snack bar for successful logins shows up still the widget do not rebuild as the logic in the main.dart file using provider state management
class FirebaseUserAuthentication {
final FirebaseAuth _auth;
FirebaseUserAuthentication(this._auth);
//State Management
Stream<User?> get authState => _auth.authStateChanges();
User get user => _auth.currentUser!;
Future<void> signIn(
{required String email,
required String password,
required BuildContext context}) async {
try {
await _auth.signInWithEmailAndPassword(email: email.toString().trim(), password: password.toString().trim());
if (_auth.currentUser!.emailVerified == false) {
await sendEmailVerificaiton(context);
}
showSnackBar(context, "Logged in Successfully");
} on FirebaseAuthException catch (e) {
if (e.code != null) {
showSnackBar(context, e.message!);
}
}
}}
-This is the main.dart class wrapper using provider class
The AuthWrapper class checks the user state using provider and if logged in then redirects to Homepage but for me, it never re-directs. It does prints the "Logged in state" command but never actually navigates directly. I am a beginner in this, so confused why on restarting the app this gets me directly to logged in page but not like this. I can make it work via Navigator but i guess that would be a bad approach.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<FirebaseUserAuthentication>(create: (_) => FirebaseUserAuthentication(FirebaseAuth.instance)),
StreamProvider(create: (context) => context.read<FirebaseUserAuthentication>().authState, initialData: null, ),
],
child: MaterialApp(
title: "My App",
home: AuthWrapper(),
routes: {
'/splash': (context) => SplashScreen(),
'/welcome': (context) => WelcomePage(),
'/login': (context) => LoginPage(),
'/register': (context) => RegisterPage(),
'/home' : (context) => HomePage(),
'/marketplace' : (context) => MarketPlacePage(),
},
debugShowCheckedModeBanner: false,
),
);
}
}
class AuthWrapper extends StatelessWidget {
const AuthWrapper({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User?>();
if(firebaseUser!=null)
{
print("Logged in State");
return HomePage();
}
print("Logged out State");
return WelcomePage();
}
}

Flutter login control in splash screen

I want to make a small login application. When entering the application, I want to inquire whether the user has a token code or not on the splash screen. How can do this? thank you for help.
main.dart file
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SplashScreen(),
);
}
}
My splash screen.
I want to know if the user has a token or not
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
loginControl();
}
// ignore: missing_return
Future<bool> loginControl() async {
bool status = AuthController.isLoginUser() as bool;
print(status);
if (status) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => HomeScreen()));
} else {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => LoginScreen()));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('welcome my app'),
),
);
}
}
my auth controller like this;
class AuthController {
static Future<bool> isLoginUser() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
String token = sharedPreferences.getString("token");
if (token == null) {
return false;
} else {
return true;
}
}
}
Your isLoginUser is actually returning a Future<bool> means that it returns a Future that will later resolve to a bool value.
So, when you use it like this in your loginControl,
bool status = AuthController.isLoginUser() as bool;
AuthController.isLoginUser() return Future<bool> and it can't be directly converted to a bool using as bool.
Instead you should await that Future to resolve, like this.
bool status = await AuthController.isLoginUser(); // This will work.
Now, your code will pause at this line, until it gets a return value from isLoginUser and then resume to next line with status being an actual bool value. i.e., true or false.

How to check if user is SignedIn in Flutter Firebase

Hy here everyone. I am new to flutter and i want to check if User is SignedIn. If so the user navigate to HomeScreen else SplashScreen.
Here is my main.dart
void main() async{
runApp(MyApp());
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Instant Tasker',
theme: theme(),
initialRoute: SplashScreen.routeName,
routes: routes,
);
}
}
Here is Splash Screen
class SplashScreen extends StatefulWidget {
static String routeName = "/splash";
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
startTime() async {
var _duration = new Duration(seconds: 2);
return new Timer(_duration, navigationPage);
}
void navigationPage() {
var auth = FirebaseAuth.instance;
// ignore: deprecated_member_use
auth.onAuthStateChanged.listen((user) {
if (user != null) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => MainScreen()),
(Route<dynamic> route) => false);
} else {}
});
}
#override
void initState() {
super.initState();
startTime();
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Scaffold(
body: Body()
);
}
}
However i achieved to check user at splash screen but it stays at splash screen to check user then move to HomeScreen which doesn't seems to be good.
Or can anybody suggest how to show CircularProgressIndicator instead of Splash Screen body when it is checking for user
You can achieve it using StreamProvder
Implementation
Steps
Create a CustomUser Data model.
class CustomUser {
final String userId;
CustomUser({this.userId});
}
Create a class named FirebaseAuthService and create a stream to listen to Firebase AuthStateChanges
import 'package:firebase_auth/firebase_auth.dart';
class FirebaseAuthService {
final FirebaseAuth auth = FirebaseAuth.instance;
// create user obj based on firebase user
CustomUser _userFromFirebaseUser(User user) {
return user != null ? CustomUser(userId: user.uid) : null;
}
// auth change user stream
//Required stream
Stream<CustomUser> get user {
return auth.authStateChanges().map(_userFromFirebaseUser);
}
}
}
Add a StreamProvider on top of the widget tree where you want to check for the AuthState.
void main() async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<CustomUser>.value(
value: FirebaseAuthService().user,
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Instant Tasker',
theme: theme(),
initialRoute: SplashScreen.routeName,
routes: routes,
)
);
}
}
Create a Wrapper and return SplashScreen or HomeScreen based on AuthState.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
#override
Widget build(BuildContext context) {
final user = Provider.of<CustomUser>(context);
if (user == null) {
return SplashScreen();
}
return HomeScreen();
}
}
Now you can use final user = Provider.of<CustomUser>(context);
in the widget tree to check if the user is null.
https://www.youtube.com/watch?v=z05m8nlPRxk&list=PL4cUxeGkcC9j--TKIdkb3ISfRbJeJYQwC&index=3

Using FutureBuilder in main.dart

Below code always show OnboardingScreen a little time (maybe miliseconds), after that display MyHomePage. I am sure that you all understand what i try to do. I am using FutureBuilder to check getString method has data. Whats my fault ? Or any other best way for this ?
saveString() async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('firstOpen', '1');
}
getString() method always return string.
getString() async {
final prefs = await SharedPreferences.getInstance();
String txt = prefs.getString('firstOpen');
return txt;
}
main.dart
home: new FutureBuilder(
future: getString(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MyHomePage();
} else {
return OnboardingScreen();
}
})
Usually I'm using another route, rather than FutureBuilder. Because futurebuilder every hot reload will reset the futureBuilder.
There always will be some delay before the data loads, so you need to show something before the data will load.
Snapshot.hasData is showing only the return data of the resolved future.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: SplashScreen(),
);
}
}
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
const isOnboardingFinished = 'isOnboardingFinished';
class _SplashScreenState extends State<SplashScreen> {
Timer timer;
bool isLoading = true;
#override
void initState() {
_checkIfFirstOpen();
super.initState();
}
Future<void> _checkIfFirstOpen() async {
final prefs = await SharedPreferences.getInstance();
var hasOpened = prefs.getBool(isOnboardingFinished) ?? false;
if (hasOpened) {
_changePage();
} else {
setState(() {
isLoading = false;
});
}
}
_changePage() {
Navigator.of(context).pushReplacement(
// this is route builder without any animation
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => HomePage(),
),
);
}
#override
Widget build(BuildContext context) {
return isLoading ? Container() : OnBoarding();
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(child: Text('homePage'));
}
}
class OnBoarding extends StatelessWidget {
Future<void> handleClose(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
prefs.setBool(isOnboardingFinished, true);
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => HomePage(),
),
);
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: RaisedButton(
onPressed: () => handleClose(context),
child: Text('finish on bording and never show again'),
),
),
);
}
}
From the FutureBuilder class documentation:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
So you need to create a new Stateful widget to store this Future's as a State. With this state you can check which page to show. As suggested, you can start the future in the initState method:
class FirstPage extends StatefulWidget {
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
final Future<String> storedFuture;
#override
void initState() {
super.initState();
storedFuture = getString();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: storedFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
return MyHomePage();
} else {
return OnboardingScreen();
}
});
}
}
So in your home property you can call it FirstPage:
home: FirstPage(),
Your mistake was calling getString() from within the build method, which would restart the async call everytime the screen gets rebuilt.

Splashscreen can not navigate to the redirected page after seconds

I'm trying to create login with session using sharedpreferences and combine it with splashscreen but not going well, please kindly help..
Here is my code,
class SplashPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
Future cekSession() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
bool session = (preferences.getBool("session") ?? false);
if (session == true) {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => MainPage()));
} else {
preferences.setBool("session", true);
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => Login()));
}
}
return Scaffold(
body: new SplashScreen(
seconds: 3,
// I got error around here
navigateAfterSeconds: cekSession(),
title: new Text('Welcome !'),
image: new Image.asset("assets/image.png"),
backgroundColor: Colors.white,
styleTextUnderTheLoader: new TextStyle(),
photoSize: 100.0,
loaderColor: Colors.blue),
);
}
}
error in the terminal said,
error in the device,
You can copy paste run full code below
You can check session in main
navigateAfterSeconds need widget
code snippet
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences preferences = await SharedPreferences.getInstance();
bool session = (preferences.getBool("session") ?? false);
...
navigateAfterSeconds: initScreen == "Login" ? Login() : MainPage(),
working demo
full code
import 'package:flutter/material.dart';
import 'package:splashscreen/splashscreen.dart';
import 'package:shared_preferences/shared_preferences.dart';
String initScreen;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences preferences = await SharedPreferences.getInstance();
bool session = (preferences.getBool("session") ?? false);
if (session == true) {
initScreen = "MainPage";
} else {
preferences.setBool("session", true);
initScreen = "Login";
}
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SplashScreen(
seconds: 3,
navigateAfterSeconds: initScreen == "Login" ? Login() : MainPage(),
title: Text(
'Welcome In SplashScreen',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0),
),
image: Image.network('https://i.imgur.com/TyCSG9A.png'),
backgroundColor: Colors.white,
styleTextUnderTheLoader: TextStyle(),
photoSize: 100.0,
onClick: () => print("Flutter Egypt"),
loaderColor: Colors.red),
);
}
}
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text("Login");
}
}
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text("Main Page");
}
}
Because navigateAfterSeconds does not receive Future, try to pass a widget instead. Take a StateFul Widget. You need to declare a bool isLoggiedIn need to evaluate your checkSession result in initState before moving to another screen.
bool isLoggiedIn;
#override
void initState() {
super.initState();
checkSession();
}
void checkSession(){
setState{(
isLoggiedIn= your value from sharedpref
)}
}
then on behalf of isLoggiedIn value you need to pass widget like this,
if (session == true) {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => MainPage()));
} else {
preferences.setBool("session", true);
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => Login()));
}