Persistently storing data in Flutter is not one of the easiest experiences if you're just starting out. If you want to move beyond simple "Preferences", which are only key - value pairs, you are probably looking at a library like SQFLite. The problem with this library is that it's very low level and maybe you really don't need to use structured data. What about NoSQL in Flutter?
In this tutorial you're going to learn about SEMBAST (Simple Embedded Application Store) which is a very powerful, yet simple to use library for storing, querying, ordering, paginating and even encrypting data.
Importing packages
Before we can start working with the SEMBAST library, we need to add it and also some other libraries to our pubspec.yaml file. Because we don't want to end up with spaghetti code, we are going to use BLoC for state management. You can, however, obviously use any kind of state management pattern in your projects.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
sembast: ^1.15.1
path_provider: ^0.5.0+1
# Used for BLoC state management
flutter_bloc: ^0.9.1
equatable: ^0.2.3
Building the data layer
Like most of the database libraries out there, SEMBAST needs to be opened before we can use it to store or retrieve data. This is so that the library can establish a connection with afile on the persistent storage in which all of the data is stored.
Opening a database 

We are going to create an AppDatabase class to hold all of the database opening logic. One more thing about a database being opened - it needs to happen only once. For that reason, AppDatabase class will be a singleton - a class with only a single instance. Singleton is a design pattern which makes sure we can very simply access an instance of a class, while ensuring that there can be only one instance of a given type.
app_database.dart
import 'dart:async';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sembast/sembast.dart';
import 'package:sembast/sembast_io.dart';
class AppDatabase {
// Singleton instance
static final AppDatabase _singleton = AppDatabase._();
// Singleton accessor
static AppDatabase get instance => _singleton;
// Completer is used for transforming synchronous code into asynchronous code.
Completer<Database> _dbOpenCompleter;
// A private constructor. Allows us to create instances of AppDatabase
// only from within the AppDatabase class itself.
AppDatabase._();
// Sembast database object
Database _database;
// Database object accessor
Future<Database> get database async {
// If completer is null, AppDatabaseClass is newly instantiated, so database is not yet opened
if (_dbOpenCompleter == null) {
_dbOpenCompleter = Completer();
// Calling _openDatabase will also complete the completer with database instance
_openDatabase();
}
// If the database is already opened, awaiting the future will happen instantly.
// Otherwise, awaiting the returned future will take some time - until complete() is called
// on the Completer in _openDatabase() below.
return _dbOpenCompleter.future;
}
Future _openDatabase() async {
// Get a platform-specific directory where persistent app data can be stored
final appDocumentDir = await getApplicationDocumentsDirectory();
// Path with the form: /platform-specific-directory/demo.db
final dbPath = join(appDocumentDir.path, 'demo.db');
final database = await databaseFactoryIo.openDatabase(dbPath);
// Any code awaiting the Completer's future will now start executing
_dbOpenCompleter.complete(database);
}
}
We want to be able to return the newly opened Database instance. However, once the database is already opened (if we call the database getter for the second time), we don't want to open the database for the second time! What we want to do is to return the already opened instance.
The catch is that opening a database is an asynchronous operation - it takes some time. Therefore, the database getter must return a Future. How can we then await the database to be opened the first time the getter is called and then return the already opened instance on subsequent calls?
We can use a Completer. It's a class for making and storing our own Futures and we want precisely that.
Creating a model class 


Once we have the low-level database class finished, we want to move on to writing a Data Access Object which can be used to actually facilitate the CRUD (create, read, update, delete) operations. The problem is, we currently don't have anything to store in the database.
In this tutorial, we are going to build an app displaying a list of fruits. Let's create a Fruit class! SEMBAST stores data as JSON strings, so we need to have a way to convert Fruit objects to Map<String, dynamic> - converting from Map to JSON is handled directly by SEMBAST.
fruit.dart
import 'package:meta/meta.dart';
class Fruit {
// Id will be gotten from the database.
// It's automatically generated & unique for every stored Fruit.
int id;
final String name;
final bool isSweet;
Fruit({
@required this.name,
@required this.isSweet,
});
Map<String, dynamic> toMap() {
return {
'name': name,
'isSweet': isSweet,
};
}
static Fruit fromMap(Map<String, dynamic> map) {
return Fruit(
name: map['name'],
isSweet: map['isSweet'],
);
}
}
Data Access Object for Fruit
Now that we have the core low-level AppDatabase class which is a singleton used to get the single opened instance of a SEMBAST database and we also have a Fruit model class, we can finally write functions responsible for inserting, updating, deleting and getting data from the database. By convention, such classes are called Data Access Objects or shortly Dao.
fruit_dao.dart
import 'package:sembast/sembast.dart';
import 'package:sembast_prep/data/app_database.dart';
import 'package:sembast_prep/data/fruit.dart';
class FruitDao {
static const String FRUIT_STORE_NAME = 'fruits';
// A Store with int keys and Map<String, dynamic> values.
// This Store acts like a persistent map, values of which are Fruit objects converted to Map
final _fruitStore = intMapStoreFactory.store(FRUIT_STORE_NAME);
// Private getter to shorten the amount of code needed to get the
// singleton instance of an opened database.
Future<Database> get _db async => await AppDatabase.instance.database;
Future insert(Fruit fruit) async {
await _fruitStore.add(await _db, fruit.toMap());
}
Future update(Fruit fruit) async {
// For filtering by key (ID), RegEx, greater than, and many other criteria,
// we use a Finder.
final finder = Finder(filter: Filter.byKey(fruit.id));
await _fruitStore.update(
await _db,
fruit.toMap(),
finder: finder,
);
}
Future delete(Fruit fruit) async {
final finder = Finder(filter: Filter.byKey(fruit.id));
await _fruitStore.delete(
await _db,
finder: finder,
);
}
Future<List<Fruit>> getAllSortedByName() async {
// Finder object can also sort data.
final finder = Finder(sortOrders: [
SortOrder('name'),
]);
final recordSnapshots = await _fruitStore.find(
await _db,
finder: finder,
);
// Making a List<Fruit> out of List<RecordSnapshot>
return recordSnapshots.map((snapshot) {
final fruit = Fruit.fromMap(snapshot.value);
// An ID is a key of a record from the database.
fruit.id = snapshot.key;
return fruit;
}).toList();
}
}
Managing the app state with BLoC
Currently, we have the model layer finished - we can read and write to the SEMBAST NoSQL database. While we could call the FruitDao's functions directly from Widgets, doing so would tightly couple our UI with the SEMBAST DB.
While in this tutorial we are surely not going to move away from SEMBAST, in a real project it's more than expected that sooner or later you will perform some drastic changes to how your app is wired. You might want to store data on a remote server, for instance.
This is the reason for adding a middleman between the UI and the model layer. We are going to use arguably the best state management library out there and that is BLoC.
In short, BLoC is like a box which takes in events and outputs state. Events and states are simply normal objects. As soon as the event gets inside the proverbial "box", a function runs where we can determine which state to output.
For example, if the BLoC receives an event called LoadFruits, it will first output FruitsLoading state so that the UI can display a loading indicator, and then, once the fruits are asynchronously gotten from the database, the BLoC will emit another state called FruitsLoaded which will hold the List of fruits.

Thus we want to create 3 files - fruit_event.dart, fruit_state.dart, and fruit_bloc.dart. Let's start with the event - the thing which goes "into the box".
fruit_event.dart
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
import 'package:sembast_prep/data/fruit.dart';
@immutable
abstract class FruitEvent extends Equatable {
FruitEvent([List props = const []]) : super(props);
}
class LoadFruits extends FruitEvent {}
class AddRandomFruit extends FruitEvent {}
class UpdateWithRandomFruit extends FruitEvent {
final Fruit updatedFruit;
UpdateWithRandomFruit(this.updatedFruit) : super([updatedFruit]);
}
class DeleteFruit extends FruitEvent {
final Fruit fruit;
DeleteFruit(this.fruit) : super([fruit]);
}
We have 4 events which will initiate the logic inside the BLoC and subsequently return a state for the UI to display data on the screen with it. State classes will be even simpler than events. There will be only 2 of them - FruitsLoading and FruitsLoaded.
fruit_state.dart
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
import 'package:sembast_prep/data/fruit.dart';
@immutable
abstract class FruitState extends Equatable {
FruitState([List props = const []]) : super(props);
}
class FruitsLoading extends FruitState {}
class FruitsLoaded extends FruitState {
final List<Fruit> fruits;
FruitsLoaded(this.fruits) : super([fruits]);
}
Finally, we need to actually connect events with the states together by writing some logic. Since we are using Business Logic Component, there is no better place to write this connecting logic than in the BLoC itself.
fruit_bloc.dart
import 'dart:async';
import 'dart:math';
import 'package:bloc/bloc.dart';
import 'package:sembast_prep/data/fruit.dart';
import 'package:sembast_prep/data/fruit_dao.dart';
import './bloc.dart';
class FruitBloc extends Bloc<FruitEvent, FruitState> {
FruitDao _fruitDao = FruitDao();
// Display a loading indicator right from the start of the app
@override
FruitState get initialState => FruitsLoading();
// This is where we place the logic.
@override
Stream<FruitState> mapEventToState(
FruitEvent event,
) async* {
if (event is LoadFruits) {
// Indicating that fruits are being loaded - display progress indicator.
yield FruitsLoading();
yield* _reloadFruits();
} else if (event is AddRandomFruit) {
// Loading indicator shouldn't be displayed while adding/updating/deleting
// a single Fruit from the database - we aren't yielding FruitsLoading().
await _fruitDao.insert(RandomFruitGenerator.getRandomFruit());
yield* _reloadFruits();
} else if (event is UpdateWithRandomFruit) {
final newFruit = RandomFruitGenerator.getRandomFruit();
// Keeping the ID of the Fruit the same
newFruit.id = event.updatedFruit.id;
await _fruitDao.update(newFruit);
yield* _reloadFruits();
} else if (event is DeleteFruit) {
await _fruitDao.delete(event.fruit);
yield* _reloadFruits();
}
}
Stream<FruitState> _reloadFruits() async* {
final fruits = await _fruitDao.getAllSortedByName();
// Yielding a state bundled with the Fruits from the database.
yield FruitsLoaded(fruits);
}
}
class RandomFruitGenerator {
static final _fruits = [
Fruit(name: 'Banana', isSweet: true),
Fruit(name: 'Strawberry', isSweet: true),
Fruit(name: 'Kiwi', isSweet: false),
Fruit(name: 'Apple', isSweet: true),
Fruit(name: 'Pear', isSweet: true),
Fruit(name: 'Lemon', isSweet: false),
];
static Fruit getRandomFruit() {
return _fruits[Random().nextInt(_fruits.length)];
}
}
Building the user interface
There can be no Flutter app if it doesn't have a UI. With all of the prior classes in place, we can finally put everything into practice.
The UI of the fruit app will be very simple - a ListView containing ListTiles. Those will contain the name and sweetness of the fruit and two buttons. One for updating the fruit in the database with a new random one, and another button for deleting the fruit.
main.dart
import 'package:flutter/material.dart';
import 'package:sembast_prep/fruit_bloc/fruit_bloc.dart';
import 'package:sembast_prep/home_page.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Wrapping the whole app with BlocProvider to get access to FruitBloc everywhere
// BlocProvider extends InheritedWidget.
return BlocProvider(
bloc: FruitBloc(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.yellow,
accentColor: Colors.redAccent,
),
home: HomePage(),
),
);
}
}
Bloc library provides a BlocProvider widget which is a subclass of InheritedWidget. This means that we can access the FruitBloc from other widgets down the tree, including the HomePage, where the ListView will be located.
home_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sembast_prep/data/fruit.dart';
import 'package:sembast_prep/fruit_bloc/bloc.dart';
class HomePage extends StatefulWidget {
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
FruitBloc _fruitBloc;
@override
void initState() {
super.initState();
// Obtaining the FruitBloc instance through BlocProvider which is an InheritedWidget
_fruitBloc = BlocProvider.of<FruitBloc>(context);
// Events can be passed into the bloc by calling dispatch.
// We want to start loading fruits right from the start.
_fruitBloc.dispatch(LoadFruits());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Fruit app'),
),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
_fruitBloc.dispatch(AddRandomFruit());
},
),
);
}
Widget _buildBody() {
return BlocBuilder(
bloc: _fruitBloc,
// Whenever there is a new state emitted from the bloc, builder runs.
builder: (BuildContext context, FruitState state) {
if (state is FruitsLoading) {
return Center(
child: CircularProgressIndicator(),
);
} else if (state is FruitsLoaded) {
return ListView.builder(
itemCount: state.fruits.length,
itemBuilder: (context, index) {
final displayedFruit = state.fruits[index];
return ListTile(
title: Text(displayedFruit.name),
subtitle:
Text(displayedFruit.isSweet ? 'Very sweet!' : 'Sooo sour!'),
trailing: _buildUpdateDeleteButtons(displayedFruit),
);
},
);
}
},
);
}
Row _buildUpdateDeleteButtons(Fruit displayedFruit) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
_fruitBloc.dispatch(UpdateWithRandomFruit(displayedFruit));
},
),
IconButton(
icon: Icon(Icons.delete_outline),
onPressed: () {
_fruitBloc.dispatch(DeleteFruit(displayedFruit));
},
),
],
);
}
}
Conclusion
SEMBAST DB is a very powerful NoSQL persistent storage option for Flutter. It's ease of setup and use makes it a viable option for your apps, especially if you don't want to bother with an SQL approach.
You've learned how to make a simple yet complete app with SEMBAST using BLoC for state management. Be sure to check out the video tutorial for a more hands-on perspective of building this app.
can we connect this no sql db to the firebase firestore ?
No, that is not possible.
Seems like one may be able to validate their schema though before potentially incurring charges on Firestore. Perhaps by logging how many times a retrieval is performed. I’m excited to find this library and I appreciate your tutorials.
Thanks, Kenneth!
Hi, do you know how can I create relationships between models? Like one-to-many relationships and stuff like that.
Hello! SEMBAST is a NoSQL database so making real relationships is not possible. A common technique is to nest data or to programmatically “join” data together after you get it out of the database.
Looks like there’s an error in your Fruit DAO where you assign the key to the fruit.id as fruit was marked final you can’t change it afterward.
It may seem like it, but the “id” field is not final itself. Only the “fruit” variable is final.
is there a sembast close database ? or make sync of database to disk ?
Fantastic tutorial, thank you for putting this material out. I was wondering if you may be able to show an example of the Fruit class with a bit more complexity. Most tutorials and examples found online always show a very simple model class, with a few simple fields. I find myself running into issues constantly when trying to correctly format my toMap and fromMap methods when the model in question has items that are objects or lists of objects. Would you be willing to show what these methods would look like if Fruit included both an object as one of its fields, as well as a list of objects. For example, what if we had a class called GrownIn which included a single String called location and a second class called Vitamin which includes a single String called mineral. Then, the two new fields included in our Fruit class would be GrownIn grownIn, and List nutrients. I have found so many examples of how to do this, manually, as well as using generators such as json_serializable and it seems that results for success can vary based on what else is in play. You seem to have a very clean and clear approach for how you explain things and I would love to see an example of this which will work correctly with the sembast tutorial you’ve built. Thank you!
Does this code compileable ? thx
final recordSnapshots = await _fruitStore.find(
await _db,
finder: finder,
);
Seems not to work. Find seems to want a DatabaseClient and _db is a Database.
Your point of view caught my eye and was very interesting. Thanks. I have a question for you.