How to Save Data to LocalStorage and SharedPreferences in Flutter
Tags: flutter
Published: 2020-04-27 15:33:07

---
title: How to Save Data to LocalStorage and SharedPreferences in Flutter
published: true
date: 2020-04-13 16:44:13 UTC
tags: flutter
canonical_url: 
---

 ![How to Save Data to LocalStorage and SharedPreferences in Flutter](https://developer.school/content/images/2020/04/Flutter--5-.jpg)

In this article we're going to investigate how we can create a simple integration with the `localstorage` and `shared_preferences` plugin inside of our Flutter applications. We'll be creating a `StorageRepository` and `StorageService` as their own package so we can include them in our other project(s) easily.

### Project Setup

Let's create a new Flutter package:

```
# Create a new package with the --template flag
$ flutter create --template=package my_storage

# Open in editor
$ cd my_storage && code .
```

We can then head over to `pubspec.yaml` and add the `localstorage` package:

```
dependencies:
  flutter:
    sdk: flutter
    
  localstorage: ^3.0.1+4
```

### Using `localstorage`

Now that we've got created package and installed `localstorage`, we can make an abstraction over `localstorage` in the event that we want to swap this out with another persistence library in the future.

For now, we'll be concerned with two methods: `getAll` and `save`:

```
/// i_local_storage_repository.dart
abstract class ILocalStorageRepository {
  Future getAll(String key);
  Future<void> save(String key, dynamic item);
}

```

We can now create an implementation of this:

```
import 'package:localstorage/localstorage.dart';
import 'package:my_storage/i_local_storage_repository.dart';

class LocalStorageRepository implements ILocalStorageRepository {
  final LocalStorage _storage;

  LocalStorageRepository(String storageKey)
      : _storage = LocalStorage(storageKey);

  @override
  Future getAll(String key) async {
    await _storage.ready;

    return _storage.getItem(key);
  }

  @override
  Future<void> save(String key, dynamic value) async {
    await _storage.ready;

    return _storage.setItem(key, value);
  }
}

```

Great. We've now have a `LocalStorageRepository` which we can use to save and get data. We still don't want to interface with this repository directly, so we can create a `LocalStorageService`:

```
/// local_storage_service.dart
import 'package:flutter/foundation.dart';
import 'package:my_storage/i_local_storage_repository.dart';

class LocalStorageService {
  LocalStorageService(
      {@required ILocalStorageRepository localStorageRepository})
      : _localStorageRepository = localStorageRepository;

  ILocalStorageRepository _localStorageRepository;

  Future<dynamic> getAll(String key) async {
    return await _localStorageRepository.getAll(key);
  }

  Future<void> save(String key, dynamic item) async {
    await _localStorageRepository.save(key, item);
  }
}

```

This takes in an injected `ILocalStorageRepository` and allows us to call the contract. Now, providing we use the `LocalStorageService` directly, we could swap out `localstorage` for something different (like `shared_preferences` but our usage would remain the same).

### Persisting Counter State

We can see this in action by persisting counter state if we create a new Flutter project:

```
# New Flutter project
$ flutter create storage_counter

# Open in editor
$ cd my_counter && code .
```

We can then add our `my_storage` package in the `pubspec.yaml` by pointing it to the directory, GitHub repo, or other:

```
dependencies:
  flutter: 
    sdk: flutter
  
  my_storage:  
    path: '../my_storage' 
```

In our typical application we'll have a service layer which we can use to call out to our `LocalStorageService` and our preferred method of persistence. Here's an example of our small `CounterService` which is able to get the current stored counter state, increment, and decrement:

```
/// lib/services/counter_service.dart
import 'package:flutter/foundation.dart';
import 'package:my_storage/i_local_storage_repository.dart';
import 'package:my_storage/local_storage_service.dart';

class CounterService {
  final LocalStorageService _localStorageService;
  final String _countKey = "count";

  CounterService({
    @required ILocalStorageRepository localStorageRepository,
  }) : _localStorageService =
            LocalStorageService(localStorageRepository: localStorageRepository);

  Future<int> getCount() async {
    return await _localStorageService.getAll(_countKey) ?? 0;
  }

  Future<int> incrementAndSave(int count) async {
    count += 1;
    await _localStorageService.save(_countKey, count);

    return count;
  }

  Future<int> decrementAndSave(int count) async {
    count -= 1;
    await _localStorageService.save(_countKey, count);

    return count;
  }
}

```

We can use this if we update our `main.dart` to contain a new `CounterPage` at `counter_page.dart`:

```
import 'package:flutter/material.dart';
import 'package:storage_counter/pages/counter_page.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Counter',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: CounterPage(title: "Storage Counter"),
    );
  }
}

```

Inside of our `CounterPage` we use a `FutureBuilder` to get the current count, and otherwise, display a loading indicator. To update our count we get a new count from our `CounterService` which also saves this to our persistence layer:

```
import 'package:flutter/material.dart';
import 'package:my_storage/local_storage_repository.dart';
import 'package:storage_counter/services/counter_service.dart';

class CounterPage extends StatefulWidget {
  CounterPage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _counter;
  CounterService _counterService;

  @override
  void initState() {
    super.initState();

    _counter = 0;
    _counterService = CounterService(
        localStorageRepository: LocalStorageRepository("counter.json"));
  }

  Future<void> _incrementCounter() async {
    final int _newCount = await _counterService.incrementAndSave(_counter);

    setState(() {
      _counter = _newCount;
    });
  }

  Future<void> _decrementCounter() async {
    final int _newCount = await _counterService.decrementAndSave(_counter);

    setState(() {
      _counter = _newCount;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            FutureBuilder<int>(
              future: _counterService.getCount(),
              builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                if (snapshot.hasData) {
                  _counter = snapshot.data;

                  return Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      IconButton(
                        icon: Icon(Icons.remove),
                        onPressed: _decrementCounter,
                      ),
                      Text(
                        '$_counter',
                        style: Theme.of(context).textTheme.headline4,
                      ),
                      IconButton(
                        icon: Icon(Icons.add),
                        onPressed: _incrementCounter,
                      ),
                    ],
                  );
                }

                return CircularProgressIndicator();
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

```

### Swapping out LocalStorage for SharedPreferences

What if we wanted to change our persistence layer? How about using `SharedPreferences` instead? That's easy enough because of the work we've already done.

Add `shared_preferences` to your `pubspec.yaml` on the `my_storage` package we created earlier:

```
dependencies:
  flutter:
    sdk: flutter
  localstorage: ^3.0.1+4
  shared_preferences: ^0.5.6+3
```

#### Creating a `SharedPreferencesRepository`

We can then create a `SharedPreferencesRepository` which uses `SharedPreferences` instead:

```
import 'package:my_storage/i_local_storage_repository.dart';
import 'package:shared_preferences/shared_preferences.dart';

class SharedPreferencesRepository implements ILocalStorageRepository {
  @override
  Future getAll(String key) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    return prefs.getInt(key);
  }

  @override
  Future<void> save(String key, item) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    return prefs.setInt(key, item);
  }
}
```

Notice how we're implementing our `ILocalStorageRepository` here, which contains our two key methods. Here are a couple of points to consider at this stage:

1. The name `ILocalStorageRepository` _could_ lend itself to being _too tied_ to the `localstorage` package if we looked at it from this lense. I feel it's still general enough at this stage, but it's certainly a consideration.
2. Our `ILocalStorageRepository` contract is _not_ representative of the `SharedPreferences` implementation, as we're using `getInt` and `setInt` for this demonstration. Consider making this more general to account for other data type(s) if changing your persistence layer is likely required in the near future.

Let's swap out our `LocalStorageRepository` for the `SharedPreferencesRepository` in our `CounterPage`:

```
class _CounterPageState extends State<CounterPage> {
  int _counter;
  CounterService _counterService;

  @override
  void initState() {
    super.initState();

    _counter = 0;
    _counterService = CounterService(
      localStorageRepository: SharedPreferencesRepository(),
    );
  }
  
  //
}
```

If we try and increment our counter, you'll notice that things work exactly how they did before:

![How to Save Data to LocalStorage and SharedPreferences in Flutter](https://developer.school/content/images/2020/04/Screenshot-2020-04-13-at-17.28.08.png)

We can even display this in another way... using both persistence layers at once!

```
import 'package:flutter/material.dart';
import 'package:my_storage/local_storage_repository.dart';
import 'package:my_storage/shared_preferences_repository.dart';
import 'package:storage_counter/services/counter_service.dart';

class CounterPage extends StatefulWidget {
  CounterPage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _sharedPreferencesCounter;
  int _localStorageCounter;

  CounterService _sharedPreferencesCounterService;
  CounterService _localStorageCounterService;

  @override
  void initState() {
    super.initState();

    _localStorageCounter = 0;
    _sharedPreferencesCounter = 0;

    _sharedPreferencesCounterService = CounterService(
      localStorageRepository: SharedPreferencesRepository(),
    );
    _localStorageCounterService = CounterService(
      localStorageRepository: LocalStorageRepository("counter.json"),
    );
  }

  Future<void> _incrementCounter(bool useSharedPrefs) async {
    final int _newCount = useSharedPrefs
        ? await _sharedPreferencesCounterService
            .incrementAndSave(_sharedPreferencesCounter)
        : await _localStorageCounterService
            .incrementAndSave(_localStorageCounter);

    _updateCount(useSharedPrefs, _newCount);
  }

  Future<void> _decrementCounter(bool useSharedPrefs) async {
    final int _newCount = useSharedPrefs
        ? await _sharedPreferencesCounterService
            .decrementAndSave(_sharedPreferencesCounter)
        : await _localStorageCounterService
            .decrementAndSave(_localStorageCounter);

    _updateCount(useSharedPrefs, _newCount);
  }

  void _updateCount(bool useSharedPrefs, int count) {
    setState(() {
      useSharedPrefs
          ? _sharedPreferencesCounter = count
          : _localStorageCounter = count;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            _buildSharedPreferencesCounter(),
            _buildLocalStorageCounter()
          ],
        ),
      ),
    );
  }

  FutureBuilder<int> _buildSharedPreferencesCounter() {
    return FutureBuilder<int>(
      future: _sharedPreferencesCounterService.getCount(),
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        if (snapshot.hasData) {
          _sharedPreferencesCounter = snapshot.data;

          return Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              IconButton(
                icon: Icon(Icons.remove),
                onPressed: () => _decrementCounter(true),
              ),
              Text(
                '$_sharedPreferencesCounter',
                style: Theme.of(context).textTheme.headline4,
              ),
              IconButton(
                icon: Icon(Icons.add),
                onPressed: () => _incrementCounter(true),
              ),
            ],
          );
        }

        return CircularProgressIndicator();
      },
    );
  }

  FutureBuilder<int> _buildLocalStorageCounter() {
    return FutureBuilder<int>(
      future: _localStorageCounterService.getCount(),
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        if (snapshot.hasData) {
          _localStorageCounter = snapshot.data;

          return Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              IconButton(
                icon: Icon(Icons.remove),
                onPressed: () => _decrementCounter(false),
              ),
              Text(
                '$_localStorageCounter',
                style: Theme.of(context).textTheme.headline4,
              ),
              IconButton(
                icon: Icon(Icons.add),
                onPressed: () => _incrementCounter(false),
              ),
            ],
          );
        }

        return CircularProgressIndicator();
      },
    );
  }
}

```

Here's our output:

![How to Save Data to LocalStorage and SharedPreferences in Flutter](https://developer.school/content/images/2020/04/Screenshot-2020-04-13-at-17.37.31.png)

Yup. Totally not necessary, but a fun experiment nonetheless.

I hope you've found this useful! I'd love to hear your thoughts.

Code:

[https://github.com/PaulHalliday/my\_storage](https://github.com/PaulHalliday/my_storage)  
[https://github.com/PaulHalliday/storage\_counter](https://github.com/PaulHalliday/storage_counter)