Tutorial: Sembast as local data storage in Flutter

Almost every app needs to store data locally on the user’s device. One option would be using SQLite, but with Flutter I prefer the Sembast library. Sembast is a NoSQL database which allows to store data in a JSON format.

In this tutorial I will show you how to use Sembast as your local data storage in your Flutter app. I will not only show you how it can be used, but also how to integrate it in your app architecture.

As an example we will create a simple app to store a list of our favorite cakes. Everyone loves cakes, right? 🎂

The complete source code of this tutorial is available on Github: https://github.com/stefangaller/flutter_sembast_local_data_storage


As with my last tutorial “Simple Flutter app initialization with Splash Screen using FutureBuilder” we will start by creating a new Flutter project and cleaning up the boilerplate code. I will name the project it sembast_tutorial.

The pubspec.yaml and the main.dart file should look like this:


name: sembast_tutorial description: A simple Sembast tutorial publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: ">=2.7.0 <3.0.0" dependencies: flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true
Code language: YAML (yaml)


import 'package:flutter/material.dart'; void main() => runApp(CakeApp()); class CakeApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'My Favorite Cakes', home: HomePage(), ); } } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("My Favorite Cakes"), ), ); } }
Code language: Dart (dart)
Flutter Sembast Local Storage Start
This is how the app should look like after the first cleanup.

The “Cake” Entity

First we create a new file called cake.dart. This file will contain our data class Cake. This entity will have an id, a name, and a yummyness factor.


class Cake { final int id; final String name; final int yummyness; Cake({this.id, this.name, this.yummyness}); Map<String, dynamic> toMap() { return { 'name': this.name, 'yummyness': this.yummyness }; } factory Cake.fromMap(int id, Map<String, dynamic> map) { return Cake( id: id, name: map['name'], yummyness: map['yummyness'], ); } Cake copyWith({int id, String name, int yummyness}){ return Cake( id: id ?? this.id, name: name ?? this.name, yummyness: yummyness ?? this.yummyness, ); } }
Code language: Dart (dart)

Let’s see what this code does in more detail:

For the sake of reducing complexity of this tutorial we do not use any external libraries for generating the code used in our data model. If you do not want to write the toMap, fromMap and copyWith functions yourself you can use libraries like freezed and json_serializable to generate these functions.

Initialization: open the Sembast database

For this step will first add Sembast, GetIt and path_provider to our project. Sembast will be used as our database implementation and GetIt to make this database available throughout our app. The path_provider library is used to retrieve the application directory on the device. This directory will be home of our database.

To add the libraries we specify them as dependencies in pubspec.yaml. The dependencies section should look like this:

... dependencies: flutter: sdk: flutter sembast: ^2.4.3 get_it: ^4.0.2 path_provider: ^1.6.7 ...
Code language: YAML (yaml)

Don’t forget to run flutter pub get to download the dependencies.

For the app initialization we will use a similar approach we have used in the tutorial “Simple Flutter app initialization with Splash Screen using FutureBuilder”. Therefore we will continue by creating a file called init.dart.


import 'package:get_it/get_it.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart'; import 'package:sembast/sembast.dart'; import 'package:sembast/sembast_io.dart'; class Init { static Future initialize() async { await _initSembast(); } static Future _initSembast() async { final appDir = await getApplicationDocumentsDirectory(); await appDir.create(recursive: true); final databasePath = join(appDir.path, "sembast.db"); final database = await databaseFactoryIo.openDatabase(databasePath); GetIt.I.registerSingleton<Database>(database); } }
Code language: Dart (dart)

The interesting part is the _initSembast function.

Cake Repository: Providing access to the database

In the next step we will create a repository to store and receive our Cake instances to and from the Sembast database. It is good practice to define an abstract class to define the interface of your repository. Using this approach you can later simply exchange your database implementation for another one easily.


In this file we just define an abstract class CakeRepository with two functions insertCake and getAllCakes that we will implement using Sembast.

import 'cake.dart'; abstract class CakeRepository { Future<int> insertCake(Cake cake); Future updateCake(Cake cake); Future deleteCake(int cakeId); Future<List<Cake>> getAllCakes(); }
Code language: Dart (dart)


The implementation isn’t complicated either:

import 'package:get_it/get_it.dart'; import 'package:sembast/sembast.dart'; import 'package:sembast_tutorial/cake.dart'; import 'package:sembast_tutorial/cake_repository.dart'; class SembastCakeRepository extends CakeRepository { final Database _database = GetIt.I.get(); final StoreRef _store = intMapStoreFactory.store("cake_store"); @override Future<int> insertCake(Cake cake) async { return await _store.add(_database, cake.toMap()); } @override Future updateCake(Cake cake) async { await _store.record(cake.id).update(_database, cake.toMap()); } @override Future deleteCake(int cakeId) async{ await _store.record(cakeId).delete(_database); } @override Future<List<Cake>> getAllCakes() async { final snapshots = await _store.find(_database); return snapshots .map((snapshot) => Cake.fromMap(snapshot.key, snapshot.value)) .toList(growable: false); } }
Code language: Dart (dart)


Finally, all that is left is to register our SembastCakeRepository to GetIt. Therefore we will edit our init.dart file. We create a new function _registerRepositories where we do the registration and we call this function in the initialize function. The code should look like this:

static Future initialize() async { await _initSembast(); _registerRepositories(); } static _registerRepositories(){ GetIt.I.registerLazySingleton<CakeRepository>(() => SembastCakeRepository()); }
Code language: Dart (dart)

Note that in line 7 we use CakeRepository as the generic type, but register a SembastCakeRepository. Later you could register a different implementation of CakeRepository here.

Putting it all together: The User Interface

Finally, we are going to connect our code and put it to use. First of all we need to clean up a little. We will move the HomePage class to a separate file and convert the CakeApp class to a StatefulWidget. Also we are going to add our initialization logic to the main.dart file.


import 'package:flutter/material.dart'; import 'package:sembast_tutorial/home_page.dart'; import 'package:sembast_tutorial/init.dart'; void main() => runApp(CakeApp()); class CakeApp extends StatefulWidget { @override _CakeAppState createState() => _CakeAppState(); } class _CakeAppState extends State<CakeApp> { final Future _init = Init.initialize(); @override Widget build(BuildContext context) { return MaterialApp( title: 'My Favorite Cakes', home: FutureBuilder( future: _init, builder: (context, snapshot){ if (snapshot.connectionState == ConnectionState.done){ return HomePage(); } else { return Material( child: Center( child: CircularProgressIndicator(), ), ); } }, ), ); } }
Code language: Dart (dart)

As you can see, this file still looks very similar to what we created in this tutorial. The only difference here is that we used a StatefulWidget. This is necessary, since some of the Sembast code is required to run after the runApp function (line 5) and this way it does.


This is the heart of our app. On our HomePage we are going to display a list of our Cakes and allow them to be added, edited and deleted.

import 'dart:math'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:sembast_tutorial/cake.dart'; import 'package:sembast_tutorial/cake_repository.dart'; class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { CakeRepository _cakeRepository = GetIt.I.get(); List<Cake> _cakes = []; @override void initState() { super.initState(); _loadCakes(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("My Favorite Cakes"), ), body: ListView.builder( itemCount: _cakes.length, itemBuilder: (context, index) { final cake = _cakes[index]; return ListTile( title: Text(cake.name), subtitle: Text("Yummyness: ${cake.yummyness}"), trailing: IconButton( icon: Icon(Icons.delete), onPressed: () => _deleteCake(cake), ), leading: IconButton( icon: Icon(Icons.thumb_up), onPressed: () => _editCake(cake), ), ); }, ), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: _addCake, ), ); } _loadCakes() async { final cakes = await _cakeRepository.getAllCakes(); setState(() => _cakes = cakes); } _addCake() async { final list = ["apple", "orange", "chocolate"]..shuffle(); final name = "My yummy ${list.first} cake"; final yummyness = Random().nextInt(10); final newCake = Cake(name: name, yummyness: yummyness); await _cakeRepository.insertCake(newCake); _loadCakes(); } _deleteCake(Cake cake) async { await _cakeRepository.deleteCake(cake.id); _loadCakes(); } _editCake(Cake cake) async { final updatedCake = cake.copyWith(yummyness: cake.yummyness + 1); await _cakeRepository.updateCake(updatedCake); _loadCakes(); } }
Code language: Dart (dart)

I will assume here that you already know how to build UIs in Flutter and won’t explain what is happening in the build function.

And that’s it! 🎉

Let’s start the app and see what we have accomplished. It allows you to do add, edit, and delete objects using a Sembast database. Most importantly you also see how an architecture looks like for integrating Sembast into your app.

Flutter Sembast Local Storage Complete
Our final version. With this simple app you can add, edit and remove cakes using the Sembast database.


We have created a simple app with a local storage using Sembast. First we have set up a data Entity to store. We needed to create some functions for converting to and from a map there.

Next, we initialized our Sembast database and made it available in the app with GetIt. Using the database we have implemented a repository allowing us to read and write to the Sembast datatabase.

Finally, in the last step, we added a user interface to add, edit and remove cakes and display them in a list.

I hope you enjoyed to the tutorial and it gave you an idea on how to integrate Sembast as a local database to your app.

Write me a comment if you gave Sembast a try or wether you prefer a different solution.

More Tutorials

7 responses to “Tutorial: Sembast as local data storage in Flutter”

  1. Szymon says:

    I am getting this error after following the tutorial. Initially, I used the same verison for the dependencies you used and then upgraded to the latest version to attempt to fix the problem. Nothing has worked so far.

    No type FormulaRepository is registered inside GetIt.
    Did you forget to pass an instance name?
    (Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;
    did you forget to register it?)
    Failed assertion: line 257 pos 14: ‘instanceFactory != null’


    • Stefan Galler says:

      Hi Szymon,
      there is no class named FormulaRepository in this tutorial.
      Maybe you registered a CakeRepository and now try to retrieve a FormulaRepository?

  2. Michael says:

    In home_page.dart file line 56 “setState(() => _cakes = cakes);” gives an error because a value of type ‘Future<List>’ can’t be assigned to a variable of type ‘List’.
    As the variable cake is a Future<List> type and _cakes is List type. I tried to cast it to a List, tried to use .tolist() inside a .then but I couldn’t figure it out
    Can you please provide us/me a solution for this?? I’m stuck after I used your architecture on my project.

    • Stefan Galler says:

      Hi Michael,
      Did you maybe miss the await keyword in line 55?
      In this case the variable cake would be a Future.

  3. Thomas says:

    Amazing tutorial. I’m having a problem. I’m trying to implement this logic on my app but I have two parameters which are nested string lists, like this “List<List>”. The terminal says “Unhandled Exception: type ‘ImmutableList’ is not a subtype of type ‘List<List>”. You know how could I implement the nested string lists into the main class??
    Kind regards.

    • Stefan Galler says:

      Hi Thomas,
      unfortunately, I cannot tell from your comment what the reason of this error is.
      It kind of looks like you’re trying to assign a Immutable list to your variable.
      Best regards,

  4. Daniele says:

    This happens when you wrong declare your interface variable which will get the instance. For example: if a make an implementation of CakeRepository, it waits an interface that declare only the sign methods, for example ICakeReposity (the I at the first position stand for Interface).
    So you’ll have somenthing like ICakeRepository varName = GetIt.I.get();

Leave a Reply

Your email address will not be published. Required fields are marked *