Using SharedPreferences in Flutter effortlessly [UPDATED]

simonpham

Simon Pham

Posted on July 24, 2020

Using SharedPreferences in Flutter effortlessly [UPDATED]


Hi folks đź‘‹
I have release a new package easy_hive, which is a better and simpler approach for storing key-value data instead of using shared_preferences. Please have a look.
Thank you so much!

We all know that SharedPreferences is a key/value store where you can read and store data very easily. It’s being used in most apps nowadays.

In Flutter, there’s a package named [shared_preferences] https://pub.dev/packages/shared_preferences) that helps us
deal with key/value data. Its documentation gives us something like this:

_incrementCounter() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  int counter = (prefs.getInt('counter') ?? 0) + 1;
  print('Pressed $counter times.');
  await prefs.setInt('counter', counter);
}
Enter fullscreen mode Exit fullscreen mode

It is really inconvenient to get the SharedPreferences instance asynchronously every time we need it. To increase reusability and reduce boilerplate code, I have an approach to save the instance of SharedPreferences and create getters/setters for the preference keys to use them anywhere in the app.

“Talk is cheap**. **Show me the code.” ~ Linus Torvalds

I will assume that you have already [added the shared_preferences] https://pub.dev/packages/shared_preferences#-installing-tab-) package.

First, we need to define a class to store the SharedPreferences instance. Let’s name it SharedPrefs.

// utils/shared_prefs.dart
class SharedPrefs {
  static SharedPreferences _sharedPrefs;
}

final sharedPrefs = SharedPrefs();
Enter fullscreen mode Exit fullscreen mode

Easy, right?

Then, we will define an init() method to get the SharedPreference instance and store it to the _sharedPrefs variable in the SharedPrefs class.

// utils/shared_prefs.dart
class SharedPrefs {
  static SharedPreferences _sharedPrefs;
  init() async {
    if (_sharedPrefs == null) {
      _sharedPrefs = await SharedPreferences.getInstance();
    }
  }
}

final sharedPrefs = SharedPrefs();
Enter fullscreen mode Exit fullscreen mode

And call it in main() function.

// main.dart
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await sharedPrefs.init();
  runApp(
    MyApp(),
  );
}
Enter fullscreen mode Exit fullscreen mode

Why called SharedPrefs.init() in the main() function? Because before runApp() is called, the app will show the splash screen. It’s a perfect moment for us to do some essential initialization for the app.

The next step is to define getters & setters for your preference keys. For example, I defined a getter and setter for getting & updating username in SharedPreferences.

// utils/shared_prefs.dart
class SharedPrefs {
  static SharedPreferences _sharedPrefs;

  init() async {
    if (_sharedPrefs == null) {
      _sharedPrefs = await SharedPreferences.getInstance();
    }
  }

  String get username => _sharedPrefs.getString(keyUsername) ?? "";

  set username(String value) {
    _sharedPrefs.setString(keyUsername, value);
  }
}

final sharedPrefs = SharedPrefs();
// constants/strings.dart
const String keyUsername = "key_username";
Enter fullscreen mode Exit fullscreen mode

I also create a keyUsername *constant for consistency. You will not want to use a String directly like *_sharedPrefs.getString(“key_username”) and in another place use _sharedPrefs.setString(“key_user_name”**, value) by mistake.

That’s it. Now you can access username ANYWHERE in the app.

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Text("Hi ${sharedPrefs.username}"),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Updated: Use factory

Factory constructors
Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. For example, a factory constructor might return an instance from a cache, or it might return an instance of a subtype. Another use case for factory constructors is initializing a final variable using logic that can’t be handled in the initializer list.

class SharedPrefs {
  late final SharedPreferences _sharedPrefs;

  static final SharedPrefs _instance = SharedPrefs._internal();

  factory SharedPrefs() => _instance;

  SharedPrefs._internal();

  Future<void> init() async {
    _sharedPrefs ??= await SharedPreferences.getInstance();
  }

  // ...
}

// Remove this below line:
// final sharedPrefs = SharedPrefs();
Enter fullscreen mode Exit fullscreen mode

Now change sharedPrefs to SharedPrefs() and you're good to go!

// main.dart
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SharedPrefs().init();
  runApp(
    MyApp(),
  );
}

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Text("Hi ${SharedPrefs().username}"),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Thank you for reading my blog. You can check out this
gist
for the full code.

Have questions? Find me at https://blog.simonit.dev

đź’– đź’Ş đź™… đźš©
simonpham
Simon Pham

Posted on July 24, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related