Flutter Data Persistence

7 mins

7 mins

Ashutosh

Published on Oct 22, 2024

Solving Data Persistence Challenges in Flutter with SQLite

Data Persistence with SQLite

Data persistence is a critical aspect of app development, particularly when creating applications that need to retain user data across sessions. Whether you’re developing a note-taking app, a task manager, or even a more complex system like a personal finance tracker, the ability to save and retrieve data efficiently is key to providing a seamless user experience. In Flutter, SQLite is a popular choice for handling data persistence due to its performance, reliability, and cross-platform compatibility. In our previous blogs, we discussed different methods of data persistence in Flutter, such as storing key-value data on disk with SharedPreferences and reading and writing files for data persistence.

In this blog, we’ll explore how to master data persistence in Flutter using SQLite. We’ll cover everything from setting up SQLite in Flutter to writing and querying data, as well as optimizing performance for real-world use cases.

What is SQLite?

SQLite is a lightweight, serverless relational database management system (RDBMS) that is embedded in various software applications, including mobile apps. It provides a structured way to store data in tables, which can be queried using SQL (Structured Query Language). Since SQLite is serverless, it doesn't require a separate server process to manage the database, making it ideal for mobile applications where simplicity and speed are essential.

In Flutter, the sqflite plugin allows developers to use SQLite for local data storage. It provides easy-to-use methods for interacting with the database, allowing you to perform CRUD (Create, Read, Update, Delete) operations.

Why Use SQLite for Data Persistence in Flutter?

SQLite is the preferred database solution for mobile apps due to the following reasons:

  • Lightweight: It doesn’t require server-side operations, making it quick to implement.

  • Cross-platform compatibility: SQLite can be used on both Android and iOS, ensuring consistent data handling across platforms.

  • Structured data: With support for complex queries, SQLite allows developers to maintain and manipulate structured data easily.

  • Offline capability: Apps can work without an internet connection and sync data when connectivity is restored.

Now that we understand why SQLite is an excellent choice, let's dive into implementing SQLite in Flutter.

Setting Up SQLite in Flutter

To get started with SQLite in Flutter, you need to include the sqflite and path packages in your project.

  1. Add dependencies
    Open your pubspec.yaml file and add the following dependencies:

    dependencies:
      flutter:
        sdk: flutter
      sqflite: ^2.0.0+3
      path: ^1.8.0
  2. Install the packages
    Run the following command to install the packages:

flutter pub get

Creating a SQLite Database in Flutter

After setting up the packages, the next step is to create and manage the SQLite database.

  1. Initialize the Database
    To initialize the SQLite database, we need to specify a file path for the database, as SQLite stores databases as files on the device. This can be done using the path package.

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

Future<Database> initDatabase() async {
  return openDatabase(
    join(await getDatabasesPath(), 'my_database.db'),
    onCreate: (db, version) {
      return db.execute(
  'CREATE TABLE items(id INTEGER PRIMARY KEY, name TEXT, quantity INTEGER)',
      );
    },
    version: 1,
  );
}

This code snippet initializes a database with a single table called items that stores an item’s ID, name, and quantity. The onCreate function is responsible for creating the database table when the database is first initialized.

  1. Opening the Database
    You can open the database by calling the initDatabase function at the beginning of your app:

late Database database;

@override
void initState() {
  super.initState();
  initDatabase().then((db) {
    setState(() {
      database = db;
    });
  });
}

Performing CRUD Operations

Once the database is set up, you can perform CRUD operations on the data. These operations allow you to create, read, update, and delete records in the SQLite database.

  1. Inserting Data
    To insert data into the SQLite database, you can use the insert method:

Future<void> insertItem(Item item) async {
  final db = await database;

  await db.insert(
    'items',
    item.toMap(),
    conflictAlgorithm: ConflictAlgorithm.replace,
  );
}

The insertItem function inserts a new item into the items table. The ConflictAlgorithm.replace ensures that if an item with the same ID already exists, it will be replaced.

  1. Reading Data
    To retrieve data from the database, use the query method:

Future<List<Item>> getItems() async {
  final db = await database;

  final List<Map<String, dynamic>> maps = await db.query('items');

  return List.generate(maps.length, (i) {
    return Item(
      id: maps[i]['id'],
      name: maps[i]['name'],
      quantity: maps[i]['quantity'],
    );
  });
}

This function retrieves all items from the items table and returns them as a list of Item objects.

  1. Updating Data
    To update a record in the database, use the update method:

Future<void> updateItem(Item item) async {
  final db = await database;

  await db.update(
    'items',
    item.toMap(),
    where: 'id = ?',
    whereArgs: [item.id],
  );
}

The updateItem function updates the item’s name and quantity based on the id.

  1. Deleting Data
    To delete a record from the database, use the delete method:

Future<void> deleteItem(int id) async {
  final db = await database;

  await db.delete(
    'items',
    where: 'id = ?',
    whereArgs: [id],
  );
}

This function deletes the item with the specified id from the items table.

Optimizing SQLite for Large Datasets

While SQLite is a powerful tool, working with large datasets can sometimes lead to performance issues. Here are a few strategies to optimize SQLite in Flutter:

  1. Use Background Isolates for Large Queries
    Flutter's single-threaded nature means that any time-consuming operation, such as querying large datasets, can cause the UI to freeze. To avoid this, you can use background isolates to run queries in a separate thread.

Future<List<Item>> getItemsInBackground() async {
  return compute(_fetchItemsFromDatabase, database);
}

List<Item> _fetchItemsFromDatabase(Database db) {
  final List<Map<String, dynamic>> maps = db.query('items');
  return List.generate(maps.length, (i) {
    return Item(
      id: maps[i]['id'],
      name: maps[i]['name'],
      quantity: maps[i]['quantity'],
    );
  });
}
  1. Indexing
    Adding an index to frequently queried columns can improve the performance of SELECT queries:

await db.execute('CREATE INDEX idx_item_name ON items(name)');
  1. Pagination
    If you're working with large datasets, consider implementing pagination to load data in smaller chunks rather than fetching all records at once.

Future<List<Item>> getItemsWithPagination(int limit, int offset) async {
  final db = await database;
  final List<Map<String, dynamic>> maps = await db.query(
    'items',
    limit: limit,
    offset: offset,
  );
  return List.generate(maps.length, (i) {
    return Item(
      id: maps[i]['id'],
      name: maps[i]['name'],
      quantity: maps[i]['quantity'],
    );
  });
}

Best Practices for SQLite in Flutter

  1. Avoid Unnecessary Writes: Writing to the database can be an expensive operation. Only write data when necessary to avoid performance hits.

  2. Batch Queries: When you need to perform multiple queries in a row, consider batching them to reduce overhead.

  3. Close the Database: Always ensure that the database is properly closed when it's no longer needed to free up resources.

  4. Use Transactions: For operations that require multiple steps (e.g., inserting multiple records), use transactions to ensure that the database remains in a consistent state:

await db.transaction((txn) async {
  await txn.insert('items', item1.toMap());
  await txn.insert('items', item2.toMap());
});

Conclusion

SQLite is an incredibly powerful and efficient solution for handling data persistence in Flutter. By using the sqflite plugin, you can easily implement robust and efficient local data storage in your Flutter applications. Following best practices, such as using background isolates for large datasets and optimizing database queries, will ensure your app performs smoothly even with large volumes of data.

Flutter and SQLite, when combined, give you the flexibility and power to build apps that can manage data efficiently, offering a seamless experience to your users.