How to load data stored in SharedPreference in flutter - flutter

I have a code for getting current logged in username and save it to a shared preference. The issue am facing is that whenever a user logs in for the first time, the username is never displayed, but when I do ahot reload on the app, the username is displayed on the screen . How can I have it in such a way the username is loaded on the first load without doing a hot reload.
How am getting the username on SharedPreference
/// Gets the current and prior accounts.
Future<dynamic> handleGetAccount() async { // <-- Replace dynamic with type of currentAccount
final result = await msal.getAccount();
if (result.currentAccount != null) {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.setString("username", result.currentAccount.username);
//print(result.currentAccount.username);
return result.currentAccount;
} else {
print('no account found');
return null;
}
}
My navigation to NavScreen ->redirects to Home screen
/// Updates the signed in state
refreshSignedInStatus() async {
bool loggedIn = await msal.getSignedIn();
if (loggedIn) {
isSignedIn = loggedIn;
if(isSignedIn) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => NavScreen(
),
),
);
}
// Remaining code for navigation
}
}
how I am getting the username to show on home screen and show the username
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
HomeState createState() => new HomeState();
}
class HomeState extends State<Home> {
final TrackingScrollController _trackingScrollController =
TrackingScrollController();
String username = "";
#override
void initState() {
getName();
}
Future<String> getName() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
username = prefs.getString("username");
return username;
}

Because getName() is a async method, you should call setState((){}) after username got.
void getName() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
username = prefs.getString("username");
setState((){});
}

Related

How to use sharedpreferences to save users roles and navigate to a specific page depending on role in Flutter

I'm working on app that have user logins (Admin login and user login). First i make a user part and it works, the account keep logged even when the app restart. and then when i have to separate the users (admin and user) i got some problem. I don't know how to code the shared preferences, this is the code when i make a user part
preference_helper.dart
import 'package:shared_preferences/shared_preferences.dart';
class PreferencesHelper {
final Future<SharedPreferences> sharedPreferences;
const PreferencesHelper({required this.sharedPreferences});
static const String login = 'LOGIN';
void setIsLogin(bool value) async {
final prefs = await sharedPreferences;
prefs.setBool(login, value);
}
Future<bool> get isLogin async {
final prefs = await sharedPreferences;
return prefs.getBool(login) ?? false;
}
}
i use the provider like this
preference_notifier.dart
class PreferencesNotifier extends ChangeNotifier {
PreferencesHelper preferencesHelper;
PreferencesNotifier({required this.preferencesHelper}) {
_getIsLogin();
}
bool _isLogin = false;
bool get isLogin => _isLogin;
void _getIsLogin() async {
_isLogin = await preferencesHelper.isLogin;
notifyListeners();
debugPrint(_isLogin ? 'isLogin true' : 'isLogin false');
}
void setIsLogin(bool value) async {
preferencesHelper.setIsLogin(value);
_getIsLogin();
}
}
i want to use shared preferences to save the user roles and navigate to specific page. So if the user's log in it will go to the UserHomePage and if the admin log in it will go to the AdminHomePage. My backend is firebase firestore.
this is part of sign page (when click register button)
MaterialButton(
color: primaryColor,
textTheme: ButtonTextTheme.primary,
height: 40,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
onPressed: () async {
setState(() {
_isLoading = true;
});
try {
final navigator = Navigator.of(context);
final email = _emailController.text;
final password = _passwordController.text;
const role = "user";
await _auth
.createUserWithEmailAndPassword(
email: email,
password: password,
)
.then((value) => {postDetailsToFirestore(email, role)});
navigator.pop();
} catch (err) {
final snackBar = SnackBar(content: Text(err.toString()));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} finally {
setState(() {
_isLoading = false;
});
}
},
child: const Text('Signup'),
),
postDetailsToFirestore(String email, String role) async {
FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
var user = _auth.currentUser;
CollectionReference ref = firebaseFirestore.collection('users');
ref.doc(user!.uid).set({'email': _emailController.text, 'role': role});
}
this is the login page (when click the login button)
MaterialButton(
color: primaryColor,
textTheme: ButtonTextTheme.primary,
height: 40,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
onPressed: () async {
setState(() {
_isLoading = true;
});
try {
final navigator = Navigator.of(context);
final email = _emailController.text;
final password = _passwordController.text;
await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
route();
value.setIsLogin(true);
navigator.pushReplacementNamed(HomePage.routeName);
} catch (err) {
final snackBar = SnackBar(content: Text(err.toString()));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} finally {
setState(() {
_isLoading = false;
});
}
},
child: const Text('Login'),
),
i want to navigate the navigator to specific user role
this is the route() function
void route() {
User? user = FirebaseAuth.instance.currentUser;
FirebaseFirestore.instance.collection('users').doc(user!.uid).get().then(
(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
if (documentSnapshot.get('role') == "user") {
Navigator.pushNamed(context, UserHomePage.routeName);
} else {
Navigator.pushNamed(context, AdminHomePage.routeName);
}
} else {
debugPrint('Document does not exist on the database');
}
},
);
}
and this is the main.dart at runApp()
runApp(
await preferencesHelper.isLogin
? const MyApp(
pageRouteName: HomePage.routeName,
)
: const MyApp(
pageRouteName: LoginPage.routeName,
),
);
I really need to know how am i supposed to do because this is for my exam. I'm sorry if my english is bad, i'm barely use English to talk. Thank you
that code that i share is what i tried to make sharedpreferences but it just for 1 user, i dont know how to separate user (admin and user)
First of all, you need to use architecture to separate the UI from logic and in your architect, you have to create a layer to handle basic requests of the local database and then create a class for implementing basic commands of the database, then you can create a separated storage layer for each of entities that you have.
the abstract basic commands class is like this :
abstract class LocalStorage {
Future<void> write(final String key, final dynamic json);
dynamic read<S>(final String key);
void remove(final String key);
void removeAll();
}
and for implementation :
class StorageService implements LocalStorage {
StorageService() {
_init();
}
late GetStorage storage;
void _init() {
storage = GetStorage();
}
#override
Future<void> write(final String key, final dynamic value) async {
await storage.write(key, convert.jsonEncode(value));
}
#override
dynamic read<S>(final String key) {
final value = storage.read(key);
if (value == null) return;
return convert.jsonDecode(value.toString());
}
#override
void remove(final String key) {
GetStorage().remove(key);
}
#override
void removeAll() {
GetStorage.Remove(key1);
GetStorage.Remove(key2);
...
}
}
and for Usage for each entity:
class UserStorage {
final LocalStorage _storage;
Future<void> SaveUser(User usr) async {
await _storage.write(userKey, usr);
}
}
I have used GetX to handle local storage for read and write but you can replace your preferred shared preference library.

SharedPreference data in TextWidget

This is a login, that catch user data and write in the other pages, like his name, etc
I set sharedPreference here:
Future<bool> login() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
SharedPreferences nome = await SharedPreferences.getInstance();
var email = _emailController.text;
var senha = _senhaController.text;
var auth = 'Basic ' + base64Encode(utf8.encode('$email:$senha'));
var url = Uri.parse("http://177.70.102.109:3005/autenticacao");
var resposta = await http.get(
url,
headers: (<String, String>{'authorization': auth}),
);
// List campos = [];
if (resposta.statusCode == 200) {
await sharedPreferences.setString(
'token', "Token ${jsonDecode(resposta.body)['token']}");
await nome.setString(
'nome', "${jsonDecode(resposta.body)['result'][0]['nome']}");
print(nome);
return true;
} else {
return false;
}
}
And i want to receive and pass the 'nome' to a TextWidget in another class.
In the other page you can write something like that:
class ExamplePage extends StatefulWidget {
const ExamplePage({Key? key}) : super(key: key);
#override
State<ExamplePage> createState() => _ExamplePageState();
}
class _ExamplePageState extends State<ExamplePage> {
final _controller = TextEditingController();
#override
void initState() {
initNome();
super.initState();
}
Future<void> initNome() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
String _nome = sharedPreferences.getString("nome", "");
_controller.text = _nome;
}
#override
Widget build(BuildContext context) {
return Text(_controller.text)
}
}
To read the value in some other widget you can use
getString https://pub.dev/documentation/shared_preferences/latest/shared_preferences/SharedPreferences/getString.html
Implementation would be similar to this:
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
Text(sharedPreferences.getString("nome");
See this post for example:
Flutter Shared Preference in Text Widget

Change bool in initState flutter

I have a page with this code:
class _HomeScreenState extends State<HomeScreen> {
bool isFirstLoading = true;
#override
void initState() {
super.initState();
if (isFirstLoading) {
getInfo();
setState(() {
isFirstLoading = false;
});
} else {
getInfoFromSharedPref();
}
}
Future<http.Response> getInfo() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
Loader.show(context,
isAppbarOverlay: true,
isBottomBarOverlay: true,
progressIndicator: CircularProgressIndicator());
var url = kLinkAPI + "/getInfo";
var response =
await http.post(url, headers: {"Content-Type": "application/json"});
var resObj = jsonDecode(response.body);
if (response != null) {
setState(() {
if (resObj.length > 0) {
address = resObj[0]['address'];
countryInfo = resObj[0]['country_info'];
phone = resObj[0]['phone'];
latitude = resObj[0]['latitude'];
longitude = resObj[0]['longitude'];
isFirstLoading = false;
prefs.setString('address', address);
prefs.setString('countryInfo', countryInfo);
prefs.setString('phone', phone);
prefs.setString('latitude', latitude);
prefs.setString('longitude', longitude);
}
});
}
Loader.hide();
}
void getInfoFromSharedPref() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
address = prefs.getString('address');
countryInfo = prefs.getString('countryInfo');
phone = prefs.getString('phone');
latitude = prefs.getString('latitude');
longitude = prefs.getString('longitude');
});
}
}
I would like to make sure that the first time I enter the page, the isFirstLoading variable is set to false and then calls the getInfo function with the http call while if it is false it takes from the shared preferences.
isFirstLoading is now always true
how could I solve?
I think you're overcomplicating your code. Let me know if this solves your issue.:
class _HomeScreenState extends State<HomeScreen> {
SharedPreferences prefs;
#override
void initState() {
super.initState();
getInfo();
}
// ...
}
Now, the first time this widget is inserted into the tree:
initState() will be called once.
Therefore, getInfo() will be called. getInfo() will make the http call and update the prefs variable using setState, which you have already done.
Whenever the widget is reloaded, the prefs variable will not be lost since it is a stateful widget.
Next, if you would like to save the preference settings locally instead of making an http call every time the user opens the app, you should handle that inside of getInfo() itself. Something like this:
getInfo() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool("isFirstLoading") == false) {
// setState to update prefs variable
} else {
// make http call
// save prefs (optional)
// setState to update prefs variable
}
}
If I undestand correctly, you are trying to only call the getInfo method on the first load, and the getInfoFromSharedPref all the other time.
My suggestion is to save the isFirstLoading bool as a preference like so:
class _HomeScreenState extends State<HomeScreen> {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isFirstLoading = prefs.getBool("isFirstLoading") ?? true;
#override
void initState() async {
super.initState();
if (isFirstLoading) {
await getInfo();
await prefs.setBool("isFirstLoading", false);
isFirstLoading = false;
} else {
getInfoFromSharedPref();
}
}
Future<http.Response> getInfo() async {
// …
}
void getInfoFromSharedPref() async {
// …
}
}

How to correctly save the value in sharedPreferences? - Flutter

Where am I going wrong?
I have login with google to get the token and send it to graphgl, this token is saved (it was meant to be) in sharedpreferences, but it is not saving, I have the following action (mobx).
#action
Future loginWithGoogle() async {
user = await _authRepository.getGoogleLogin();
final idToken = await user.getIdToken();
print('Bearer ${idToken.token}');
sharedPreferenceService.setToken('Bearer ${idToken.token}');
}
Services shared.
class SharedPreferenceService {
SharedPreferences _prefs;
Future<bool> getSharedPreferencesInstance() async {
_prefs = await SharedPreferences.getInstance().catchError((e) {
print("shared prefrences error : $e");
return false;
});
return true;
}
Future setToken(String token) async {
await _prefs.setString('token', token);
}
Future clearToken() async {
await _prefs.clear();
}
Future<String> get token async => _prefs.getString('token');
}
SharedPreferenceService sharedPreferenceService = SharedPreferenceService();
Action login in view.
#action
Future loginWithGoogle() async {
try {
loading = true;
await auth.loginWithGoogle();
Modular.to.pushReplacementNamed('/index');
} catch (e) {
loading = false;
}
}
The login happens normal but it accuses error when it goes to index, informing that it received null the getString("token").
I/flutter ( 3198): ClientException: Unhandled Failure NoSuchMethodError: The method 'getString' was called on null.
I/flutter ( 3198): Receiver: null
I/flutter ( 3198): Tried calling: getString("token")
This token string is not being saved.
Sorry for bad english
Just copied your code and made some changes just check:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
SharedPreferenceService sharedPreferenceService = SharedPreferenceService();
#override
void initState() {
super.initState();
loginWithGoogle();
getSharedValues();
}
getSharedValues() async{
bool value = await sharedPreferenceService.getSharedPreferencesInstance();
if(value)
print(await sharedPreferenceService.token);
}
loginWithGoogle() async {
// this is the where you get your bearer, but time being I have taken sample bearer
String token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJZb3VuaXNaYXJnYXIiLCJlbWFpbCI6InlvdW5pc0BiYXh0dXJlLmNvbSIsImp0aSI6IjlhNjc2OTVlLTBiZmEtNDdmMy04ZTVlLWVhYWMzY2VmNmRlOSIsIklkIjoiMSIsIkVtYWlsIjoieW91bmlzQGJheHR1cmUuY29tIiwiZXhwIjoxNTgzODQ2ODU0LCJpc3MiOiJQYWNpZmljIFByaW50aW5nIiwiYXVkIjoiUGFjaWZpYyBQcmludGluZyJ9.CKxBwAB7YeOKJRmoCg4_JAhJKHP2qXb7KJXPysqmbAs';
bool value = await sharedPreferenceService.getSharedPreferencesInstance();
if (value == true) {
sharedPreferenceService.setToken('Bearer $token');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(home: Scaffold(body: Center(child: Text('sample'))));
}
}
class SharedPreferenceService {
SharedPreferences _prefs;
Future<bool> getSharedPreferencesInstance() async {
_prefs = await SharedPreferences.getInstance().catchError((e) {
print("shared prefrences error : $e");
return false;
});
return true;
}
Future setToken(String token) async {
await _prefs.setString('token', token);
}
Future clearToken() async {
await _prefs.clear();
}
Future<String> get token async => _prefs.getString('token');
}
Thank you very much, I made the correction in the action.
#action
Future loginWithGoogle() async {
user = await _authRepository.getGoogleLogin();
final idToken = await user.getIdToken();
print('Bearer ${idToken.token}');
bool value = await sharedPreferenceService.getSharedPreferencesInstance();
if (value == true) {
sharedPreferenceService.setToken('Bearer ${idToken.token}');
}
}

Flutter: How to use SharedPreferences synchronously?

I am using Shared Preferences in my Flutter app and what I would like to do is store SharedPreferences as a field on startup and then use it synchronously in the app. However I'm not sure if I'm not missing anything.
What I want to achieve is instead of:
method1() async {
SharedPreferences sp = await SharedPreferences.getInstance();
return sp.getString('someKey');
}
to
SharedPreferences sp;
//I would probably pass SharedPreferences in constructor, but the idea is the same
someInitMethod() async {
sp = await SharedPreferences.getInstance();
}
method1() {
return sp.getString('someKey');
}
method2() {
return sp.getString('someKey2');
}
method3() {
return sp.getString('someKey3');
}
In that way I would achieve synchronous access to sharedPrefs. Is it bad solution?
EDIT:
What is worth mentioning is that getInstance method will only check for instance and if there is any than it returns it, so as I see it, is that async is only needed to initialize instance. And both set and get methods are sync anyway.
static Future<SharedPreferences> getInstance() async {
if (_instance == null) {
final Map<String, Object> fromSystem =
await _kChannel.invokeMethod('getAll');
assert(fromSystem != null);
// Strip the flutter. prefix from the returned preferences.
final Map<String, Object> preferencesMap = <String, Object>{};
for (String key in fromSystem.keys) {
assert(key.startsWith(_prefix));
preferencesMap[key.substring(_prefix.length)] = fromSystem[key];
}
_instance = new SharedPreferences._(preferencesMap);
}
return _instance;
}
I use the same approach as the original poster suggests i.e. I have a global variable (actually a static field in a class that I use for all such variables) which I initialise to the shared preferences something like this:
in globals.dart:
class App {
static SharedPreferences localStorage;
static Future init() async {
localStorage = await SharedPreferences.getInstance();
}
}
in main.dart:
void main() {
start();
}
Async.Future start() async {
await App.init();
localStorage.set('userName','Bob');
print('User name is: ${localStorage.get('userName)'}'); //prints 'Bob'
}
The above worked fine but I found that if I tried to use App.localStorage from another dart file e.g. settings.dart it would not work because App.localStorage was null but I could not understand how it had become null.
Turns out the problem was that the import statement in settings.dart was import 'package:<packagename>/src/globals.dart'; when it should have been import 'globals.dart;.
#iBob101 's answer is good, but still, you have to wait before you use the SharedPreferences for the first time.
The whole point is NOT to await for your SharedPreferences and be sure that it will always be NOT NULL.
Since you'll have to wait anyway let's do it in the main() method:
class App {
static SharedPreferences localStorage;
static Future init() async {
localStorage = await SharedPreferences.getInstance();
}
}
And the main method:
void main() async{
await SharedPref.initSharedPref();
runApp(MyApp());
}
the line await SharedPref.initSharedPref(); takes ~100ms to execute. This is the only drawback as far as I can see.
But you definitely know that in every place in the app your sharedPreferenes instance in NOT NULL and ready for accessing it:
String s = App.localStorage.getString(PREF_MY_STRING_VALUE);
I think it's worthwhile
The cleanest way is to retrieve SharedPreferences in main method and pass it to MyApp as a dependency:
void main() async {
// Takes ~50ms to get in iOS Simulator.
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
runApp(MyApp(sharedPreferences: sharedPreferences));
}
class MyApp extends StatefulWidget {
final SharedPreferences sharedPreferences;
const MyApp({Key key, this.sharedPreferences})
: assert(sharedPreferences != null),
super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
// You can access shared preferences via widget.sharedPreferences
return ...
}
I made a simple way to using this PrefUtil class:
import 'package:shared_preferences/shared_preferences.dart';
class PrefUtil {
static late final SharedPreferences preferences;
static bool _init = false;
static Future init() async {
if (_init) return;
preferences = await SharedPreferences.getInstance();
_init = true;
return preferences;
}
static setValue(String key, Object value) {
switch (value.runtimeType) {
case String:
preferences.setString(key, value as String);
break;
case bool:
preferences.setBool(key, value as bool);
break;
case int:
preferences.setInt(key, value as int);
break;
default:
}
}
static Object getValue(String key, Object defaultValue) {
switch (defaultValue.runtimeType) {
case String:
return preferences.getString(key) ?? "";
case bool:
return preferences.getBool(key) ?? false;
case int:
return preferences.getInt(key) ?? 0;
default:
return defaultValue;
}
}
}
In main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
PrefUtil.init();
.....
Save it like:
PrefUtil.setValue("isLogin", true);
Get the value like:
PrefUtil.getValue("isLogin", false) as bool
By this, it will initialize only once and get it where ever you need.
You can use FutureBuilder to render the loading screen while waiting for SharedPreferences to be intialized for the first time in a singleton-like class. After that, you can access it synchronously inside the children.
local_storage.dart
class LocalStorage {
static late final SharedPreferences instance;
static bool _init = false;
static Future init() async {
if (_init) return;
instance = await SharedPreferences.getInstance();
_init = true;
return instance;
}
}
app_page.dart
final Future _storageFuture = LocalStorage.init();
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _storageFuture,
builder: (context, snapshot) {
Widget child;
if (snapshot.connectionState == ConnectionState.done) {
child = MyPage();
} else if (snapshot.hasError) {
child = Text('Error: ${snapshot.error}');
} else {
child = Text('Loading...');
}
return Scaffold(
body: Center(child: child),
);
},
);
}
my_page.dart
return Text(LocalStorage.instance.getString(kUserToken) ?? 'Empty');
call shared prefs on startup of a stateful main app (we call ours a initState() override of a StatefulWidget after super.initState())
after shared prefs inits, set the value to a field on main (ex: String _someKey)
inject this field into any child component
You can the call setState() on _someKey at you leisure and it will persist to children injected with your field