How to use shared preferences to keep user logged in flutter? - flutter

I want to keep the user logged in after the user successfully logsin in flutter.
I am using a REST API to retrieve the user name and password of the user. But I want to save those details so that the user can stay logged in. My current situation is i can successfully log the user in but when i restart the app i have to login again so i need to save the details of the user in a shared preference so that the user can stay logged for the entire session until logout.But i am unable to do that so please help me with it.
Thanks in advance
This is the code i have for my login page.
I have removed the UI contents which should be inside the listview as those are not that relevant.
Login.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:restaurant_app/globalVar.dart';
import 'package:restaurant_app/homescreen.dart';
import 'package:restaurant_app/models/auth.dart';
import 'package:restaurant_app/signup.dart';
import 'package:http/http.dart' as http;
import 'package:restaurant_app/utils/authutils.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SignIn extends StatefulWidget {
SignIn({ Key key, this.post }): super(key: key);
#override
_SignInState createState() => _SignInState();
}
class _SignInState extends State<SignIn> with SingleTickerProviderStateMixin
{
TabController controller;
TextEditingController _email = new TextEditingController();
TextEditingController _password = new TextEditingController();
bool loading;
final GlobalKey < ScaffoldState > _scaffoldKey = new GlobalKey<ScaffoldState>
();
#override
void initState() {
// TODO: implement initState
super.initState();
_fetchSessionAndNavigate();
controller = new TabController(length: 2, vsync: this);
loading = false;
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
controller.dispose();
setState(() {
loading = false;
});
_email.dispose();
_password.dispose();
}
final GlobalKey < FormState > _formKey = GlobalKey<FormState>();
bool _autoValidate = false;
_login(username, password) async {
setState(() {
loading = true;
});
var body = json.encode({
"username": username,
"password": password,
});
Map < String, String > headers = {
'Content-type': 'application/json',
'Accept': 'application/json',
};
await http
.post("${GlobalVar.Ip}/wp-json/jwt-auth/v1/token",
body: body, headers: headers)
.then((response) {
var body = json.decode(response.body);
//var response1;
if (response.statusCode == 200) {
// TODO: you need to store body['token'] to use in some authentication
loading = false;
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext ctx) => HomePage()));
} else {
// TODO: alert message
final snackBar = SnackBar(
content: Text(body['message'].toString().trim()),
);
_scaffoldKey.currentState.showSnackBar(snackBar);
}
setState(() {
loading = false;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomPadding: false,
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/art.png'),
fit: BoxFit.fill,
colorFilter: ColorFilter.mode(
Colors.white12.withOpacity(0.2), BlendMode.dstATop),
),
),
child: ListView();
}

You can navigate to the Login page if the user details are saved in the storage else to the Home page with the below code
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
var email = prefs.getString('email');
print(email);
runApp(MaterialApp(home: email == null ? Login() : Home()));
}
Save the required user details after the successful login
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () async {
//after the login REST api call && response code ==200
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('email', 'useremail#gmail.com');
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext ctx) => Home()));
},
child: Text('Login'),
),
),
);
}
}
clear the details on logout
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: RaisedButton(
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove('email');
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext ctx) => Login()));
},
child: Text('Logout'),
),
),
);
}
}
Hope it helps!

Make sure WidgetFlutterBinding.ensureInitialized() is the first line of main()
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
bool login = prefs.getBool("login");
print("login:" + login.toString());
runApp(MaterialApp(home: login == null ? LoginPage(title: 'My App') : HomePage()));
}
class LoginPage extends StatelessWidget { ...

The above answers using SharedPreferences works (make sure you have WidgetsFlutterBinding.ensureInitiazed(); as your first line of main), but it will give you a null on re-start, ie, if you remove the app from recent and re-open it again, it will not re-direct you to the Home or Profile Page. I solved this issue by giving write external storage permission to your app because the shared preferences need to write the data somewhere in your device or emulator.
Just add the write and read external storage permissions in your Android Manifest file and you can use permission_handler plugin for flutter from pub.dev to get the required permissions from user at runtime when the app is opened for the first time and then Shared Preferences won't give you null.

Use user sessions instead. Check out Consession. The package adds user session support in Flutter and is easy to use.
// Store value to session
await Consession().set("token", myJWTToken);
// Retrieve item from session
dynamic token = await Consession().get("token");

Concession is now deprecated in favour of flutter_session. We can now use flutter_session to manage user sessions seamlessly.
//Write values to the session:
await FlutterSession().set("token", myJWTToken);
//Read values from the session:
dynamic token = await FlutterSession().get("token");

Related

How to use a variable within a method elsewhere in Flutter?

I know this is a stupid question, but I'm asking as a newbie to flutter.
I created a getData() method to call Firebase's User data and display it on the app. And to call it, result.data() is saved as a variable name of resultData.
But as you know I can't use Text('user name: $resultData'). How do I solve this? It's a difficult problem for me, since I don't have any programming basics. thank you.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:shipda/screens/login/login_screen.dart';
import 'package:get/get.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _authentication = FirebaseAuth.instance;
User? loggedUser;
final firestore = FirebaseFirestore.instance;
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
final resultData = result.data();
}
#override
void initState() {
super.initState();
getCurrentUser();
getData();
}
void getCurrentUser(){
try {
final user = _authentication.currentUser;
if (user != null) {
loggedUser = user;
print(loggedUser!.email);
}
} catch (e){
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: [
Text('Home Screen'),
IconButton(
onPressed: () {
FirebaseAuth.instance.signOut();
Get.to(()=>LoginScreen());
},
icon: Icon(Icons.exit_to_app),
),
IconButton(
onPressed: () {
Get.to(() => LoginScreen());
},
icon: Icon(Icons.login),
),
Text('UserInfo'),
Text('user name: ')
],
),
),
);
}
}
What you are referring to is called state.
It is a complex topic and you will have to start studying it to correctly develop any web based app.
Anyway, as for your situation, you should have resultData be one of the attributes of the _HomeScreenState class.
Then change resultData in a setState method, like this:
setState(() {
resultData = result.data();
});
Then, in the Text widget, you can actually do something like:
Text("My data: " + resultData.ToString())
Instead of ToString of course, use anything you need to actually access the data.
By writing
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
final resultData = result.data();
}
you make resultData only local to the function getData(). You should declare it outside. Also you need to put it in a setState to make it rebuild the screen after loading. I don't know what type it is, but if it's a String for example you could write
String? resultData;
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
setState(() {
resultData = result.data();
});
}
Then you can use Text('user name: $resultData') for example

SharedPreference does not read local data in Flutter

In a Flutter project, on clicking the "SignUp" button, I need 2 things,
Saving data to cloud
Saving data to local storage.
I user SharedPreferece to save the data and to retrieve it. The problem is, the data I saved into local storage is available immediately after I Sign Up, but if I hot reload the emulator, the data shows null!
The function by which I saved the data to both cloud and local storage:
Future <void> _saveDataToFirestore(User? currentUser) async{
await FirebaseFirestore.instance.collection("sellers").doc(currentUser!.uid).set({
"sellerUID": currentUser.uid,
"sellerName": _fullNameController.text.trim(),
"sellerAvatarUrl": sellerImageUrl,
"sellerEmail": currentUser.email,
"phone": _phoneNumberController.text.trim(),
"address": completeAddress,
"status": "approved",
"earnings": 0.0,
"lat": position!.latitude,
"lng": position!.longitude
});
// save 3 data locally
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
await sharedPreferences.setString("uid", currentUser.uid);
await sharedPreferences.setString("email", currentUser.email.toString()); // do not take from controllers, because it will not be null if sign up fail
await sharedPreferences.setString("name", _fullNameController.text);
await sharedPreferences.setString("image", sellerImageUrl);
print("${currentUser.uid},${currentUser.email.toString()}, ${_fullNameController.text}, ${sellerImageUrl} ");}
I initialized SharedPreference in main.dart
Future <void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences.setMockInitialValues({});
await SharedPreferences.getInstance();
await Firebase.initializeApp();
runApp(const MyApp());
}
The home_screen where I needed the local storage data
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:food_fancy_chef/authentication/auth_screen.dart';
import 'package:food_fancy_chef/authentication/login.dart';
import 'package:food_fancy_chef/authentication/register.dart';
import 'package:food_fancy_chef/global/global.dart';
import 'package:shared_preferences/shared_preferences.dart';
class HomeScreen extends StatefulWidget {
static const routeName = "home_screen";
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String sharedName = "Null value";
Future<void> _getIntFromSharedPref()async{
final pref = await SharedPreferences.getInstance();
final startupName = pref.getString("name");
if(startupName == null){
sharedName = "no name";
} else{
setState(() {
sharedName = startupName;
});
}
}
#override
void initState() {
_getIntFromSharedPref();
super.initState();
}
#override
void didChangeDependencies() {
_getIntFromSharedPref();
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.cyan,
Colors.amber
],
begin: FractionalOffset(0.0, 0.0),
end: FractionalOffset(1.0, 1.6),
stops: [0.0,1.0],
tileMode: TileMode.mirror
)
),
),
title: Text(
sharedName // I need "name" from local storage
// sharedPreferences!.getString("name")! == null? "null value":sharedPreferences!.getString("name")!
)
),
body: Column(
children: [
Center(
child: ElevatedButton(
onPressed: (){
setState(() {
firebaseAuth.signOut();
Navigator.pushReplacement(context, MaterialPageRoute(builder: (c)=> LoginScreen()));
});
},
child: Text("Log Out"),
),
),
Center(
child: ElevatedButton(
onPressed: (){
setState(() {
firebaseAuth.signOut();
Navigator.pushReplacement(context, MaterialPageRoute(builder: (c)=> AuthScreen()));
});
},
child: Text("signout"),
),
),
],
),
);
}
}
What I have tried:
I have tried to get the data directly without any init() method, it returns null:
sharedPreferences!.getString("name")! == null? "null value":sharedPreferences!.getString("name")!
I have declared the variable first and assigned the value via a function run at init(), code is below.
I tried the same process above but with didChangeDependencies() method.
I used both init() and didChangeDependencies()
I deleted the emulator and reinstalled it.
I also saved SharePreferences() in a global.dart file, so that, I can access them anywhere in the project.
Try this
Future<void> _getIntFromSharedPref() async{
SharedPreferences? pref = await SharedPreferences.getInstance();
String startupName = pref!.getString("name") ?? 'no name';
setState(() {});
}
Try removing SharedPreferences.setMockInitialValues({}); from main.dart.
When hot restart the widget tree re built it means only this
Widget build(BuildContext context) method triggered. initState is called only once for a widget and didChangeDependencies may be called multiple times per widget lifecycle. So initializing some thing on didChangeDependencies you have to be careful. Calling like this will resolve your issue whille hot restart
#override
Widget build(BuildContext context) {
_getIntFromSharedPref();
return Scaffold(
);
}

InitState() not finishing before Build method is called

I'm having a problem in my home_screen.dart file. I
have a method called pullUserData() that is called in initState() but before pullUserData() is completely finished, the build method in home_screen.dart begins. This results in null values (auth and friendsList) being sent to NavDrawer() and FriendsFeed() in the build method of home_screen.dart.
How can I prevent NavDrawer() and FriendsFeed() from being called in the build method before initState() is completely finished? Should I use FutureBuilder?
User_data.dart handles gets the values for auth and friendsList.
home_screen.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:mood/components/friends_feed.dart';
import 'package:mood/components/nav_drawer.dart';
import 'package:mood/services/user_data.dart';
class LandingScreen extends StatefulWidget {
static const String id = 'landing_screen';
#override
_LandingScreenState createState() => _LandingScreenState();
}
class _LandingScreenState extends State<LandingScreen> {
FirebaseAuth auth;
List<dynamic> friendsList;
#override
void initState() {
super.initState();
pullUserData();
}
Future<void> pullUserData() async {
UserData userData = UserData();
await userData.getUserData();
auth = userData.auth;
friendsList = userData.friendsList;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Mood'),
centerTitle: true,
),
drawer: NavDrawer(auth),
body: FriendsFeed(friendsList),
);
}
}
user_data.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class UserData {
final FirebaseAuth _auth = FirebaseAuth.instance;
User _currentUser;
String _currentUserUID;
List<dynamic> _friendsList;
FirebaseAuth get auth => _auth;
User get currentUser => _currentUser;
String get currentUserUID => _currentUserUID;
List<dynamic> get friendsList => _friendsList;
Future<void> getUserData() async {
getCurrentUser();
getCurrentUserUID();
await getFriendsList();
}
void getCurrentUser() {
_currentUser = _auth.currentUser;
}
void getCurrentUserUID() {
_currentUserUID = _auth.currentUser.uid;
}
Future<void> getFriendsList() async {
await FirebaseFirestore.instance
.collection("Users")
.doc(_currentUserUID)
.get()
.then((value) {
_friendsList = value.data()["friends"];
});
}
}
There are couple of problems in your code but it will work.
Firstly, if you want to set value of your friendslist during build, you have to use setState like this:
setState(() {
friendsList = userData.friendsList;
});
And if you want to wait until pullUserData() finish, you are looking for something called splash screen, but in your problem, you are waiting only for body to be build so I will recommend to use progress indicator in your scaffold like this:
return Scaffold(
appBar: Bars.menuAppBar('Devices'),
drawer: DrawerMenu(),
backgroundColor: ColorThemes.backgroundColor,
body: _loading
? Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(
Colors.blue), //choose your own color
))
: FriendsFeed(friendsList)
);
You can see that I used _loading variable. You will have to define it before your initState() like
bool _loading = true;
Then after you set your friendsList inside of your pullUserData() function, change _loading to false inside of setState just like this:
setState(() {
friendsList = userData.friendsList;
_loading = false;
});

How to get data from Firestore when homepage loads?

I am trying to make a todo list app with Flutter. When the homepage loads, the data does not appear on program
I tried QuerySnapshot, '.then', sleep and more but unfortunately none of them worked
I tried to debug and find where is the problem. It looks like while the program loads, the data from Firestore is not ready yet that is why it does not appear on the homepage.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:io';
class home extends StatefulWidget {
static const String id = 'home';
#override
_homeState createState() => _homeState();
}
class _homeState extends State<home> {
String userUid;
FirebaseUser loggedInUser;
final _fireStore = Firestore.instance;
final _auth = FirebaseAuth.instance;
List<Container> messageWidgets = [];
void getCurrentUser() async {
final user = await _auth.currentUser();
if (user != null) {
loggedInUser = user;
userUid = loggedInUser.uid;
}
}
getTasks() {
print('///////////////Get tasks///////////////');
return _fireStore
.document('Userss')
.collection('$userUid/Tasks')
.getDocuments();
}
bool boool = false;
var tasks;
#override
void initState() {
getCurrentUser();
getTasks().then((QuerySnapshot docs) {
if (docs.documents.isNotEmpty) {
boool = true;
tasks = docs.documents[0].data;
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Center(
child: Column(
children: <Widget>[
boool
? Column(
children: <Widget>[
Text(tasks['Task']),
Text(tasks['Category']),
],
)
: Container(
child: Text('No DATA'),
),
],
)));
}
I expect the data appears when homepage loads but it does not appear
My quick guess is that you need to use setState(), since the data is loaded from Firestore asynchronously.
So something like:
getTasks() {
print('///////////////Get tasks///////////////');
_fireStore
.document('Userss')
.collection('$userUid/Tasks')
.getDocuments().then((QuerySnapshot docs) {
if (docs.documents.isNotEmpty) {
boool = true;
setState(() {
tasks = docs.documents[0].data;
});
}
});
}

OnSharedPreferenceChangeListener for Flutter

In Android, you can do the following to listen to shared preference change
SharedPreferences.OnSharedPreferenceChangeListener spChanged = new
SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
// your stuff here
}
};
Is it possible to do this using flutter? I have read through the official flutter shared_preference and this features seems not yet implemented.
Is there any other library or ways to achieve the above without diving into native code. Thanks.
You can easily "listen" to SharedPreferences using a package like flutter_riverpod.
Initialize sharedPreferences
SharedPreferences? sharedPreferences;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
sharedPreferences = await SharedPreferences.getInstance();
runApp(const ProviderScope(child: MyApp()));
}
Create the stateProvider
import 'package:hooks_riverpod/hooks_riverpod.dart';
final keepOnTopProvider = StateProvider<bool>((ref) {
return sharedPreferences?.getBool('on_top') ?? true;
});
Update your UI when something changes
class SettingsView extends ConsumerWidget {
const SettingsView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
bool onTop = ref.watch(keepOnTopProvider);
return Scaffold(
appBar: AppBar(title: const Text('Settings'), centerTitle: false),
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 12),
children: [
SwitchListTile(
title: const Text('Keep on top'),
value: onTop,
onChanged: (value) async {
sharedPreferences?.setBool('on_top', value);
ref.read(keepOnTopProvider.notifier).state = value;
await windowManager.setAlwaysOnTop(value);
},
),
],
),
);
}
}
As a work around, add the following codes to your main():
void funTimerMain() async {
// here check any changes to SharedPreferences, sqflite, Global Variables etc...
if (bolAnythingChanged) {
// do something
// 'refresh' any page you want (below line using Redux as example)
GlobalVariables.storeHome.dispatch(Actions.Increment);
}
// recall this timer every x milliseconds
new Future.delayed(new Duration(milliseconds: 1000), () async {
funTimerMain();
});
}
// call the timer for the first time
funTimerMain();