Storing and retrieving data in Flutter with shared preferences in a maintainable way

Eternity (Isaac Adariku)
7 min readJan 6, 2023

by Isaac Adariku (LinkedIn, Twitter, Website)

In a Flutter application, it is often necessary to store and retrieve small pieces of data, such as user preferences or login information. One way to do this is through the use of shared preferences. Shared preferences allow you to store key-value pairs of data as simple strings, and they are persisted across app launches.

While shared preferences can be a useful tool for storing and retrieving small amounts of data, it is essential to maintain them in a clean and organized manner. My last mentorship projects where I mentored hundreds of Flutter developers made me realize that shared preferences are often used in an unclean and unorganized way by beginners, which can make it difficult to maintain the codebase over time. This is particularly important in larger applications, where multiple developers may be working on different parts of the codebase.

Here are some of the common reasons why shared preferences are not maintained in a clean and organized manner include:

  1. Keys are not named consistently
  2. Keys are not named in a way that makes it easy to understand their purpose
  3. Shared preferences are not used consistently throughout the application
  4. Shared preferences are not cleaned up regularly, which can lead to performance issues
  5. The logic for reading and writing shared preferences is not centralized in a single class
  6. Using “async” and “await” everywhere when reading and writing shared preferences
  7. Different log messages are used for reading and writing shared preferences because they are not centralized in a single class.
🤔

Maintaining Shared Preferences in a Clean and Organized Manner

One way to maintain shared preferences in a Flutter application is to use a dedicated class for handling all shared preference operations. This class can be responsible for reading and writing data to shared preferences, as well as providing convenient methods for retrieving and updating specific values. By centralizing all shared preference operations in a single class, it becomes easier to understand and maintain the codebase, as well as to ensure that shared preferences are being used consistently throughout the application.

In addition to using a dedicated class for shared preference operations, it is also important to follow best practices for naming shared preference keys. For example, using a consistent naming convention is often helpful, such as using all lower camel case letters. This makes it easier to understand the purpose of each shared preference key and can help to avoid naming conflicts or confusion.

Setup

Here is a sample code for a dedicated shared preference class in Flutter:

To add shared preferences to your Flutter application, you can add the shared_preferences package to your pubspec.yaml file:

dependencies:
flutter:
sdk: flutter
shared_preferences: ^latest_version

Create a new file under lib/services called shared_preferences_service.dart. This file will contain the SharedPreferencesService class that will be used to read and write data to shared preferences.

We will use the singleton pattern to create a single instance of the SharedPreferencesService class. This will ensure that we only have one instance of the class and that we can access it from anywhere in the application. To do this, we will use a private constructor and a static getInstance() method to retrieve the single instance of the class.

import 'package:shared_preferences/shared_preferences.dart';

class SharedPreferencesService {
static SharedPreferencesService? _instance;
static late SharedPreferences _preferences;

SharedPreferencesService._();

// Using a singleton pattern
static Future<SharedPreferencesService> getInstance() async {
_instance ??= SharedPreferencesService._();

_preferences = await SharedPreferences.getInstance();

return _instance!;
}
}

Next, we will add two private methods to _saveData() and _getData() to the SharedPreferencesService class. These methods will be responsible for reading and writing data to shared preferences, which will be used by the public methods in the class.

The _getData() will be a generic method that will be used to retrieve data from shared preferences. It will take just a single parameter, which will be the key for the data that we want to retrieve. The method will return a dynamic value, which will be the data that we retrieve from shared preferences.

// Private generic method for retrieving data from shared preferences
dynamic _getData(String key) {
// Retrieve data from shared preferences
var value = _prefs.get(key);

// Easily log the data that we retrieve from shared preferences
print('Retrieved $key: $value');

// Return the data that we retrieve from shared preferences
return value;
}

The _saveData() method will be a method that will be used to save data to shared preferences, it will handle all types of data that we want to save by checking the type of data that we want to save. It will take two parameters, which will be the key and value for the data that we want to save.

// Private method for saving data to shared preferences
void _saveData(String key, dynamic value) {
// Easily log the data that we save to shared preferences
print('Saving $key: $value');

// Save data to shared preferences
if (value is String) {
_prefs.setString(key, value);
} else if (value is int) {
_prefs.setInt(key, value);
} else if (value is double) {
_prefs.setDouble(key, value);
} else if (value is bool) {
_prefs.setBool(key, value);
} else if (value is List<String>) {
_prefs.setStringList(key, value);
}
}

Now that we have private methods for reading and writing data to shared preferences, it provides convenient methods for reading and writing various types which include strings, integers, booleans and lists.

We can now add public methods for retrieving and updating specific values. For example, we can add username and themeColor properties to the SharedPreferencesService class that will be used to retrieve and update the username and theme colour. These properties will be String and Color respectively and will be initialized to empty strings and Colors.blue respectively.

First, we will create global private constants for the keys that we will use to retrieve and update the username and theme color. These constants will be prefixed with _k to indicate that they are private constants outside of the SharedPreferencesService class.

// Global private constants
const String _kUsernameKey = 'username';
const String _kThemeColorKey = 'theme_color';

class SharedPreferencesService {

Next, we will add the public properties for retrieving and updating the username and theme color. These properties will be prefixed with get and set to indicate that they are getters and setters respectively. The get and set keywords are used to define getters and setters in Dart.

  // Persist and retrieve username
String get username => _getData(_kUsernameKey) ?? '';
set username(String value) => _saveData(_kUsernameKey, value);

// Persist and retrieve theme color
Color get themeColor => Color(_getData(_kThemeColorKey) ?? Colors.blue.value);
set themeColor(Color value) => _saveData(_kThemeColorKey, value.value);

The same pattern can be used to add getters and setters for other values that we want to persist in shared preferences.

Usage

To use this shared preferences class, if you are using a dependency injection library like GetIt, you can register the class as a singleton and inject it into your application (which is what I will recommend). If you are not using a dependency injection library, you can simply call the getInstance() method to retrieve the single instance of the class.

Then you can simply call the appropriate method and pass in the desired value. For example, to retrieve the username from shared preferences, you can use the following code:

String username = preferences.username;

And to save (set) the theme color in shared preferences, you can use the following code:

preferences.themeColor = Colors.blue;

By centralizing shared preference operations in a dedicated class like this, it becomes easier to maintain and organize the shared preference data in your Flutter application.

Your code is now ready to be in production 😄

You can find a simple colorful counter app that uses the shared preferences class that we created in this tutorial in the GitHub repository below:

Conclusion

In conclusion, shared preferences is a useful tool for storing and retrieving small amounts of data in a Flutter application. However, it is important to maintain shared preferences in a clean and organized manner in order to ensure that your application remains maintainable and scalable. By using a dedicated shared preferences class like the one we created in this tutorial, you can easily organize and maintain the shared preference data in your Flutter application.

In addition to using a dedicated shared preference class, it is also important to follow best practices for naming shared preference keys, consistent logging and properly handling errors and exceptions. Regularly reviewing and cleaning up shared preferences can also help to improve the performance of the app and make it easier to maintain the shared preferences over time.

I hope you enjoyed this article!

You can contribute to this article by;

  • 📢 Sharing it with your friends, family, and colleagues.
  • 👏🏾 Press the clap button below to show your appreciation and motivation.
  • 📝 Leave a comment below to share your thoughts and questions.
  • ➕ Follow me on Medium to get notified when I publish new articles.
  • 😍 Connect with me on Twitter and LinkedIn and I will be happy to help you out.

Every day is another opportunity to gain Mastery! 💙 — Isaac Adariku (Eternity)

Resources

FilledStacks — cleaner shared preferences in flutter

--

--

Eternity (Isaac Adariku)
Eternity (Isaac Adariku)

Written by Eternity (Isaac Adariku)

GDE Flutter & Dart | Software Craftsman | Organizer FlutterKaduna

No responses yet