how to initialize sharedPreferences flutter? - flutter

In order to Authenticate to Api, I created AuthProvider class to authenticate, after getting accessToken from the Api,I stored the token using sharedPreferences,I wanted to check if the user loggedIn or not, so I initialized sharedPreferences to return a boolean value if it contains the token... as shown in the code bellow:
class AuthProvider with ChangeNotifier{
bool _isLoading = false;
bool get isLoading => _isLoading;
User user = User();
late SharedPreferences prefs ;
String token = '';
Map<String, String> _mainHeaders = {
'Content-Type': 'application/json; charset=UTF-8',
};
void updateHeader(String token) {
_mainHeaders = {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer $token',
};
}
Future<ResponseModel> login(String username, String password) async {
print("Getting token");
//print(authRepo.getUserToken().toString());
_isLoading = true;
notifyListeners();
http.Response response = await dologin(username, password);
// print('${response.body.}');
var answer = User.fromJson(jsonDecode(response.body));
print('the result is : ${answer.token}');
print('level 1: ${response.body.toString()}');
late ResponseModel responseModel;
print(response.statusCode);
if (response.statusCode == 200) {
//authRepo.saveUserToken((response.body["token"]).toString());
print("Backend token: ${response.body.toString()}");
responseModel = ResponseModel(true, answer.token!);
// SharedPreferences prefs = await SharedPreferences.getInstance();
// prefs.setString(ApiConstants.kEY_ACCESS_TOKEN, answer.token!);
storeToken(answer.token!);
} else {
responseModel = ResponseModel(false, response.reasonPhrase!);
}
_isLoading = false;
notifyListeners();
return responseModel;
}
bool userLoggedIn() =>
prefs.containsKey(ApiConstants.kEY_ACCESS_TOKEN) ? true : false;
// bool clearSharedData() => authRepo.clearSharedData();
void storeToken(String token) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString(ApiConstants.kEY_ACCESS_TOKEN, token);
print('the token is here : ${token}');
}
...
in main.dart I created a consumer to check if the user is loggedIn or not :
child: MaterialApp(
home: Consumer<AuthProvider>(
builder: ((context, auth, _){
return auth.userLoggedIn() ? const HomePage() : const loginScreen();
})
),
...
when I hit run I got the following error in my console:
The following LateError was thrown building Consumer(dirty, dependencies: [_InheritedProviderScope<AuthProvider?>]):
LateInitializationError: Field 'prefs' has not been initialized.
The relevant error-causing widget was
Consumer.
PS : I tried to initialize sharedPreferences : SharedPreferences? prefs;
the error would be:
Null check operator used on a null value.
...

You're getting the error because you've marked your prefs as late but you haven't initialised it (as written in the error message). Just get rid of the line late SharedPreferences prefs; at the top and ensure that you initialise it each time you need it within the method. I.e. call await SharedPreferences.getInstance(); each time.

Future<bool> userLoggedIn() async{
prefs = await SharedPreferences.getInstance();
prefs.containsKey(ApiConstants.kEY_ACCESS_TOKEN) ? true : false;
}
Change userLoggedIn getter to this.

You can call a function from init state and check the sharedprefrences in it like
late SharedPreferences prefs;
//initialize shared prefrences then access it
void initState() {
super.initState();
sharedData();
}
void sharedData() async {
prefs = await SharedPreferences.getInstance();
}
void storeToken(String token) async {
prefs = await SharedPreferences.getInstance();
prefs.setString(ApiConstants.kEY_ACCESS_TOKEN, token);
print('the token is here : ${token}');
}

Related

This expression has a type of 'void' so its value can't be used - Flutter

import 'package:demo_app/services/api.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthProvider extends ChangeNotifier{
bool isAuthenticated = false;
late String token;
late ApiService apiService;
AuthProvider() {
init();
}
Future<void> init() async {
token = await getToken();
if (token.isNotEmpty) {
isAuthenticated = true;
}
apiService = ApiService(token);
notifyListeners();
}
Future<void> register(String name, String email, String password, String passwordConfirm, String deviceName) async{
token = await apiService.register(name, email, password, passwordConfirm, deviceName);
isAuthenticated = true;
setToken();
notifyListeners();
}
Future<void> logIn(String email, String password, String deviceName) async{
token = await apiService.login(email, password, deviceName);
isAuthenticated = true;
setToken();
notifyListeners();
}
Future<void> logOut() async{
token = '';
isAuthenticated = false;
setToken();
notifyListeners();
}
Future<void> setToken() async{
final pref = await SharedPreferences.getInstance();
pref.setString('token', token);
}
Future<void> getToken() async{
final pref = await SharedPreferences.getInstance();
pref.getString('token') ?? '';
}
}
token = await getToken();
gives this error
This expression has a type of 'void' so its value can't be used.
Try checking to see if you're using the correct API; there might be a function or call that returns void you didn't expect. Also check type parameters and variables which might also be void.
Any clue on solving this issue?
Try the following code:
Future<void> init() async {
token = await getToken();
if (token.isNotEmpty) {
isAuthenticated = true;
}
apiService = ApiService(token);
notifyListeners();
}
Future<String> getToken() async {
final pref = await SharedPreferences.getInstance();
final token = pref.getString("token") ?? "";
return token;
}

An optimize way for tryAutoLogin function in flutter?

I want to create a function for auto login like Facebook in flutter but don't know the best way to do it.
My function for login and auto login, I used SharedPreferences plugin for store data.
SignIn function:
Future<void> signIn(String userName, String pass) async {
final url = Uri.parse('MyAPI_login');// sorry it for privacy
debugPrint("$userName / $pass");
try {
var respone = await http.post(url, body: {
'user_name': userName,
'password': pass,
'platform': 'mobile',
'device_token': '',
});
final reponseData = jsonDecode(respone.body);
_userName = userName;
_token = reponseData['data']['accessToken'];
_expiryDate = DateTime.now().add(Duration(
seconds: int.parse(reponseData['data']['tokenExpireAt'].toString())));
_refreshToken = reponseData['data']['refreshToken'].toString();
_timerRefreshToken =
int.parse(reponseData['data']['refreshTokenExpireAt'].toString());
// debugPrint(
// '$_token \n $_expiryDate \n $_refreshToken \n $_timerRefreshToken');
notifyListeners();
final prefs = await SharedPreferences.getInstance();
final userData = json.encode({
'_userId': _userName.toString(),
'token': _token.toString(),
'expiryDate': _expiryDate!.toIso8601String(),
'refreshToken': _refreshToken,
'timerRefreshToken': _timerRefreshToken.toString(),
});
await prefs.setString('userData', userData);
} catch (error) {
throw Exception(error.toString());
}}
TryAutoLogin function:
Future<bool> tryAutoLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userData')) {
return false;
}
final extractedUserData = json
.decode(prefs.getString('userData').toString()) as Map<String, dynamic>;
final expiryDate =
DateTime.parse(extractedUserData['expiryDate'].toString());
if (expiryDate.isBefore(DateTime.now())) {
_token = extractedUserData['refreshToken'].toString();
_expiryDate = DateTime.now().add(
Duration(seconds: int.parse(extractedUserData['timerRefreshToken'])));
_refreshNewToken(extractedUserData['refreshToken'].toString());
}
return true;}
RefreshNewToken function:
Future<void> _refreshNewToken(String oldRefreshToken) async {
final url =
Uri.parse('MyAPI_refreshtoken');
var respone = await http.post(url, body: {'refreshToken': oldRefreshToken});
debugPrint(respone.body);}
My API for login response is like this:
{"data":{"tokenKey":"Authorization","tokenType":"Bearer","accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl9pZCI6ImE1YzkyMTQwLTA3Y2YtMTFlZC1hNDQ2LTYzY2YyNjNiZjllMiIsInVzZXJfaWQiOiJDODAzQ0I3RS1CQTcyLTQ4NjgtQjdEMC05NkRBOUNCREQyMTkiLCJ1c2VyX25hbWUiOiIxMDAyMCIsImZ1bGxfbmFtZSI6IkzDqiBUaOG7iyBMacOqbiIsImlzQWRtaW5pc3RyYXRvciI6MCwidXNlcl9jb21wYW5pZXMiOltdLCJpYXQiOjE2NTgyODIzOTMsImV4cCI6MTY1ODI4NTk5M30.3kMByfweUhzQM-4d5S0G7tUaC0e-nZLJF3_dbdV_7fM","tokenExpireAt":1658285940964,"refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl9pZCI6ImE1YzkyMTQwLTA3Y2YtMTFlZC1hNDQ2LTYzY2YyNjNiZjllMiIsInVzZXJfaWQiOiJDODAzQ0I3RS1CQTcyLTQ4NjgtQjdEMC05NkRBOUNCREQyMTkiLCJ1c2VyX25hbWUiOiIxMDAyMCIsImZ1bGxfbmFtZSI6IkzDqiBUaOG7iyBMacOqbiIsImlzQWRtaW5pc3RyYXRvciI6MCwidXNlcl9jb21wYW5pZXMiOltdLCJpYXQiOjE2NTgyODIzOTMsImV4cCI6MTY1ODM2ODc5M30.Bv7PZrnx9zDzwIuxNMppFxlwZlJEnthVjEYBKYl-aWM","refreshTokenExpireAt":1658368740964},"message":"Logged in successfully!","status":200,"errors":null}
Also, my API has a refresh token request, it returns like this:
{"data":{"tokenKey":"Authorization","tokenType":"Bearer","accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl9pZCI6ImE1ZjQyOGUwLTA3Y2YtMTFlZC1hNDQ2LTYzY2YyNjNiZjllMiIsInVzZXJfaWQiOiJDODAzQ0I3RS1CQTcyLTQ4NjgtQjdEMC05NkRBOUNCREQyMTkiLCJ1c2VyX25hbWUiOiIxMDAyMCIsImZ1bGxfbmFtZSI6IkzDqiBUaOG7iyBMacOqbiIsImlzQWRtaW5pc3RyYXRvciI6MCwidXNlcl9jb21wYW5pZXMiOltdLCJpYXQiOjE2NTgyODIzOTQsImV4cCI6MTY1ODI4NTk5NH0.wcyouoprMHFnRD4_oSpP9RSasxMBrktX6nZI2x2PQec","tokenExpireAt":1658285940242,"refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl9pZCI6ImE1ZjQyOGUwLTA3Y2YtMTFlZC1hNDQ2LTYzY2YyNjNiZjllMiIsInVzZXJfaWQiOiJDODAzQ0I3RS1CQTcyLTQ4NjgtQjdEMC05NkRBOUNCREQyMTkiLCJ1c2VyX25hbWUiOiIxMDAyMCIsImZ1bGxfbmFtZSI6IkzDqiBUaOG7iyBMacOqbiIsImlzQWRtaW5pc3RyYXRvciI6MCwidXNlcl9jb21wYW5pZXMiOltdLCJpYXQiOjE2NTgyODIzOTQsImV4cCI6MTY1ODM2ODc5NH0.y-8MP4M_1LzCwmqo_KQZGyQXkycrxdOLWz_fdqIPRyQ","refreshTokenExpireAt":1658368740242},"message":"Request successfully!","status":200,"errors":null}

Flutter shared preference code optimization suggestion?

I am using the shared_preferences package. https://pub.dev/packages/shared_preferences/example
In my repository class, for each function, I am doing this to get the instance.
SharedPreferences prefs = await SharedPreferences.getInstance();
class AuthenticationRepository {
Future<dynamic> logIn({required String email, required String password}) async {
SharedPreferences prefs = await SharedPreferences.getInstance(); <--------
....
prefs.clear();
prefs.setString('user', encodedUser);
}
Future<String> logOut() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); <---------
prefs.clear();
if(prefs.containsKey('user')){
return 'failed';
}else{
return 'cleared';
}
}
}
I am just wondering if this is initiating a new sharedPreference object or as the function implies, we are only getting the same instance?
Is there a better way to create the instance once, maybe as a class variable like below?
class AuthenticationRepository {
SharedPreferences prefs = await SharedPreferences.getInstance();
Future<dynamic> logIn({required String email, required String password}) async {
....
this.prefs.clear();
prefs.setString('user', encodedUser);
}
Future<String> logOut() async {
this.prefs.clear();
if(prefs.containsKey('user')){
return 'failed';
}else{
return 'cleared';
}
}
}
Please advice, thanks in advance :)
Yes, you can get the same instance. In the shared_preference.dart file, there is a static value _completer. Here is getInstance() function. You can see the if (_completer == null), and it immediately returns a value when the _completer had been initialized.
static Completer<SharedPreferences>? _completer;
...
static Future<SharedPreferences> getInstance() async {
if (_completer == null) {
final completer = Completer<SharedPreferences>();
try {
final Map<String, Object> preferencesMap =
await _getSharedPreferencesMap();
completer.complete(SharedPreferences._(preferencesMap));
} on Exception catch (e) {
// If there's an error, explicitly return the future with an error.
// then set the completer to null so we can retry.
completer.completeError(e);
final Future<SharedPreferences> sharedPrefsFuture = completer.future;
_completer = null;
return sharedPrefsFuture;
}
_completer = completer;
}
return _completer!.future;
}
I think it is a better way to use the getInstance() function not to create another class.

Flutter: What is the Correct approach to get value from Future?

I have a function that which returns user token, and saves it to shared preference, if token is present it saves it to SP. Another method awaits for the token and if token is present authenticates user
Here is my code
login(username, password) async {
final String url = "http://10.0.2.2:8080/api/auth/signin"; // iOS
final http.Response response = await http.post(
url,
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'username': username,
'password': password,
}),
);
LoginModel userSave = loginModelFromJson(response.body);
print(response.body);
final SharedPreferences prefs = await SharedPreferences.getInstance();
bool result = await prefs.setString('user', jsonEncode(userSave));
print(result);
}
This piece of code works as expected, but I'm having issue getting the token value from Future.
Case Scenario: User enters username and password and authenticates with server. Incase account exists a token is generated from server and sent to app and stored in shared prefs. token if available is picked from shared prefs and used to login to app, but the check of the token is done before it is generated and saved
Future<LoginModel> getUserInfo() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
Map<String, dynamic> userMap;
final String userStr = prefs.getString('user');
if (userStr != null) {
userMap = jsonDecode(userStr) as Map<String, dynamic>;
}
if (userMap != null) {
final LoginModel user = LoginModel.fromJson(userMap);
print(user);
return user;
}
return null;
}
the token gets called way before it is saved throwing an error of null
RoundedButton(
text: "LOGIN",
press: () async {
if (_formKey.currentState.validate()) {
progressDialog.show();
await login(
username,
password,
);
SharedPreferences prefs =
await SharedPreferences.getInstance();
String token = prefs.getString("accessToken");
getUserInfo();
// ignore: null_aware_in_condition
if (token == null) {
progressDialog.hide();
showAlertsDialog(context);
// ignore: null_aware_in_condition
} else {
progressDialog.hide();
showAlertzDialog(context);
}
}
},
),
I believe there is very small logical mistake, but unable to find it myself.
I believe you cannot directly call jsonEncode with a custom object like the way you do it in jsonEncode(userSave). Do print(jsonEncode(userSave)); to see if the value is properly being converted into a string.

Flutter - SharedPreference not saving value - Returning NULL all time

I have implemented SharedPreference in Flutter using the following dependency:
shared_preferences: ^0.5.12+4
My Preference class :
class Preference {
static Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
static saveLoginInfo(VerifyUserLoginResult verifyUserLoginResult) async {
SharedPreferences pref = await _prefs;
pref
.setString("user_login", jsonEncode(verifyUserLoginResult))
.then((value) => print('User Login Value Saved : $value'));
}
static Future<bool> isLoggedIn() async {
SharedPreferences pref = await _prefs;
print('is Logged In : ${pref.getString("user_login")}');
return pref.getString("user_login") != null;
}
}
My logs as follow:
I/flutter (28468): User Login Value Saved : true
I/flutter (28468): is Logged In null
I/flutter (28468): is Logged In: false
Can anyone help me to save the JSON in Preference? I also want to check whether JSON is saved or not to display another screen if already logged in.
Edit:
I have called the method to save String as below:
if (value.verifyUserLoginResult.mID == '1')
{
Preference.saveLoginInfo(value.verifyUserLoginResult),
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => Dashboard()))
}
Because you are not adding await before calling saveLoginInfo method. and sharedpreferences save method returns "future"
You need to add await before calling saveLoginInfo
your code should be :
await Preference.saveLoginInfo(value.verifyUserLoginResult),
How are you calling Preference.isLoggedIn()? Maybe you are not awaiting the result?
I have reproduced your code with a test case and evertyhing worked as expected.
Preferences.dart
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class Preference {
static Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
static saveLoginInfo(dynamic verifyUserLoginResult) async {
SharedPreferences pref = await _prefs;
pref
.setString("user_login", jsonEncode(verifyUserLoginResult))
.then((value) => print('User Login Value Saved : $value'));
}
static Future<bool> isLoggedIn() async {
SharedPreferences pref = await _prefs;
return pref.getString("user_login") != null;
}
}
preferences_test.dart
import 'package:flutter_apph/Preferences.dart';
import 'package:flutter_test/flutter_test.dart';
void main(){
test('sharedprefs',() async {
Map<String,Map<String,String>> value = {
"verifyUserLoginResult" : {
"mID" : "1"
}
};
if (value['verifyUserLoginResult']['mID'] == '1')
{
await Preference.saveLoginInfo(value['verifyUserLoginResult']);
expect(
await Preference.isLoggedIn(),
true);
}
});
}