We would all agree that at certain points in our mobile app development lifecycle, we consider storing and utilizing data. This is where a database comes in handy.
A database is a piece of software that stores and uses electronic information (data) in a structured manner, or data persistence. Data is reliably stored (persisted) and available to work with unless it is intentionally erased, in contrast to caching.
A database allows developers to use a programming language or API to save, read, modify, and remove data in the database. These activities are carried out in the background of an application, away from the view of end-users.
CRUD is a synonym for the most common database interactions and it stands for Create, Read, Update, and Delete.
For the scope of this topic, we would focus primarily on two types of databases that can be used with mobile technologies. Databases can be classified based on a number of factors, which range from the type of data they support, how they scale, how they can be defined, and the location of their storage.
There are lots of databases out there, but we will stick with these two:
In this guide, we would be exploring the types of databases in Flutter and cover how to get each of them set up in less than no time.
Relational databases are datasets that have relationships and values linking them to each other. They are commonly found as a set of database tables, of rows and columns. They hold information about an object and each table acts as a blueprint of the represented object. Every column in the database holds data pertaining to a particular type and the fields houses the precise value of the attribute.
The table’s rows indicate a group of interrelated values for a single data item. A primary key is a distinctive identifier assigned to each row in a table, while foreign keys are used to link rows from other tables in our database.
Without altering the database tables, this data may be accessed in a variety of ways.
SQL, otherwise known as the relational database, and it is one of the most recognized types of database in any tech stack. Let’s look at how Flutter does SQL.
sqflite is basically an implementation of SQLite. It provides us with lots of functionality that gives us full control of our database and help us write queries, relationships, and every other database function that our application requires.
Let’s take a look at how to set this up in our application according to the official Flutter plugin page.
In order to use this package, we need to add the dependency in our pubspec
file, as shown in the installation page:
// Get a location using getDatabasesPath var databasesPath = await getDatabasesPath(); String path = join(databasesPath, 'demo.db'); // Delete the database await deleteDatabase(path); // open the database Database database = await openDatabase(path, version: 1, onCreate: (Database db, int version) async { // When creating the db, create the table await db.execute( 'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)'); }); // Insert some records in a transaction await database.transaction((txn) async { int id1 = await txn.rawInsert( 'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)'); print('inserted1: $id1'); int id2 = await txn.rawInsert( 'INSERT INTO Test(name, value, num) VALUES(?, ?, ?)', ['another name', 12345678, 3.1416]); print('inserted2: $id2'); }); // Update some record int count = await database.rawUpdate( 'UPDATE Test SET name = ?, value = ? WHERE name = ?', ['updated name', '9876', 'some name']); print('updated: $count'); // Get the records List<Map> list = await database.rawQuery('SELECT * FROM Test'); List<Map> expectedList = [ {'name': 'updated name', 'id': 1, 'value': 9876, 'num': 456.789}, {'name': 'another name', 'id': 2, 'value': 12345678, 'num': 3.1416} ]; print(list); print(expectedList); assert(const DeepCollectionEquality().equals(list, expectedList)); // Count the records count = Sqflite .firstIntValue(await database.rawQuery('SELECT COUNT(*) FROM Test')); assert(count == 2); // Delete a record count = await database .rawDelete('DELETE FROM Test WHERE name = ?', ['another name']); assert(count == 1); // Close the database await database.close();
What we have just done above is create a defined path and storage name for our database that would serve as the database location on our device. After that, we make use of the Database
class instance to open our database, and this provides us with some functionalities that include the following:
onCreate
: Here we want to define the logic for our database when it is created
OnOpen
: Code that runs when the database is opened
We’ve also inserted data into our database and printed out the results in our console.
Subsequently, we can also see how to update, delete, and query data, and finally close our database.
Drift, which was formerly known as Moor, is a reactive persistence library for Flutter and Dart, built on SQLite.
It is more of a wrapper around the SQlite package that provides us with the same functions and tools needed to write structured relational database queries, and it also takes time to reduce the boilerplate encountered in traditional SQLite scenarios.
The major advantage of Moor database is that it can be used alongside build_runner. You can find more information about build_runner here. With build_runner and Moor, you do not have to manually type out all your queries. You simply create a class specifying the rows and columns you want as fields in your class and let code generation using build_runner generate the needed database initialization codes.
In order to use Drift, you have to add it to your pubspec
file and run the command flutter pub get
to fetch your dependencies, as written in the docs here:
///// //// For more information on using drift, please see https://drift.simonbinder.eu/docs/getting-started/ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; part 'main.g.dart'; class TodoItems extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get title => text()(); TextColumn get content => text().nullable()(); } @DriftDatabase(tables: [TodoItems]) class Database extends _$Database { Database(QueryExecutor e) : super(e); @override int get schemaVersion => 1; @override MigrationStrategy get migration { return MigrationStrategy( onCreate: (m) async { await m.createAll(); // Add a bunch of default items in a batch await batch((b) { b.insertAll(todoItems, [ TodoItemsCompanion.insert(title: 'A first entry'), TodoItemsCompanion.insert( title: 'Todo: Checkout drift', content: const Value('Drift is a persistence library for Dart ' 'and Flutter applications.'), ), ]); }); }, ); } // The TodoItem class has been generated by drift, based on the TodoItems // table description. // // In drift, queries can be watched by using .watch() in the end. // For more information on queries, see https://drift.simonbinder.eu/docs/getting-started/writing_queries/ Stream<List<TodoItem>> get allItems => select(todoItems).watch(); } Future<void> main() async { // Create an in-memory instance of the database with todo items. final db = Database(NativeDatabase.memory()); db.allItems.listen((event) { print('Todo-item in database: $event'); }); // Add another entry await db .into(db.todoItems) .insert(TodoItemsCompanion.insert(title: 'Another entry added later')); // Delete all todo items await db.delete(db.todoItems).go(); /////
Here are a few key takes on using Moor (Drift):
It produces strongly typed results, which reduce the chances of runtime errors. It also integrates code generation to handle most of the heavy lifting involved in writing our queries. Also, it is feature rich and supported on both Android, iOS, MacOS, web, desktop, and Linux.
To read extensively about Drift, you can explore their official documentation website here.
Inspired by the Room persistence package, Floor provides a nice SQLite abstraction for your Flutter apps. It provides automatic mapping between in-memory objects and database rows, as well as full control over the database via SQL. As a result, a thorough grasp of SQL and SQLite is required to fully use Floor’s capabilities.
In order to use Floor, there are basically six steps you need to take:
@entity
annotation and is a representation or blueprint of what we want our database table to look like:// entity/person.dartThe Data Access Object simply gives us access to the underlying SQLite database. It has an abstract class that defines the method signatures that we need to work with, and they all return a Future
or Stream
:
// dao/person_dao.dart import 'package:floor/floor.dart'; @dao abstract class PersonDao { @Query('SELECT * FROM Person') Future<List<Person>> findAllPersons(); @Query('SELECT * FROM Person WHERE id = :id') Stream<Person?> findPersonById(int id); @insert Future<void> insertPerson(Person person); } ///
Flutter packages pub run build_runner build
. To automatically run it, whenever a file changes, use flutter packages pub run build_runner watch
.$FloorAppDatabase
class, which provides us access to the database. The name is being composed by $Floor
and the database class name. The string passed to databaseBuilder()
will be the database file name. For initializing the database, call build()
and make sure to await the result.In order to retrieve the PersonDao
instance, calling the personDao
getter on the database instance would suffice. Its functions can be used as shown below:
final database = await $FloorAppDatabase.databaseBuilder('app_database.db').build(); final personDao = database.personDao; final person = Person(1, 'Frank'); await personDao.insertPerson(person); final result = await personDao.findPersonById(1); ///
To get more information about Floor, you can checkout the official example repo on GitHub here.
This is slightly different from relational databases, with nonrelational databases, data is stored in a nontabular form. Storage is based on a structured document-like format and can handle detailed information while storing a wide variety of data formats.
When it comes to the NoSQL databases in Flutter there are a few and very promising options to consider, and the most popular of them is the Google Firebase, which is an online leverages on cloud storage and we also have other user tailored options such as Objectbox, Hive, and SharedPreferences.
To learn more detailed information about Firebase and also get your hands on their codelabs, you can head over to the FlutterFire Overview page here.
One of the major perks of using Firebase that I personally love is the fact that storage location is cloud based, and this means we can synchronize data across multiple devices, instead of saving them to a user’s specific device.
Firebase provides more than one option to store data: we have Firebase Storage, Firebase Firestore, and the Realtime Database. Each of these can be chosen based on your use case and the type of data to be stored. For simple document storage, the Firebase Firestore works very well.
Firebase also has a free plan, and most other advanced features would need to be paid for, but overall, Firebase is great and fairly easy to integrate.
Hive is a lightweight and blazing fast key-value database written in pure Dart.
Hive is one of the storage plugins with the highest number of likes on the pub.dev website, and the reason a lot of people love it is of how easy it is to use.
Here’s an example snippet of how to set it up and begin using it immediately in your project:
var box = Hive.box('myBox'); box.put('name', 'David'); var name = box.get('name'); print('Name: $name');
Looks too easy, right? Well, that is why it is one of the most widely used in the Flutter community.
Aside from storing key-value pairs, Hive can also be used to store objects:
@HiveType(typeId: 0) class Person extends HiveObject { @HiveField(0) String name; @HiveField(1) int age; } var box = await Hive.openBox('myBox'); var person = Person() ..name = 'Dave' ..age = 22; box.add(person); print(box.getAt(0)); // Dave - 22 person.age = 30; person.save();
To get more info, check out the pub.dev package page or Hive docs, and if you encounter any issues, be sure to open an issue on the repository issues page, or check to see if someone has had such an issue before now.
ObjectBox is a super fast database for storing objects locally in Flutter.
It has some good features, just like a vast majority of others, some of which include, scalability, being statically typed, being multiplatform (which means it can run on Android, iOS, web, and desktop), and performing well with memory.
To get more information on how to use and implement ObjectBox for local storage in your application, check out the official tutorial here.
shared_preferences is one of the most common ways mobile developers store key-value pairs locally on their application, and this is a relatively easier and faster option.
The only drawback of using shared_preferences is that it is not advisable for storing large chunks of data, and lists.
In order to use shared_preferences in Flutter, you simply add the dependencies and run the Flutter pub get
command. After that, you create an instance of SharedPreferences
and await it, since it returns a Future
.x
After that, you use the variable type callbacks that the instance provides to save it, and use a similar callback to retrieve your data when needed. This would look something like this:
Future<SharedPreferences> _prefs = SharedPreferences.getInstance(); _prefs.setInt("counter", 1); var result = _prefs.getInt("counter");
For more detailed information, check out the pub.dev page here.
Flutter is increasing in popularity, but there are not so many options for storing data. However, the packages available can serve your need notwithstanding. The above guide has shown us some options and key points for consideration.
For instance, if you needed to store data and provide sync across different devices, you would have to go with Firebase, and if you are not going to be storing data continuously, Hive or shared_preferences might seem like a good choice.
It all boils down to your use case and application needs.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
One Reply to "Choosing the right database for your Flutter application"
Very informative article. One sentence caught my attention about Hive “and if you are not going to be storing data continuously, Hive or shared_preferences might seem like a good choice”. Is Hive not considered as a good choice for transactional data storage (e.g. order management app)?