Flutter Null Safe Config Class with shared_preferences - flutter

In flutter 1.x, I implemented a Config class using the Flutter shared_preferences package; the code looks like this:
import 'package:shared_preferences/shared_preferences.dart';
class Config {
static final Config _config = Config._internal();
factory Config() => _config;
final accessTokenKey = 'accessToken';
String _accessToken;
SharedPreferences prefs;
Config._internal() {
loadData();
}
void loadData() async {
prefs = await SharedPreferences.getInstance();
_accessToken = prefs.getString(accessTokenKey) ?? '';
}
String get accessToken {
return _accessToken;
}
set accessToken(String accessToken) {
_accessToken = accessToken;
_saveString(accessTokenKey, accessToken);
}
_saveString(String key, String value, {String printValue = ''}) {
String printVal = printValue.length > 0 ? printValue : value;
prefs.setString(key, value);
}
}
I’m creating a new project in Flutter 2.x and trying to use the same code, but due to changes associated with null safety I’m having some difficulty getting the updated code just right.
The updated documentation for the package says to initialize the _prefs object like this:
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Then create a local prefs object using:
final SharedPreferences prefs = await _prefs;
This is fine, but I don’t want to have to make every class method that uses shared_preferences async then recreate the variable. At the same time I can’t create it as a class variable without initializing it first. Can someone please show me a cleaner way to do this, or do I just have to redeclare it every time I use it?
Also, how do I initialize the config object in my other classes? In my 1.x code, I would just do this:
final Config config = new Config();
then start accessing the properties of the config object. How do I initialize it with all of the async code in the class now?
Here’s where the updated code is today:
import 'package:shared_preferences/shared_preferences.dart';
import '../models/device.dart';
class Config {
static final Config _config = Config._internal();
factory Config() => _config;
final accessTokenKey = 'accessToken';
String _accessToken = '';
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Config._internal() {
print('Config constructor');
loadData();
}
Future<void> loadData() async {
final SharedPreferences prefs = await _prefs;
_accessToken = prefs.getString(accessTokenKey) ?? '';
}
String get accessToken {
return _accessToken;
}
set accessToken(String accessToken) {
_accessToken = accessToken;
_saveString(accessTokenKey, accessToken);
}
_saveString(String key, String value, {String printValue = ''}) {
String printVal = printValue.length > 0 ? printValue : value;
print('Config: _saveString("$key", "$printVal")');
final SharedPreferences prefs = await _prefs;
prefs.setString(key, value);
}
}

You can get instance of SharedPreferences as static field in init method:
static SharedPreferences? _prefs; //or: static late SharedPreferences _prefs;
static init() async {
_prefs = await SharedPreferences.getInstance();
}
And call init() somewhere like in build() method of first widget run, for once.Now you can use _prefs everywhere as you want.
If I want to show you a complete class to use SharedPreferences, it looks like this:
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesRepository {
static SharedPreferences? _prefs;
static init() async {
_prefs = await SharedPreferences.getInstance();
}
static putInteger(String key, int value) {
if (_prefs != null) _prefs!.setInt(key, value);
}
static int getInteger(String key) {
return _prefs == null ? 0 : _prefs!.getInt(key) ?? 0;
}
static putString(String key, String value) {
if (_prefs != null) _prefs!.setString(key, value);
}
static String getString(String key) {
return _prefs == null ? 'DEFAULT_VALUE' : _prefs!.getString(key) ?? "";
}
static putBool(String key, bool value) {
if (_prefs != null) _prefs!.setBool(key, value);
}
static bool getBool(String key) {
return _prefs == null ? false : _prefs!.getBool(key) ?? false;
}
}
I hope this useful for you.

If you need to wait for some async work to finish before getting an instance of a class, consider using a static method (not a factory constructor, since constructors must always return the base type).
You can use late fields to allow them to be non-null before you initialize them:
class Config {
late String _accessToken;
String get accessToken => _accessToken;
Config._(); // private constructor to prevent accidental creation
static Future<Config> create() async {
final config = Config();
final preferences = await SharedPreferences.getInstance();
config._accessToken = await preferences.getString('<your key>');
return config;
}
}
If you want to make sure this is initialized before running your app, you can initialize it in your main() method before you call runApp() to give control to the Flutter framework:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); // make sure all plugins loaded etc.
final config = await Config.create();
print(config.accessToken);
runApp(MyApp());
}

Related

Flutter shared prefernce return NULL

I have next piece of flutter code, to get shared preference key-value
I do understand why _blueUriInit is always NULL
I assume that you are forgot to provide the value for that key before call to get its value, you need to first assign value to it first:
Future<bool> saveData(String key, dynamic value) async {
final prefs = await SharedPreferences.getInstance();
return prefs.setString(key, value);
}
and call it like this:
void initState() {
saveData('blueUri', 'test');
setState(() {
_blueUriInit = getValue('blueUri');
});
super.initState();
}
now next time you open your app, getValue should return you test.
you can create this function for setting value
static setUserID(String key, String value) async {
final SharedPreferences preferences = await SharedPreferences.getInstance();
preferences.setString(key, value);
}
Use case :
await SharedValue.setUserID("Email", "demo#gmail.com");
And For getting value from shared preference you can use this function
static Future<String?> getUserID(String key) async {
final SharedPreferences preferences = await SharedPreferences.getInstance();
return preferences.getString(key);
}
Use case :
userName = await SharedValue.getUserID("Email");
First you need to setString with key and value (name is key)
Future setValue() async {
final prefs = await SharedPreferences.getInstance();
prefs.setString("name", "Hitarth");
}
getString with key (here i took "name" as key)
Future getValue(String key) async {
final prefs = await SharedPreferences.getInstance();
String value = prefs.getString(key) ?? "NULL";
return value;
}
store in variable callin getValue
void initState() {
setState(() {
_blueUriInit = getValue("name");
});
super.initState();
}

Flutter, how to set and get list of values in shared preferences

I have SharedPreferencesHelper class where I stored simple data that I need.
I came across an issue. I need to store 3-4 strings in the list in shared preferences. How can I do the setter and getter for it?
SharedPreferencesHelper class:
class SharedPreferencesHelper {
static SharedPreferences? _preferences;
Future<void> init() async {
_preferences ??= await SharedPreferences.getInstance();
}
String get username => _preferences?.getString(keyUsername) ?? '';
set username(String value) {
_preferences?.setString(keyUsername, value);
}
// get aliases
List<String>? get aliases => _preferences?.getStringList(keyAliases);
// set aliases
}
You can do this to save list of string:
var pref = await SharedPreferences.getInstance();
pref.setStringList('someList',['1','2','3']);
and get it like this:
var result = pref.getStringList('someList');
if (result != null) {
print('result= $result'); //result= [1, 2, 3]
}
You can write the code like that :
class SharedPreferencesService {
SharedPreferencesServiceImpl(this.preferences);
final SharedPreferences preferences;
/// Static Variables
static const _userId = 'user_id';
#override
Future<String?> get userId async => preferences.getString(_userId);
#override
Future<void> setUserId(String value) async =>
await preferences.setString(_userId, value);
#override
Future<void> clear() async => await preferences.clear();
}
Moreover, you have to inject SharedPreferencesInstance in your getIt (recommended to use)
final getIt = GetIt.instance;
Future<void> setupExternals() async {
final prefs = await SharedPreferences.getInstance();
getIt.registerLazySingleton(() => prefs);
getIt.registerLazySingleton<SharedPreferencesService>(
getIt());
}

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: I want to create a global variant. After close the app, it can be saved for the next time I open the app again

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Global {
static SharedPreferences _prefs;
static String key;
static Future init() async {
WidgetsFlutterBinding.ensureInitialized();
_prefs = await SharedPreferences.getInstance();
var _key = _prefs.getString("key");
if (_key != null) {
key = _key;
}
}
static saveKey(value) => _prefs.setString("key", value);
}
Here I create a global class. And I set and get value by this way.
Global.key = value; //set
Global.key; //get
But after I close the app, the value is gone. Any suggestions?
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Global {
static SharedPreferences _prefs;
static String key;
String get init => key; // adding a getter
static Future init() async {
WidgetsFlutterBinding.ensureInitialized();
_prefs = await SharedPreferences.getInstance();
var _key = _prefs.getString("key");
if (_key != null) {
key = _key;
}
}
static saveKey(value) => _prefs.setString("key", value);
}
Global().init =value; or Global.init = value; //set
Global().init; //get Global.init; //get
Are you ever calling your Init function in your Main method?
void main() async {
await Global.init();
runApp(MyApp());
}
Assuming you're doing that, the other mistake here is how you're trying to store your value. Use the saveKey function you have in place that's what it's there for. Throw this on one of your pages.
ElevatedButton(
onPressed: () {
Global.saveKey('Test');
},
child: Text('Test'),
),
Your Global class was perfectly fine, its how you were trying to use it. Try this though, only change is that I included a null check so you don't have to create an extra variable that's not used. Also added a print statement that will print the stored value when you re-start the app. It all works fine on my end.
class Global {
static SharedPreferences _prefs;
static String key;
static Future init() async {
WidgetsFlutterBinding.ensureInitialized();
_prefs = await SharedPreferences.getInstance();
key = _prefs.getString('key') ?? ''; // null check here
debugPrint(key);
}
static saveKey(value) => _prefs.setString("key", value);
}

how to implement Shared_preferences in my own class elegantly on Flutter?

I created such class to store and update the api address locally. But it doesn't work well. How to add the Shared_preferences in the normal class instead of the flutter state widget? So it would make things clearly.
// The Server class
import 'package:shared_preferences/shared_preferences.dart';
class Server{
String _listUrl;
String _itemUrl;
static String _cache1;
static String _cache2;
static final Server _server = new Server._internal();
factory Server({String listUrl, String itemUrl}) {
_cache1 = listUrl;
_cache2 = itemUrl;
return _server;
}
Server._internal() {
read();
_listUrl=_cache1??"https://www.sjjg.uk./eat/food-items/";
_itemUrl=_cache2??"https://www.sjjg.uk/eat/recipe-details/";
}
String listUrl()=>_listUrl;
String itemUrl()=>_listUrl;
void update({String listUrl, String itemUrl}){
_listUrl = listUrl??_listUrl;
_itemUrl = itemUrl??_itemUrl;
save();
}
void read() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
_cache1=prefs.getString('_listUrl')??"https://www.sjjg.uk./eat/food-items/";
_cache2=prefs.getString('_itemUrl')??"https://www.sjjg.uk/eat/recipe-details/";
// print(_cache1);
// print(_cache1);
}
void save() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('_listUrl', _listUrl);
prefs.setString('_itemUrl', _itemUrl);
}
}
I found the problem.
I should not import 'package:flutter_test_app/server.dart';
Still difference between it with import 'server.dart';