1

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

}
johnwargo
  • 601
  • 2
  • 7
  • 22

2 Answers2

4

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.

Saeed Fekri
  • 1,067
  • 9
  • 12
  • This is a singleton and I'm trying to avoid having to initialize anything - using the constructor instead to do that for me. – johnwargo Jun 12 '21 at 16:34
  • you can remove all `static` modifiers and get object and use it. Eventually, this class sample is one of the optimal methods to use the shared_preferences. – Saeed Fekri Jun 13 '21 at 12:34
0

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());
}
cameron1024
  • 9,083
  • 2
  • 16
  • 36
  • I don't have a lot of experience with Dart classes, so that's why I ended up with the Factory approach. But I don't want have to manually initialize this class, I want to be able to use the constructor to do that. – johnwargo Jun 12 '21 at 16:37
  • Unfortunately, constructors *must* return an instance of the class (i.e. a constructor for `class Foo` must return a `Foo`, and can never return a `Future`). You will always need to use `await` somewhere. The method in my answer shows how to perform that wait when you create the instance, but a more normal pattern is to have asynchonous getters instead – cameron1024 Jun 13 '21 at 00:59