I've been learning Flutter and learned this to improve usage with Pub.dev package Shared Preferences,
To sum up, it is to create a service that I can consume and work in a better way all around my app with SharedPreferences, it works perfectly on my Code for token
because I have my async function
So, the getMethod works perfectly using final token = await keyValueStorageService.getValue<String>('token')
(also async function above),
but I can't manage to use this code on my UI code, it gives me this instead (Because of the lack of an await function)
but as I read you can use it and initiate (shown below) on your main.dart, I've been trying to use a method getSharedPrefs()
on the main.dart
to implement and use it all around my app for example calling my getValue('user')
wherever I need to but haven't made it work
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Enviroment.initEnvirontment();
//! Key Value instance for main async
await KeyValueStorageServiceImpl().getSharedPrefs();
await initializeDateFormatting('es_ES', null);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((value) => runApp(ProviderScope(child: MyApp())));
}
My actual code is this
key_value_storage.dart
import 'key_value_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
class KeyValueStorageServiceImpl extends KeyValueStorageService {
Future<SharedPreferences> getSharedPrefs() async {
return await SharedPreferences.getInstance();
}
@override
Future<T?> getValue<T>(String key) async {
final prefs = await getSharedPrefs();
switch (T) {
case String:
return prefs.getString(key) as T?;
case int:
return prefs.getInt(key) as T?;
case double:
return prefs.getDouble(key) as T?;
case bool:
return prefs.getBool(key) as T?;
case List:
return prefs.getStringList(key) as T?;
default:
throw UnimplementedError(
'No implementation Get for ${T.runtimeType}');
}
}
@override
Future<bool> removeKey(String key) async {
final prefs = await getSharedPrefs();
return await prefs.remove(key);
}
@override
Future<void> setKeyValue<T>(String key, T value) async {
final prefs = await getSharedPrefs();
switch (T) {
case String:
await prefs.setString(key, value as String);
break;
case int:
await prefs.setInt(key, value as int);
break;
case double:
await prefs.setDouble(key, value as double);
break;
case bool:
await prefs.setBool(key, value as bool);
break;
case List:
await prefs.setStringList(key, value as List<String>);
break;
default:
throw UnimplementedError(
'No implementation Set for ${T.runtimeType}');
}
}
}
key_value_class.dart
abstract class KeyValueStorageService {
Future<void> setKeyValue<T>(String key, T value);
Future<T?> getValue<T>(String key);
Future<bool> removeKey(String key);
}
What & why I made the changes:
Store an instance of SharedPreferences
in the KeyValueStorageServiceImpl
class directly and initialize with the constructor. This makes sure that the once the main
function initializes SharedPreferences
once, there is no subsequent need of calling getInstance
again inside the getValue
function.
The get
functions of SharedPreferences
return the T? value directly and not Future<T?> value, hence there is no need for the getValue
function to return Future<T?>
, so I changed it's definition.
Changed the function signature in key_value_class.dart
Changed the main function to pass a SharedPreferences
object to the constructor of KeyValueStorageServiceImpl
.
Gave an alternative approach by making the KeyValueStorageServiceImpl
into a Singleton.
You can read my comments in the code if you like.
key_value_storage.dart
import 'key_value_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
class KeyValueStorageServiceImpl extends KeyValueStorageService {
/// The instance of shared preferences is declared once for every object you create of this type.
final SharedPreferences prefs;
/// Call the `SharedPreferences.getInstance();` function outside of this class in order to not need to return a
/// future from the `getValue` method.
KeyValueStorageServiceImpl(this.prefs);
/// The `get` operation returns the value itself, not a future.
@override
T? getValue<T>(String key) {
switch (T) {
case String:
return prefs.getString(key) as T?;
case int:
return prefs.getInt(key) as T?;
case double:
return prefs.getDouble(key) as T?;
case bool:
return prefs.getBool(key) as T?;
case List:
return prefs.getStringList(key) as T?;
default:
throw UnimplementedError(
'No implementation Get for ${T.runtimeType}');
}
}
@override
Future<bool> removeKey(String key) async {
return await prefs.remove(key);
}
@override
Future<void> setKeyValue<T>(String key, T value) async {
switch (T) {
case String:
await prefs.setString(key, value as String);
break;
case int:
await prefs.setInt(key, value as int);
break;
case double:
await prefs.setDouble(key, value as double);
break;
case bool:
await prefs.setBool(key, value as bool);
break;
case List:
await prefs.setStringList(key, value as List<String>);
break;
default:
throw UnimplementedError(
'No implementation Set for ${T.runtimeType}');
}
}
}
key_value_class.dart
abstract class KeyValueStorageService {
Future<void> setKeyValue<T>(String key, T value);
T? getValue<T>(String key);
Future<bool> removeKey(String key);
}
main()
function
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Enviroment.initEnvirontment();
//! Key Value instance for main async
var prefs = await SharedPreferences.getInstance();
// use this wherever you need to call `getValue`
var keyValueStorageService = KeyValueStorageServiceImpl(prefs);
await initializeDateFormatting('es_ES', null);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((value) => runApp(ProviderScope(child: MyApp())));
}
key_value_storage.dart
import 'key_value_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
class KeyValueStorageServiceImpl extends KeyValueStorageService {
/// The instance of shared preferences
final SharedPreferences prefs;
/// The single instance which will remain all through the app lifecycle
static KeyValueStorageServiceImpl? _instance;
/// Getter for the instance
static KeyValueStorageServiceImpl get instance => _instance!;
/// Set the instance value, if not already set, by creating an object
/// of `KeyValueStorageServiceImpl` type and passing the SharedPreferences.
static void setSharedPreferences(SharedPreferences prefs) {
_instance ??= KeyValueStorageServiceImpl._(prefs);
}
/// Private Constructor to prevent multiple instances of this class.
KeyValueStorageServiceImpl._(this.prefs);
/// The `get` operation returns the value itself, not a future.
@override
T? getValue<T>(String key) {
switch (T) {
case String:
return prefs.getString(key) as T?;
case int:
return prefs.getInt(key) as T?;
case double:
return prefs.getDouble(key) as T?;
case bool:
return prefs.getBool(key) as T?;
case List:
return prefs.getStringList(key) as T?;
default:
throw UnimplementedError(
'No implementation Get for ${T.runtimeType}');
}
}
@override
Future<bool> removeKey(String key) async {
return await prefs.remove(key);
}
@override
Future<void> setKeyValue<T>(String key, T value) async {
switch (T) {
case String:
await prefs.setString(key, value as String);
break;
case int:
await prefs.setInt(key, value as int);
break;
case double:
await prefs.setDouble(key, value as double);
break;
case bool:
await prefs.setBool(key, value as bool);
break;
case List:
await prefs.setStringList(key, value as List<String>);
break;
default:
throw UnimplementedError(
'No implementation Set for ${T.runtimeType}');
}
}
}
main()
function
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Enviroment.initEnvirontment();
//! Key Value instance for main async
var prefs = await SharedPreferences.getInstance();
KeyValueStorageServiceImpl.setSharedPreferences(prefs);
await initializeDateFormatting('es_ES', null);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((value) => runApp(ProviderScope(child: MyApp())));
}
Now wherever you need to get a value, you can just do the following
var value = KeyValueStorageServiceImpl.instance.getValue<Type>('key');
Hope this solves your issue! Happy Coding! :)
Basically, a Singleton class means that only one instance of that Class can be created, and hence it gets the name, Singleton. A Singleton class has only one static instance of itself, and as such, the static instance is available throughout the lifecycle of the app, from the app initializing to the app being closed. Thus, A Singleton enables us to declare global data and functions, which can be accessed from any class, as we don't need the BuildContext
like in the case of InheritedWidget
s.