flutterdartsharedpreferencesflutter-sharedpreference

Cant Manage to use correctly SharedPreferences on my Main.dart on Flutter?


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

Example how to use Set for Shared Prefrences

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)

Future dyanamic

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);
}

Solution

  • What & why I made the changes:

    1. 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.

    2. 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.

    3. Changed the function signature in key_value_class.dart

    4. Changed the main function to pass a SharedPreferences object to the constructor of KeyValueStorageServiceImpl.

    5. 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())));
    }
    

    Alternative Approach using Singleton

    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! :)

    How the Singleton approach works:

    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 InheritedWidgets.