In my tutorial Sembast as local data storage in Flutter, I used BLoC for state management. This time, I will create the same example application using Riverpod.
Sembast is a NoSQL database that can be used as a local storage in your Flutter application. It is fast and easy to use for storing JSON data locally. As in the previous tutorial using BLoC, we will build an app to add, edit, delete and view a list of yummy cakes.
The source code to this tutorial is available on GitHub: https://github.com/bettercoding-dev/sembast-riverpod.
Prerequisite
As always, we start with a clean new project. (See 5 Things I do when starting a new Flutter project for more information).
Next, let’s add our dependencies to pubspec.yaml
:
|
|
Of course, we will use sembast
and flutter_riverpod
for our project. For our Cake
model class, we will use freezed
to help us generate some convenience functions for us. For this, we also need to add freezed_annotations
, json_annotations
, json_serializable
and build_runner
to the dependencies (and dev_dependencies). Finally, we also need to add path_provider
– this package will be used for locating the correct directory to store our Sembast database file.
flutter_lints
is now default for each new Flutter project. This package helps to find bad coding practices and helps to improve your code.
Next, we create a file called global_providers.dart
and add the following code:
|
|
Here we defined a Provider
for our Sembast Database
. But for now, it is not implemented yet. This is because the initialization of the database will be async
, but we want to avoid using a FutureProvider
. We will use Riverpod’s overrides to bind the initialized database to this provider later.
We will do this in our main.dart
file:
|
|
- Line 10: This line ensures that packages that use native code (such as
path_provider
) have access to native code API’s. For more info, see this post on stackoverflow. - Lines 12-15: Here, we initialize our Sembast database. First, we use
path_provider
to get our app’s documents directory (line 12) and create it if it does not yet exist (line 13). In line 14 we create a path for our database file (calledcakes.db
) and finally open the Sembast database (line 15). - Lines 17-24: Here we run our app and initialize Riverpod’s
ProviderScope
. This is also where we override thedatabaseProvider
and assign it our initialized database (line 20). - Lines 27-36: This is all we need for defining our
CakeApp
. It will open ourHomePage
that we will create later.
The Cake Model
Next, we create our Cake
model class that holds all information about the cakes we want to display. For this, we create a new folder models
and add a cake.dart
file. We’ll be using freezed
to generate some functions for us.
The
_$Cake
,_Cake
and_$CakeFromJson
objects will not be found by your IDE or editor when you first define your model. They are generated byfreezed
later when calling thedart run build_runner build --delete-conflicting-outputs
command.
|
|
Lines 3-5: We use
freezed
andjson_serializable
to generate some handy functions for ourCake
model (such astoString
,copyWith
,toJson
,fromJson
, …). The generated code is put to the files specified in line 3 and 5. We can link them to this file using thepart
keyword.Line 7: Don’t forget the
@freezed
decorator!Lines 8-16: Here we define our model class. We use two factory constructors. The first one (lines 9-13) is used to define the attributes of our model, the second one tells
freezed
to afromJson
constructor (line 15).
Check out the
[freezed](https://pub.dev/packages/freezed)
page on pub.dev or watch this video for more information.
Now that we specified everything call
dart run build_runner build --delete-conflicting-outputs
to generate code once or
dart run build_runner watch --delete-conflicting-outputs
to watch for file changes, to continuously generate code.
The Cake Repository
In this section, we will finally use Sembast to read, write and update data to our local database. As with the Sembast tutorial using BLoC, we create an abstract CakeRepository
first, to allow abstraction. In a new repositories
directory, add a file called cake_repository.dart
with this content:
|
|
In comparison to the other tutorial, we will now take advantage of the improved Sembast library, that now allows to receive changes to the database as a stream.
So, let’s implement the Repository in a new file called sembast_cake_repository.dart
.
|
|
- Lines 7-11: Here, we define our Riverpod provider to provide the
SembastCakeRepository
to other parts of our application and inject its dependencies. - Lines 14-20: We create a new class that implements the abstract
CakeRepository
. TheSembastCakeRepository
contains two properties:database
and_store
that inject and create in the constructor. - Lines 22-30: Inserting, updating and deleting with Sembast is really simple. Here we make use of the generated
toJson
functions. - Lines 32-38: With Sembast it is also possible to listen to changes in your database and receive the updated data as a
Stream
. We can do this, by creating aquery
on the_store
(an empty query returns all objects). TheonSnapshots
functions is where Sembast returns us the stream of changes. We convert the snapshots into a list ofCake
s.
The Cake Page
Finally, all that is left is the user interface. As described in my blog post Tutorial: Simple Riverpod App Architecture in Flutter, we will have three files:
home_page.dart
: Here, we define how the UI looks like.home_controller.dart
: In this file, we will handle use input.home_state.dart
: This file will hold the state of theHomePage
.
Add all these files into a new pages
directory.
home_state.dart
|
|
This simple file only holds one Provider: cakesProvider
is a StreamProvider
that uses the CakeRepository
to get a stream of all available cakes.
home_controller.dart
|
|
- Lines 8-12: Here, we create a
Provider
that will make theHomeController
available to theHomePage
. - Lines 14-18: The
HomeController
class contains a list of flavors that will be used when creating a newCake
. It also holds a reference to theCakeRepository
that will be used to handle the different user interactions. - Lines 20-36: We add functions to allow the handling of
delete
,edit
, andadd
actions by the user. Delete will remove the cake from the database, edit will increase the yummyness factor by one, and add will randomly add a new flavored cake to the database. Since the stream will automatically update, there is no need to query the database manually.
home_page.dart
|
|
- Line 6: Instead of
StatelessWidget
, we extend fromConsumerWidget
to get access toWidgetRef
in ourbuild
function. - Lines 11-12: Using the
WidgetRef
we can listen to thehomeControllerProvider
andcakesProvider
. - Lines 18-21: The
StreamProvider
returns anAsyncValue
. This class gives us access to awhen
function to handle different states of the stream. We can specify widgets forloading
,error
, anddata
(success) state. - Lines 21-38: We simply use a
ListView
to display theCake
s. EveryListTile
has two buttons: one to increase yummyness and one to delete theCake
. We simply use thecontroller
to handle the clicks. - Lines 40-43: Finally, to add new
Cake
s we create aFloatingActionButton
that also uses thecontroller
to delegate the action.
When we now run our application, this is what we should see. Our app should behave the same as in Tutorial: Sembast as local data storage in Flutter, but uses Riverpod for state management.
The source code to this tutorial is available on GitHub: https://github.com/bettercoding-dev/sembast-riverpod.
Summary
No matter which state management solution we use, Sembast is a solid database package for storing your local data in a NoSQL way. In this tutorial, I transformed the tutorial using BLoC into a Riverpod-driven application.
Which state management solution do you prefer? Do you also use Sembast for storing your data, or do you use packages like Hive or sqflite?