CategoriesAndroidiOSProgramming

Penyimpanan Data Offline di Flutter dengan Floor

Penyimpanan data lokal sangat diperlukan untuk Aplikasi yang membutuhkan akses data secara offline. salah satu metode penyimpanan data secara offline pada aplikasi mobile adalah menggunakan SQLite. SQLite adalah database tanpa server yang berdiri sendiri yang bisa diinstall ke dalam aplikasi mobile. pada Flutter terdapat beberapa library SQLite yang bisa di pakai salah satunya adalah Floor. Floor adalah library yang memanfaatkan SQLite abstraction, mirip dengan Room jika di Android Native. memiliki beberapa kelebihan seperti null-safe, typesafe, reactive, ringan, simpel, dan cross platform (bisa dipakai di android, ios, Linux, MacOS, dan Windows)

kita coba praktekkan implementasi Floor di Flutter, buat satu project Flutter baru.

edit pubspec.yaml, tambahkan dependency berikut untuk menginstall floor

dependencies:
  ...
  floor: ^1.3.0

dev_dependencies:
  ...
  floor_generator: ^1.3.0
  build_runner: ^2.1.2

disini kita perlu menginstall floor_generator dan build_runner untuk men-generate file database yang akan dipakai untuk floor. lakukan pub get untuk menginstall library.

setelah itu buat satu file dart baru untuk Entity, Entity adalah struktur data tabel SQLite. karena kita disini akan membuat aplikasi untuk menyimpan data kontak, kita beri nama filenya contact.dart. isi file tersebut dengan kode berikut :

@entity
class Contact {
  @PrimaryKey(autoGenerate: true)
  final int id;
  final String personName;
  final String phoneNumber;

  Contact(this.id, this.personName, this.phoneNumber);
}

untuk menandai kalau class tersebut adalah Entity, kita gunakan anotasi @entity. kita juga menggunakan anotasi @PrimaryKey untuk menandai variabel yang berperan sebagai Primary Key yang mana di kode kita Primary Key nya auto generate. jika kita ingin Primary Key nya tidak autoGenerate bisa mengatur autoGenerate nya ke false atau bisa juga menuliskan anotasinya dengan p kecil tanpa ada tanpa kurung ( @primaryKey ). kita juga bisa menentukan nama tabel dan column sendiri agar tidak sama dengan nama class dan variabel dengan kode seperti berikut :

@Entity(tableName: "tb_contact")
class Contact {
  @PrimaryKey(autoGenerate: true)
  final int? id;
  @ColumnInfo(name: "name")
  final String personName;
  @ColumnInfo(name: "phone")
  final String phoneNumber;

  Contact(this.id, this.personName, this.phoneNumber);
}

setelah itu kita buat file untuk DAO (Data Access Object). DAO disini memuat berbagai operasi database seperti Insert, Update, Get, Delete.

Buat satu file dart baru, beri nama contact_dao.dart, lalu isikan dengan kode berikut :

@dao
abstract class ContactDao {
  @Query("SELECT * FROM tb_contact")
  Future<List<Contact>> getAllContact();

  @insert
  Future<void> insertContact(Contact contact);

  @update
  Future<void> updateContact(Contact contact);

  @delete
  Future<void> deleteContact(Contact contact);
}

untuk mendefinisikan class DAO kita perlu anotasi @dao. lalu untuk SQLite Query kita perlu anotasi @Query, anotasi @insert untuk Insert data, @update untuk mengupdate data, dan @delete untuk menghapus data. kita wajib menggunakan Future function karena operasi Floor berjalan secara Asynchronous.

langkah selanjutnya kita harus membuat file untuk konfigurasi database nya. buat satu file baru beri nama app_database.dart lalu isikan dengan kode berikut :

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

import 'contact.dart';
import 'contact_dao.dart';

// wajib
part 'database.g.dart';

@Database(version: 1, entities: [Contact])
abstract class AppDatabase extends FloorDatabase {
  ContactDao get contactDao;
}

disini kita wajib mengimport beberapa package dan menuliskan part ‘app_database.g.dart’; untuk mengimport file yang akan tergenerate nanti, penamaan app_database disini menyesuaikan dengan nama file dart untuk konfigurasi database kita.

disini pasti akan ada error di bagian part ‘app_database.g.dart’; karena kita belum men-generate file tersebut. kita perlu men-generatenya secara manual dengan menuliskan command di terminal / cmd kita. buka terminal / cmd kita, pastikan sudah mengarah ke directory root project kita. tuliskan command berikut di terminal / cmd lalu klik enter.

flutter packages pub run build_runner build

setelah itu error pada file app_database.dart akan hilang dengan sendirinya jika command berhasil dijalankan.

kita perlu menjalankan command diatas lagi jika kita melakukan perubahan di Entity dan Dao. jika tidak ingin menjalankan command tersebut berkali kali, kita bisa menggunakan command dibawah ini.

flutter packages pub run build_runner watch

file app_database.g.dart akan otomatis tergenerate saat kita melakukan perubahan di Entity dan Dao jika kita menggunakan command di atas.

sekarang kita buat file repository yang akan menjadi penghubung antara UI dan Database kita. beri nama contact_repository.dart lalu isikan dengan kode berikut :

class ContactRepository {
  Future<List<Contact>> getAllContact() async {
    final database = await $FloorAppDatabase.databaseBuilder("app_database.db").build();
    final contactDao = database.contactDao;
    List<Contact> result = await contactDao.getAllContact();
    return result;
  }
  
  Future<void> insertContact(Contact contact) async {
    final database = await $FloorAppDatabase.databaseBuilder("app_database.db").build();
    final contactDao = database.contactDao;
    await contactDao.insertContact(contact);
  }

  Future<void> updateContact(Contact contact) async {
    final database = await $FloorAppDatabase.databaseBuilder("app_database.db").build();
    final contactDao = database.contactDao;
    await contactDao.updateContact(contact);
  }

  Future<void> deleteContact(Contact contact) async {
    final database = await $FloorAppDatabase.databaseBuilder("app_database.db").build();
    final contactDao = database.contactDao;
    await contactDao.deleteContact(contact);
  }
}

untuk mendapatkan database kita gunakan

await $FloorAppDatabase.databaseBuilder(“app_database.db”).build();

lalu dari database tersebut kita perlu mendapatkan dao nya, baru kita bisa meng-eksekusi method yang ada di file Dao tersebut.

sekarang kita buat UI untuk aplikasi kita. tambahkan satu class baru untuk membuat halaman Home dengan type Statefull Component. kodenya adalah sebagai berikut :

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  ContactRepository contactRepository = ContactRepository();
  List<Contact> contactList = [];

  @override
  void initState() {
    getContact();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Demo Floor"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => routeToCreateContact(
          null,
          "",
          "",
        ),
        child: const Icon(Icons.add),
      ),
      body: ListView.builder(
        itemCount: contactList.length,
        itemBuilder: (BuildContext context, int index) {
          return InkWell(
            onTap: () => routeToCreateContact(
              contactList[index].id,
              contactList[index].personName,
              contactList[index].phoneNumber,
            ),
            child: Card(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      contactList[index].personName,
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 18.0,
                      ),
                    ),
                    Text(
                      contactList[index].phoneNumber,
                    )
                  ],
                ),
              ),
            ),
          );
        },
      ),
    );
  }

  void routeToCreateContact(int? id, String name, String phone) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => CreateContactPage(
          id: id,
          name: name,
          phone: phone,
        ),
      ),
    ).then((value) {
      getContact();
    });
  }

  void getContact() async {
    contactList.clear();
    contactList.addAll(await contactRepository.getAllContact());
    // refresh UI
    setState(() {});
  }
}

disitu kita membuat halaman yang berisikan AppBar, ListView, dan FloatingActionButton.

disitu kita juga membuat function untuk route/Intent ke halaman CreateContactPage, dalam function route tersebut kita juga mengatur agar tampilan otomatis me refresh saat route kembali ke halaman Home. kita juga menambahkan function untuk mendapatkan kontak yaitu getContact(), disitu kita menggunakan variabel contactList yang sudah di inisialisasi di class HomePage lalu di bagian bawah function kita me refresh halaman dengan setState().

tampilan HomePage akan menjadi seperti berikut :

selanjutnya buat halaman Create Contact. buat class baru dengan type Statefull Component, beri nama CreateContactPage. kode nya adalah seperti berikut :

class CreateContactPage extends StatefulWidget {
  final int? id;
  final String name;
  final String phone;

  const CreateContactPage(
      {Key? key, required this.id, required this.name, required this.phone})
      : super(key: key);

  @override
  State<CreateContactPage> createState() => _CreateContactPageState();
}

class _CreateContactPageState extends State<CreateContactPage> {
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _phoneController = TextEditingController();

  ContactRepository contactRepository = ContactRepository();

  @override
  void initState() {
    _nameController.text = widget.name;
    _phoneController.text = widget.phone;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("${widget.id == null ? "Create" : "Edit"} Contact"),
      ),
      body: Padding(
        padding: const EdgeInsets.all(21.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            TextField(
              controller: _nameController,
              decoration: const InputDecoration(
                  label: Text("Nama"), hintText: "Masukkan Nama Kontak"),
            ),
            const SizedBox(
              height: 12.0,
            ),
            TextField(
              keyboardType: TextInputType.phone,
              controller: _phoneController,
              decoration: const InputDecoration(
                  label: Text("Nomor Telepon"),
                  hintText: "Masukkan Nomor Telepon"),
            ),
            const SizedBox(
              height: 21.0,
            ),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () {
                  if (widget.id == null) {
                    saveContact();
                  } else {
                    updateContact();
                  }
                },
                child: const Text("Simpan"),
              ),
            ),
            const SizedBox(
              height: 12.0,
            ),
            widget.id != null
                ? SizedBox(
                    width: double.infinity,
                    child: ElevatedButton(
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.red,
                      ),
                      onPressed: () {
                        deleteContact();
                      },
                      child: const Text("Hapus"),
                    ),
                  )
                : Container(),
          ],
        ),
      ),
    );
  }

  void saveContact() async {
    if (_nameController.text.isEmpty || _phoneController.text.isEmpty) {
      SnackBar snackBar = const SnackBar(
        content: Text("Isi nama dan nomor telepon terlebih dahulu"),
      );

      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
      }
    } else {
      Contact contact =
          Contact(null, _nameController.text, _phoneController.text);
      await contactRepository.insertContact(contact);
      SnackBar snackBar =
          const SnackBar(content: Text("Berhasil Menambahkan Kontak"));
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
        Navigator.pop(context);
      }
    }
  }

  void updateContact() async {
    if (_nameController.text.isEmpty || _phoneController.text.isEmpty) {
      SnackBar snackBar = const SnackBar(
        content: Text("Isi nama dan nomor telepon terlebih dahulu"),
      );

      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
      }
    } else {
      Contact contact =
          Contact(widget.id!, _nameController.text, _phoneController.text);
      await contactRepository.updateContact(contact);
      SnackBar snackBar =
          const SnackBar(content: Text("Berhasil Mengupdate Kontak"));
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
        Navigator.pop(context);
      }
    }
  }

  void deleteContact() async {
    Contact contact = Contact(widget.id!, widget.name, widget.phone);
    await contactRepository.deleteContact(contact);
    SnackBar snackBar =
        const SnackBar(content: Text("Berhasil Menghapus Kontak"));
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(snackBar);
      Navigator.pop(context);
    }
  }

  @override
  void dispose() {
    _phoneController.dispose();
    _nameController.dispose();
    super.dispose();
  }
}

di halaman ini ada 2 mode yaitu mode Create dan Edit. yang mana mode Create akan terbuka saat kita meng klik tombol add di home dan mode Edit saat kita meng klik item pada ListView. pada mode Create kita hanya bisa insert kontak, dan mode Edit kita bisa update dan delete kontak. tampilan halaman ini adalah seperti berikut


Mode Create

Mode Edit

sekarang coba run project. hasilnya akan seperti berikut.


Tampilan Awal

Isi Data Kontak Mode Create

Berhasil Insert Data

Tampilan Edit di Mode Edit

Berhasil Mengupdate Data

Berhasil Menghapus Kontak

cukup mudah bukan menyimpan data dengan menggunakan Floor.
kalau mau meng explore lagi mengenai library Floor bisa menuju link di bawah ini

https://pub.dev/packages/floor

Terima kasih sudah mampir di Artikel saya, semoga bermanfaat.

Published by Ahmad Saifur Ridlo

Android Developer at Algostudio.net

Leave a Reply

Your email address will not be published. Required fields are marked *