Published on

|

5 mins

Exploring Flutter's Floor Library for Efficient Data Persistence

Prithviraj Kapil
Prithviraj Kapil

Dive into the concept of local data persistence in Flutter mobile apps and introduces the Floor library as a powerful tool for achieving this goal. Local data persistence is vital for storing data on a user's device, ensuring data availability and performance. The Floor library streamlines the process by providing an abstraction layer for SQLite databases in Flutter.
Cover Image for Exploring Flutter's Floor Library for Efficient Data Persistence

In the dynamic realm of mobile application development, efficient data management is crucial for delivering a seamless user experience. Flutter, with its rich ecosystem of libraries, offers developers powerful tools to tackle various challenges. One such gem is the Floor library, a SQLite abstraction that simplifies data persistence in Flutter apps.

Introduction:

When building Flutter applications, one often encounters the need to store and retrieve data efficiently. Whether it's user preferences, application settings, or complex datasets, the ability to persistently store information is fundamental. The Floor library, inspired by the Room Persistence Library in Android development, provides a robust and developer-friendly solution for handling SQLite databases in Flutter.

Importance of persistance in mobile apps.

  1. Offline Functionality: Mobile apps are expected to function seamlessly, even in offline mode. Whether users are traveling through areas with poor connectivity or intentionally disconnecting, having locally stored data ensures uninterrupted access to critical information.

  2. Improved Performance: By storing frequently accessed data locally, Flutter apps can significantly enhance performance. Local storage reduces the need for continuous network requests, resulting in faster load times and a smoother user experience.

  3. User Preferences and Settings: Persistent storage is vital for maintaining user preferences and application settings. Whether it's remembering theme choices, language preferences, or personalized settings, Flutter's data persistence capabilities empower developers to create personalized and user-friendly experiences.

  4. Secure Data Handling: Storing sensitive information securely is paramount. The Floor library, through its integration with SQLite, provides a secure environment for handling data, ensuring that confidential information remains protected.

  5. Scalability and Manageability: As Flutter apps evolve, so does the complexity of data management. A reliable data persistence solution simplifies the process of scaling an application, making it easier to manage and maintain as the project grows

Use case:

Let's get our hands dirty with code and implement local data persistence in our flutter app using floor library. Let us create a flutter app which would locally store student's data. Below is the step by step guide to carry out the same

1. Setting up dependencies.

Add the runtime dependency floor as well as the generator floor_generator to your pubspec.yaml. The third dependency is build_runner which has to be included as a dev dependency just like the generator.

  1. floor holds all the code you are going to use in your application.

  2. floor_generator includes the code for generating the database classes.

  3. build_runner enables a concrete way of generating source code files.

This is how your pubspec.yaml should look like:


dependencies:
  flutter:
    sdk: flutter
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  floor: ^1.4.2
  sqflite: ^2.3.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  floor_generator: ^1.4.2
  build_runner: ^2.1.2

2. Creating an entity.

It will represent a database table as well as the scaffold of your business object. @entity marks the class as a persistent class. It's required to add a primary key to your table. You can do so by adding the @primaryKey annotation to an int property. There is no restriction on where you put the file containing the entity.

import 'package:floor/floor.dart';

@Entity(tableName: 'students')
class Student {
  @PrimaryKey(autoGenerate: true)
  final int id;

  @ColumnInfo(name: 'name')
  final String name;

  @ColumnInfo(name: 'age')
  final int age;

  Student(this.id, this.name, this.age);
}

@ColumnInfo entity gives more information about the variable which is added as a column in the table. Followed by this is a constructor to create a Student.

3. Create database

Create a class that extends FloorDatabase and annotate it with @Database. This class will represent your database and contain methods for accessing the database:

import 'dart:async';
import 'package:floor/floor.dart';
import 'package:sqflite/sqflite.dart' as sqflite;

import '../dao/dao.dart';
import '../entity/student_entity.dart';

// This is generated by the floor generator
part 'student_db.g.dart';

@Database(version: 1, entities: [Student])
abstract class AppDatabase extends FloorDatabase {
  StudentDao get studentDao;
}

4. Create DAO

Create an interface with methods for database operations (e.g., insert, query). Annotate it with @dao:

import 'package:floor/floor.dart';
import 'package:student_app/floor/entity/student_entity.dart';

@dao
abstract class StudentDao {
  @Query('SELECT * FROM students')
  Future<List<Student>> findAllStudents();

  @Insert(onConflict: OnConflictStrategy.replace)
  Future<void> insertStudent(Student student);
}

Creating an abstract class with @dao annotation provides a level of abstraction thus, you do not have to manually write the functions to insert or get data from the database. You can use various other queries such as @Query to run any SQL query. Floor also provides you with annotations to carry out basic database functions such as @Insert. onConflictStrategy is the strategy followed by the database in case of a conflict. Replace will replace the student entry with the incoming entry in case of conflict.

5. Run code generator

Run the code generation to generate the required files for the database. Use the above provided command.

flutter packages pub run build_runner build

flutter packages pub run build_runner buildThis command will generate a student_db.g file which must be added as a part file to the student_db file, like this

import 'dart:async';
import 'package:floor/floor.dart';
import 'package:sqflite/sqflite.dart' as sqflite;

import '../dao/dao.dart';
import '../entity/student_entity.dart';

// This is generated by the floor generator
part 'student_db.g.dart';

6. Creating app

Now, for the mobile application, we create 2 screens i.e. HomeScreen where we utilize the findAllStudents() function to get data of all the students in the database. We declare a database variable like this , and then we create a function to initialize this variable

var database;
Future<List<Student>> getDatabase() async {
    database = await $FloorAppDatabase.databaseBuilder('student_database.db').build();
    return database.studentDao.findAllStudents();
}

The above function initializes the database variable and returns a list of all students added to the database. Further in the screen


FutureBuilder(
          future: getDatabase(),
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            if (snapshot.hasData) {
              final List<Student> students = snapshot.data;
              if (students.isNotEmpty) {
                return ListView.separated(
                  shrinkWrap: true,
                    itemBuilder: (ctx, index) {
                      return ListTile(
                        leading: Text('${index+1}'),
                        title: Text('Name : ${students[index].name}'),
                        subtitle: Text('Age: ${students[index].age}'),
                      );
                    },
                    separatorBuilder: (ctx, index){
                      return const Divider(color: Colors.black12,);
                    },
                    itemCount: students.length);
              }
              else {
                return const Center(child: Text('No students added yet!'),);
              }
            } else if (snapshot.hasError) {
              return Center(child: Text(snapshot.error.toString()));
            } else {
              return const Center(child: CircularProgressIndicator());
            }
          }),

We use the FutureBuilder( ) widget in flutter to call a future function, our getDatabase( ) function, and then the snapshot data retrieved is utilized in the screen. Further, using the FloatingActionButton widget in the Scaffold widget in flutter, we pass this database object in the constructor of the AddStudentScreen( ). Using this object, we utilize the insertStudent( ) function in the database to add the Student object created in the screen.

OutlinedButton(
              onPressed: () {
                Student student = Student(0, nameController.text, int.parse(ageController.text));
                widget.database.studentDao.insertStudent(student);
                Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (ctx) => const HomeScreen()), (route) => false);
              },
              child: const Text('Save')
          )

After adding the student object to the database, we navigate to the home screen where our added student is visible.

Advantages of floor

  1. null-safe

  2. typesafe

  3. reactive

  4. lightweight

  5. SQL centric