Guidance on Selecting the Appropriate Local Database Solution in Flutter

Developing mobile applications with Flutter often requires storing data locally on the device. Selecting the right local database solution is crucial for performance, scalability, and maintainability. This guide will walk you through the available options and help you choose the best solution for your Flutter project.

Why Use a Local Database in Flutter?

  • Offline Access: Enables the app to function without an internet connection.
  • Fast Data Retrieval: Provides quicker access to frequently used data.
  • Reduced Latency: Minimizes data retrieval latency compared to remote databases.
  • Data Privacy: Keeps sensitive data securely on the user’s device.

Common Local Database Solutions in Flutter

1. SQLite

SQLite is a widely used, lightweight, disk-based database. It is supported by almost every platform and is a reliable choice for structured data storage.

Pros:
  • Standard and Mature: Well-established with extensive community support.
  • Lightweight: Low overhead, ideal for mobile devices.
  • SQL Support: Allows complex queries using SQL.
  • Transaction Support: Ensures data integrity with ACID compliance.
Cons:
  • Verbosity: Requires writing SQL queries, which can be verbose and error-prone.
  • Asynchronous Operations: Needs proper handling of asynchronous operations to prevent UI blocking.
Implementation with sqflite package

To use SQLite in Flutter, you can use the sqflite package.

Step 1: Add Dependency
dependencies:
  sqflite: ^2.0.0+4
Step 2: Create a Database Helper Class

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper {
  static Database? _database;

  Future get database async {
    if (_database != null) return _database!;

    _database = await _initDatabase();
    return _database!;
  }

  Future _initDatabase() async {
    String path = join(await getDatabasesPath(), 'my_database.db');
    return await openDatabase(
      path,
      version: 1,
      onCreate: _onCreate,
    );
  }

  Future _onCreate(Database db, int version) async {
    await db.execute('''
      CREATE TABLE my_table (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        value INTEGER
      )
    ''');
  }

  Future insert(Map row) async {
    final db = await database;
    return await db.insert('my_table', row);
  }

  Future>> queryAll() async {
    final db = await database;
    return await db.query('my_table');
  }
}
Step 3: Usage

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final dbHelper = DatabaseHelper();

  // Insert data
  await dbHelper.insert({'name': 'Example', 'value': 123});

  // Query data
  List> results = await dbHelper.queryAll();
  print(results); // Output: [{id: 1, name: Example, value: 123}]
}

2. Shared Preferences

Shared Preferences is a simple way to store key-value pairs of primitive data types. It is suitable for storing small amounts of data such as user settings or flags.

Pros:
  • Simple API: Easy to use for basic data storage.
  • Built-in: Requires no external dependencies.
  • Synchronous Access: Provides simple synchronous access to data.
Cons:
  • Limited Data Types: Supports only primitive types (int, bool, String, etc.).
  • Not Suitable for Complex Data: Inefficient for storing structured data or large datasets.
  • No Querying: Lacks querying capabilities.
Implementation with shared_preferences package
Step 1: Add Dependency
dependencies:
  shared_preferences: ^2.0.0
Step 2: Usage

import 'package:shared_preferences/shared_preferences.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final prefs = await SharedPreferences.getInstance();

  // Set data
  await prefs.setInt('my_int', 123);
  await prefs.setString('my_string', 'Hello, World!');

  // Get data
  final myInt = prefs.getInt('my_int') ?? 0;
  final myString = prefs.getString('my_string') ?? '';

  print('Int: \$myInt, String: \$myString'); // Output: Int: 123, String: Hello, World!
}

3. Hive

Hive is a lightweight NoSQL database solution written in pure Dart. It offers excellent performance and is well-suited for storing structured data.

Pros:
  • Pure Dart: Works on all platforms supported by Flutter.
  • NoSQL: Stores data in key-value pairs, suitable for flexible data structures.
  • Fast: High read and write performance.
  • Encryption: Supports data encryption.
Cons:
  • Limited Querying: Lacks advanced querying capabilities compared to SQL databases.
  • Smaller Community: Smaller community compared to SQLite.
Implementation with hive package
Step 1: Add Dependency
dependencies:
  hive: ^2.0.0
  hive_flutter: ^1.0.0
dev_dependencies:
  build_runner: ^2.0.0
  hive_generator: ^1.0.0
Step 2: Initialize Hive

import 'package:hive_flutter/hive_flutter.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Hive.initFlutter();
  await Hive.openBox('myBox');

  // Put data
  var box = Hive.box('myBox');
  box.put('myInt', 456);
  box.put('myString', 'Hive Data');

  // Get data
  final myInt = box.get('myInt');
  final myString = box.get('myString');

  print('Int: \$myInt, String: \$myString'); // Output: Int: 456, String: Hive Data
}

4. Isar

Isar is a super-fast, asynchronous, and strongly-typed NoSQL database for Flutter. It is designed to provide excellent performance and ease of use.

Pros:
  • Asynchronous: High performance with non-blocking operations.
  • Strongly Typed: Ensures type safety.
  • NoSQL: Flexible data structures.
  • Easy to Use: Simple and intuitive API.
Cons:
  • Newer Solution: Relatively new compared to other solutions like SQLite.
  • Limited Community: Smaller community and less extensive documentation.
Implementation with isar package
Step 1: Add Dependency
dependencies:
  isar: ^3.0.0
  isar_flutter_libs: ^3.0.0
dev_dependencies:
  isar_generator: ^3.0.0
  build_runner: ^2.0.0
Step 2: Define a Model

import 'package:isar/isar.dart';

part 'my_model.g.dart';

@Collection()
class MyModel {
  Id? id;
  String? name;
  int? value;
}
Step 3: Initialize and Use Isar

import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'my_model.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final dir = await getApplicationDocumentsDirectory();
  final isar = await Isar.open(
    [MyModelSchema],
    directory: dir.path,
  );

  // Create data
  final newModel = MyModel()
    ..name = 'Isar Example'
    ..value = 789;

  await isar.writeTxn(() async {
    await isar.myModels.put(newModel);
  });

  // Query data
  final myModel = await isar.myModels.where().nameEqualTo('Isar Example').findFirst();

  print('Name: \${myModel?.name}, Value: \${myModel?.value}'); // Output: Name: Isar Example, Value: 789
}

5. Moor (Now Drift)

Moor (now known as Drift) is an SQL database library for Flutter, offering compile-time checks, type-safe queries, and an easy way to interact with SQLite databases.

Pros:
  • Type Safety: Ensures type safety with compile-time checks.
  • Code Generation: Generates Dart code from SQL schema.
  • Asynchronous: Fully asynchronous operations.
  • SQL Integration: Provides a seamless SQL integration with Dart code.
Cons:
  • Complexity: Can be more complex to set up compared to simpler solutions.
  • Learning Curve: Requires understanding of SQL and Dart integration.
Implementation with Drift
Step 1: Add Dependencies
dependencies:
  drift: ^2.0.0
  sqlite3_flutter_libs: ^0.5.0 
dev_dependencies:
  drift_dev: ^2.0.0
  build_runner: ^2.0.0
Step 2: Define Database Schema

Create a Dart file to define your database schema.


import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart' as path_provider;
import 'dart:io';
import 'package:path/path.dart' as p;

part 'database.g.dart';

class MyTable extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text().nullable()();
  IntColumn get value => integer().nullable()();
}

@DriftDatabase(tables: [MyTable])
class MyDatabase extends _$MyDatabase {
  MyDatabase() : super(_openConnection());

  @override
  int get schemaVersion => 1;
}

LazyDatabase _openConnection() {
  return LazyDatabase(() async {
    final dbFolder = await path_provider.getApplicationDocumentsDirectory();
    final file = File(p.join(dbFolder.path, 'db.sqlite'));
    return NativeDatabase(file);
  });
}
Step 3: Usage

import 'package:flutter/widgets.dart';
import 'package:drift_example/database.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final database = MyDatabase();

  // Insert data
  final newRow = MyTableCompanion(
    name: Value('Drift Example'),
    value: Value(901),
  );
  await database.into(database.myTable).insert(newRow);

  // Query data
  final results = await database.select(database.myTable).get();
  results.forEach((row) {
    print('Id: \${row.id}, Name: \${row.name}, Value: \${row.value}');
  });

  // Close the database
  database.close();
}

Choosing the Right Solution

Consider the following factors when selecting a local database solution:

  • Data Complexity: For simple key-value pairs, Shared Preferences may suffice. For structured data, consider SQLite, Hive, Isar, or Moor.
  • Performance Requirements: If performance is critical, Isar and Hive are excellent choices. Moor offers compile-time safety with good performance.
  • Scalability: For larger datasets and complex relationships, SQLite (with proper ORM) or Moor might be more appropriate.
  • Ease of Use: Shared Preferences is the simplest to use. Hive and Isar offer a balance of ease of use and performance.
  • Community and Support: SQLite has the largest community. Hive, Isar, and Moor are growing but have smaller communities.

Summary Table

Solution Data Type Pros Cons Use Case
SQLite Relational Mature, SQL support, transaction support Verbose, requires SQL Complex data, structured storage
Shared Preferences Key-Value (Primitives) Simple, built-in Limited data types, no querying Simple settings, small data
Hive Key-Value (NoSQL) Pure Dart, fast, encryption Limited querying, smaller community Structured data, performance critical
Isar Key-Value (NoSQL) Asynchronous, strongly typed, easy to use Newer, limited community High performance, structured data
Drift (Moor) SQL Type safety, code generation, SQL integration Complex, learning curve Type-safe SQL, compile-time checks

Conclusion

Choosing the appropriate local database solution in Flutter depends on your project’s specific requirements. SQLite provides robust relational storage, while Shared Preferences is suitable for simple settings. Hive and Isar offer excellent performance for structured data, and Drift provides type-safe SQL integration. By considering data complexity, performance needs, and ease of use, you can select the solution that best fits your application’s needs.