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
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.