Mengelola Database pada Aplikasi Flutter dengan Studi Kasus Membuat Aplikasi Buku Tamu

Bagikan jika Anda sukai postingan ini!

Aplikasi Buku Tamu
Aplikasi Buku Tamu

Seperti yang saya sampaikan pada artikel sebelumnya, pembahasan kali ini saya akan mencoba menjelaskan cara menggunakan database pada Flutter, dengan studi kasus pembuatan aplikasi Buku Tamu. Aplikasi ini terinspirasi dari challenge Dicoding: Digital Guest Book. Aplikasi ini terdiri dari dua fitur yaitu pencatatan acara (event) dan pencatatan tamu (guest) untuk tiap-tiap acara.

Kita langsung saja ke pembahasannya. Silakan buat proyek baru dengan Flutter. Setelah proyek siap, buka berkas pubspec.yaml. Saya akan menambahkan beberapa pustaka ke dalam proyek, yaitu: sqflite, intl, dan fluttertoast. Pustaka sqflite digunakan untuk mengelola database, intl digunakan untuk pelokalan (sebetulnya tidak sempat digunakan pada proyek ini), dan fluttertoast digunakan untuk menampilkan toast pada aplikasi dengan Flutter.

Database lokal pada flutter menggunakan sqflite, mirip dengan SQLite. Mungkin nama SQFLite ini diambil dari akronim SQLite for Flutter.

Baik, pada pubspec.yaml tambahkan baris di bawah ini setelah baris dependencies:.

  sqflite: ^1.1.6+1
  intl: 0.15.8
  fluttertoast: ^3.1.0

Kemudian klik Packages get pada batang di atas jendela kode pubspec.yaml. Setelah proses penambahan pustaka selesai, tambahkan beberapa berkas dart baru pada proyek, yaitu: model.dart, database.dart, event.dart, guests.dart, dan guest.dart, sehingga kita mempunyai 6 (enam) buah berkas dart termasuk main.dart.

Berkas model.dart saya akan gunakan untuk menulis kelas-kelas obyek yang akan digunakan pada proyek. Berikut ini kode lengkap dari model.dart.

import 'dart:convert';

/* Kelas untuk mengelola obyek event/acara */
class EventItem {
   int eventId;
   int createDate;
   int eventDate;
   int eventTime;
   String eventName;
   String eventAddress;
   int isCompleted;

   EventItem(
       {
         this.eventId,
         this.createDate,
         this.eventDate,
         this.eventTime,
         this.eventName,
         this.eventAddress,
         this.isCompleted
       }
    );

   /* Fungsi untuk mengonversi JSON ke EventItem */
   factory EventItem.fromJson(Map<String, dynamic> json) {
     return new EventItem (
       eventId: json['eventId'] as int,
       createDate: json['createDate'] as int,
       eventDate: json['eventDate'] as int,
       eventTime: json['eventTime'] as int,
       eventName: json['eventName'] as String,
       eventAddress: json['eventAddress'] as String,
       isCompleted: json['isCompleted'] as int,
     );
   }

   /* Fungsi untuk mengonversi EventItem ke JSON */
   Map<String, dynamic> toJson() => {
     'eventId': eventId,
     'createDate': createDate,
     'eventDate': eventDate,
     'eventTime': eventTime,
     'eventName': eventName,
     'eventAddress': eventAddress,
     'isCompleted': isCompleted,
   };

   /* Fungsi untuk mengonversi EventItem ke tipe data Map.
      Tidak berbeda dengan fungsi toJson sebetulnya.
      Ini digunakan untuk mengirim obyek ke tabel database
   */
   Map<String, dynamic> toMap() {
     return {
       'eventId': eventId,
       'createDate': createDate,
       'eventDate': eventDate,
       'eventTime': eventTime,
       'eventName': eventName,
       'eventAddress': eventAddress,
       'isCompleted': isCompleted,
     };
   }

   /* Fungsi untuk mengonversi EventItem ke Map.
      Tidak berbeda dengan toMap hanya saja tidak menyertakan eventId.
      Ini digunakan sebagai obyek masukan ke tabel
      di mana eventId bersifat AUTOINCREMENT
   */
   Map<String, dynamic> toMapInsert() {
     return {
       'createDate': createDate,
       'eventDate': eventDate,
       'eventTime': eventTime,
       'eventName': eventName,
       'eventAddress': eventAddress,
       'isCompleted': isCompleted,
     };
   }

   /* Fungsi untuk mengonversi Map ke EventItem.
      Ini digunakan untuk menampung baris (row) data
      dari tabel database
   */
   EventItem.fromMap(Map<String, dynamic> map) {
     eventId = map['eventId'];
     createDate = map['createDate'];
     eventDate = map['eventDate'];
     eventTime = map['eventTime'];
     eventName = map['eventName'];
     eventAddress = map['eventAddress'];
     isCompleted = map['isCompleted'];
   }

   /* Fungsi untuk mengonversi EventItem ke String.
      Ini digunakan untuk menyimpan obyek ke SharedPreferences
   */
   @override
   String toString() {
     var jsonData = json.encode(toJson());
     return jsonData;
   }
}

/* Kelas untuk mengelola obyek guest/tamu */
class GuestItem {
  int guestId;
  int eventId;
  String guestFullName;
  String guestNoPhone;
  String guestEmail;
  String guestAddress;
  String guestNote;
  int guestVisitTime;

  GuestItem(
      {
        this.guestId,
        this.eventId,
        this.guestFullName,
        this.guestNoPhone,
        this.guestEmail,
        this.guestAddress,
        this.guestNote,
        this.guestVisitTime,
      }
   );

  /* Fungsi untuk mengonversi JSON ke GuestItem */
  factory GuestItem.fromJson(Map<String, dynamic> json) {
    return new GuestItem (
      guestId: json['guestId'] as int,
      eventId: json['eventId'] as int,
      guestFullName: json['guestFullName'] as String,
      guestNoPhone: json['guestNoPhone'] as String,
      guestEmail: json['guestEmail'] as String,
      guestAddress: json['guestAddress'] as String,
      guestNote: json['guestNote'] as String,
      guestVisitTime: json['guestVisitTime'] as int,
    );
  }

  /* Fungsi untuk mengonversi GuestItem ke JSON */
  Map<String, dynamic> toJson() => {
    'guestId': guestId,
    'eventId': eventId,
    'guestFullName': guestFullName,
    'guestNoPhone': guestNoPhone,
    'email': guestEmail,
    'guestAddress': guestAddress,
    'guestNote': guestNote,
    'guestVisitTime': guestVisitTime,
  };

  /* Fungsi untuk mengonversi GuestItem ke tipe data Map.
     Tidak berbeda dengan fungsi toJson sebetulnya.
     Ini digunakan untuk mengirim obyek ke tabel database
  */
  Map<String, dynamic> toMap() {
    return {
      'guestId': guestId,
      'eventId': eventId,
      'guestFullName': guestFullName,
      'guestNoPhone': guestNoPhone,
      'guestEmail': guestEmail,
      'guestAddress': guestAddress,
      'guestNote': guestNote,
      'guestVisitTime': guestVisitTime,
    };
  }

  /* Fungsi untuk mengonversi GuestItem ke Map.
     Tidak berbeda dengan toMap hanya saja tidak menyertakan guestId.
     Ini digunakan sebagai obyek masukan ke tabel
     di mana guestId bersifat AUTOINCREMENT
  */
  Map<String, dynamic> toMapInsert() {
    return {
      'eventId': eventId,
      'guestFullName': guestFullName,
      'guestNoPhone': guestNoPhone,
      'guestEmail': guestEmail,
      'guestAddress': guestAddress,
      'guestNote': guestNote,
      'guestVisitTime': guestVisitTime,
    };
  }

  /* Fungsi untuk mengonversi Map ke GuestItem.
     Ini digunakan untuk menampung baris (row) data
     dari tabel database
  */
  GuestItem.fromMap(Map<String, dynamic> map) {
    guestId = map['guestId'];
    eventId = map['eventId'];
    guestFullName = map['guestFullName'];
    guestNoPhone = map['guestNoPhone'];
    guestEmail = map['guestEmail'];
    guestAddress = map['guestAddress'];
    guestNote = map['guestNote'];
    guestVisitTime = map['guestVisitTime'];
  }

  /* Fungsi untuk mengonversi GuestItem ke String.
     Ini digunakan untuk menyimpan obyek ke SharedPreferences
  */
  @override
  String toString() {
    var jsonData = json.encode(toJson());
    return jsonData;
  }
}

/* Kelas untuk mengelola obyek kolom/field dari tabel database */
class ColumnItem {
  final String name;
  final String type;
  final String extra;

  const ColumnItem(
      {
        this.name,
        this.type,
        this.extra,
      }
  );
}

/* Daftar konstanta untuk menentukan kolom-kolom pada tabel event di database.
   Terdiri dari 3 argumen:
   - name : menentukan nama kolom
   - type : menentukan tipe data kolom
   - extra : menentukan argumen pembuatan kolom lainnya
*/
const List<ColumnItem> eventColumns = const <ColumnItem>[
  const ColumnItem(name: "eventId", type: "INTEGER", extra: "PRIMARY KEY AUTOINCREMENT"),
  const ColumnItem(name: "createDate", type: "INTEGER", extra: ""),
  const ColumnItem(name: "eventDate", type: "INTEGER", extra: ""),
  const ColumnItem(name: "eventTime", type: "INTEGER", extra: ""),
  const ColumnItem(name: "eventName", type: "VARCHAR(50)", extra: ""),
  const ColumnItem(name: "eventAddress", type: "VARCHAR(500)", extra: ""),
  const ColumnItem(name: "isCompleted", type: "INTEGER", extra: ""),
];

/* Daftar konstanta untuk menentukan kolom-kolom pada tabel guest di database */
const List<ColumnItem> guestColumns = const <ColumnItem>[
  const ColumnItem(name: "guestId", type: "INTEGER", extra: "PRIMARY KEY AUTOINCREMENT"),
  const ColumnItem(name: "eventId", type: "INTEGER", extra: ""),
  const ColumnItem(name: "guestFullName", type: "VARCHAR(50)", extra: ""),
  const ColumnItem(name: "guestNoPhone", type: "VARCHAR(20)", extra: ""),
  const ColumnItem(name: "guestEmail", type: "VARCHAR(50)", extra: ""),
  const ColumnItem(name: "guestAddress", type: "VARCHAR(500)", extra: ""),
  const ColumnItem(name: "guestNote", type: "TEXT", extra: ""),
  const ColumnItem(name: "guestVisitTime", type: "INTEGER", extra: ""),
];

/* Fungsi unttuk mengambil nama kolom */
List<String> getColumnsName(List<ColumnItem> columnItems){
  List<String> result = new List();
  for (ColumnItem columnItem in columnItems){
    result.add(columnItem.name);
  }
  return result;
}

Saya kira komentar-komentar yang saya tulis pada kode sumber sudah cukup jelas ya? Jadi tidak perlu dijelaskan ulang. Selanjutnya berkas database.dart tentu saja akan saya tulisi kode sumber untuk mengelola database. Saya akan membuat 2 (dua) buah tabel pada database yaitu tblEvent yang akan digunakan untuk menyimpan daftar acara, serta tblGuest yang akan digunakan untuk menyimpan daftar tamu.

Di bawah ini kode lengkap dari database.dart.

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

var tableEvent = 'tblEvent';
var tableGuest = 'tblGuest';

class DatabaseProvider {
  Database database;

  /* Fungsi untuk membuka kelas database */
  Future open() async {
    var databasesPath = await getDatabasesPath();
    /* Menentukan nama dan alamat database */
    String path = join(databasesPath, 'guest_book.db');

    /* Membuka database */
    database = await openDatabase(path, version: 1,
      /* Membuat tabel bila belum terdapat tabel pada database */
      onCreate: (Database db, int version) async {
        /* Membangun query pembuatan tabel tblEvent */
        String sqlTableEvent = "CREATE TABLE $tableEvent (";
        for (var i = 0; i < eventColumns.length; i++){
          ColumnItem columnItem = eventColumns[i];
          sqlTableEvent += columnItem.name + " " + columnItem.type + " " + columnItem.extra;
          if (i < (eventColumns.length - 1)){
            sqlTableEvent += ",";
          }
        }
        sqlTableEvent += ");";

        /* Membuat query pembuatan indeks pada table tblEvent */
        String indexTableEvent = " CREATE INDEX idx_" + tableEvent + "_" +
            "row_event" + " ON " + tableEvent + " (" +  eventColumns[0].name + ");";

        /* Membangun query pembuatan tabel tblGuest */
        String sqlTableGuest = "CREATE TABLE $tableGuest (";
        for (var i = 0; i < guestColumns.length; i++){
          ColumnItem columnItem = guestColumns[i];
          sqlTableGuest += columnItem.name + " " + columnItem.type + " " + columnItem.extra;
          if (i < (guestColumns.length - 1)){
            sqlTableGuest += ",";
          }
        }
        sqlTableGuest += ");";

        /* Membuat query pembuatan indeks pada table tblGuest */
        String indexTableGuest = " CREATE INDEX idx_" + tableGuest + "_" +
            "row_guest" + " ON " + tableGuest + " (" + guestColumns[0].name + "," + guestColumns[1].name + ");";

        /* Mengeksekusi query pembuatan tabel-tabel */
        db.execute(sqlTableEvent);
        db.execute(indexTableEvent);
        db.execute(sqlTableGuest);
        db.execute(indexTableGuest);
      },
    );
  }

  /* Fungsi untuk menutup kelas database */
  Future close() async => database.close();

  /* Fungsi untuk menambah baris ke tabel tblEvent */
  Future<EventItem> insertEvent(EventItem event) async {
    event.eventId = await database.insert(tableEvent, event.toMapInsert());
    return event;
  }

  /* Fungsi untuk menghapus baris di tabel tblEvent */
  Future<int> deleteEvent(int id) async {
    return await database.delete(tableEvent, where: eventColumns[0].name + ' = ?', whereArgs: [id]);
  }

  /* Fungsi untuk memperbarui baris di tabel tblEvent */
  Future<int> updateEvent(EventItem event) async {
    return await database.update(tableEvent, event.toMap(),
        where: eventColumns[0].name + ' = ?', whereArgs: [event.eventId]);
  }

  /* Fungsi untuk mengambil baris di tabel tblEvent */
  Future<EventItem> getEvent(int id) async {
    List<Map> maps = await database.query(tableEvent,
        columns: getColumnsName(eventColumns),
        where: eventColumns[0].name + ' = ?',
        whereArgs: [id]);
    if (maps.length > 0) {
      return EventItem.fromMap(maps.first);
    }
    return null;
  }

  /* Fungsi untuk mengambil daftar baris di tabel tblEvent */
  Future<List<EventItem>> getListEvent() async {
    List<Map> maps = await database.query(tableEvent,
        columns: getColumnsName(eventColumns)
    );
    List<EventItem> listEvent = List();
    if (maps.length > 0) {
      for (Map map in maps) {
        EventItem event = EventItem.fromMap(map);
        listEvent.add(event);
      }
      return listEvent;
    }
    return listEvent;
  }

  /* Fungsi untuk menambah baris ke tabel tblGuest */
  Future<GuestItem> insertGuest(GuestItem guest) async {
    guest.guestId = await database.insert(tableGuest, guest.toMapInsert());
    return guest;
  }

  /* Fungsi untuk menghapus baris di tabel tblGuest */
  Future<int> deleteGuest(int id) async {
    return await database.delete(tableGuest, where: guestColumns[0].name + ' = ?', whereArgs: [id]);
  }

  /* Fungsi untuk memperbarui baris ke tabel tblGuest */
  Future<int> updateGuest(GuestItem guest) async {
    return await database.update(tableGuest, guest.toMap(),
        where: guestColumns[0].name + ' = ?', whereArgs: [guest.guestId]);
  }

  /* Fungsi untuk mengambil baris di tabel tblGuest */
  Future<GuestItem> getGuest(int id) async {
    List<Map> maps = await database.query(tableGuest,
        columns: getColumnsName(guestColumns),
        where: guestColumns[0].name + ' = ?',
        whereArgs: [id]);
    if (maps.length > 0) {
      return GuestItem.fromMap(maps.first);
    }
    return null;
  }

  /* Fungsi untuk mengambil daftar baris di tabel tblGuest berdasarkan eventId */
  Future<List<GuestItem>> getListGuest(int eventId) async {
    List<Map> maps = await database.query(tableGuest,
      columns: getColumnsName(guestColumns),
      where: guestColumns[1].name + " = ? ",
      whereArgs: [eventId]
    );
    List<GuestItem> listGuest = List();
    if (maps.length > 0) {
      for (Map map in maps) {
        GuestItem guest = GuestItem.fromMap(map);
        listGuest.add(guest);
      }
      return listGuest;
    }
    return listGuest;
  }
}

Selanjutnya guest.dart saya gunakan untuk membuat activty GuestActivity yang digunakan untuk membuat atau mengedit data tamu. Berikut ini kode sumber lengkapnya.

import 'package:flutter/material.dart';
import 'model.dart';
import 'database.dart';
import 'package:fluttertoast/fluttertoast.dart';

/* Activity untuk menambah atau mengedit guest/tamu */
class GuestActivity extends StatefulWidget {
  const GuestActivity({Key key, this.event, this.guest}) : super(key: key);
  final EventItem event;
  final GuestItem guest;

  @override
  GuestPage createState() {
    return GuestPage();
  }
}

class GuestPage extends State<GuestActivity>{
  EventItem _event;
  GuestItem _guest;
  String _selVisitTime = '';
  String _selName = '';
  String _selPhone = '';
  String _selAddress = '';
  String _selEmail = '';
  String _selNote = '';

  TextEditingController _controllerVisitTime = new TextEditingController();
  TextEditingController _controllerName = new TextEditingController();
  TextEditingController _controllerAddress = new TextEditingController();
  TextEditingController _controllerPhone = new TextEditingController();
  TextEditingController _controllerEmail = new TextEditingController();
  TextEditingController _controllerNote = new TextEditingController();

  /* Inisialisasi awal */
  @override
  void initState() {
    /* Mengambil nilai dari widget.guest dan mengisinya ke _guest */
    _guest = widget.guest;
    /* Mengambil nilai dari widget.event dan mengisinya ke _event */
    _event = widget.event;

    /* Jika _guest/widget.guest tidak null
      (mode pengeditan guest)
    */
    if (_guest != null){
      /* mengambil data guest sebelumnya */
      _selName = _guest.guestFullName;
      _selAddress = _guest.guestAddress;
      DateTime visitTime =
        new DateTime.fromMillisecondsSinceEpoch(_guest.guestVisitTime);
      _selVisitTime = formatDate(visitTime) + " " +
        formatTime(new TimeOfDay.fromDateTime(visitTime));
      _selPhone = _guest.guestNoPhone;
      _selEmail = _guest.guestEmail;
      _selNote = _guest.guestNote;

      /* Menampilkan pada TextField melalui perantara TextEditingController */
      _controllerVisitTime.text = _selVisitTime;
      _controllerName.text = _selName;
      _controllerAddress.text = _selAddress;
      _controllerPhone.text = _selPhone;
      _controllerEmail.text = _selEmail;
      _controllerNote.text = _selNote;
    }

    super.initState();
  }

  /* Fungsi untuk mengonversi angka di bawah 10 menjadi 2 digit.
     Misalnya: 01, 02, 03, dst sampai 09
  */
  String sprintF(var number){
    return number.toString().padLeft(2, "0");
  }

  /* Mengonversi tipe DateTime menjadi format tanggal.
     Misalnya: 2-02-2019
  */
  String formatDate(DateTime date){
    int year = date.year;
    int month = date.month;
    int day = date.day;

    return day.toString() + "-" + sprintF(month)+ "-" + year.toString();
  }

  /* Mengonversi tipe TimeOfDay menjadi format waktu.
     Misalnya: 08:09
  */
  String formatTime(TimeOfDay time){
    int hour = time.hour;
    int minute = time.minute;

    return sprintF(hour) + ":" + sprintF(minute);
  }

  /* Fungsi untuk menampilkan dialog TimePicker */
  Future selectTime() async {
    var today = DateTime.now();
    TimeOfDay picked = await showTimePicker(
      context: context,
      initialTime: new TimeOfDay(
          hour: DateTime.now().hour,
          minute: DateTime.now().minute),
      builder: (BuildContext context, Widget child) {
        return MediaQuery(
          /* Menggunakan format 24 jam (00-23) */
          data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true),
          child: child,
        );
      },
    );
    if(picked != null)
      setState(() {
        /* Menyimpan nilai pada _selVisitTime */
        _selVisitTime = formatDate(today) + " " + formatTime(picked);
        /* Menampilkan pada TextField */
        _controllerVisitTime.text = _selVisitTime;
      });
  }

  /* Metode untuk menyimpan event */
  void saveGuest(){
    bool isNew = false;

    /* Jika _guest null (tidak ada kiriman guest dari Navigator) */
    if (_guest == null){
      _guest = new GuestItem();
      _guest.eventId = _event.eventId;

      /* Tandai sebagai guest baru */
      isNew = true;
    }

    /* Mengisi variabel _guest */
    _guest.guestFullName = _selName;
    _guest.guestAddress = _selAddress;
    _guest.guestNoPhone = _selPhone;
    _guest.guestEmail = _selEmail;
    _guest.guestNote = _selNote;

    int year = DateTime.now().year;
    int month = DateTime.now().month;
    int day = DateTime.now().day;
    int hour = DateTime.now().hour;
    int minute = DateTime.now().minute;
    int second = 0;

    /* Mengonversi data dari TextField */
    /* Jika _selVisitTime tidak kosong (jika memilih maktu) */
    if (_selVisitTime.isNotEmpty){
      /* Membagi _selVisitTime menjadi 2 bagian (tanggal dan waktu) */
      List<String> arrDateTime = _selVisitTime.split(" ");
      if (arrDateTime.length >= 2){
        String strDate = arrDateTime[0];
        String strTime = arrDateTime[1];

        if (strDate.isNotEmpty){
          List<String> arrDate = strDate.split("-");
          if (arrDate.length == 3){
            day = int.parse(arrDate[0]);
            month = int.parse(arrDate[1]);
            year = int.parse(arrDate[2]);
          }
        }
        if (strTime.isNotEmpty){
          List<String> arrTime = strTime.split(":");
          if (arrTime.length >= 2){
            hour = int.parse(arrTime[0]);
            minute = int.parse(arrTime[1]);
            if (arrTime.length == 3){
              second = int.parse(arrTime[2]);
            }
          }
        }
      }
    }

    var selVisitTime = new DateTime(year, month, day, hour, minute, second);
    _guest.guestVisitTime = selVisitTime.millisecondsSinceEpoch;

    DatabaseProvider databaseProvider = new DatabaseProvider();
    /* Jika guest merupakan guest baru */
    if (isNew){
      /* Membuka database */
      databaseProvider.open().then((value){
        /* Menambahkan guest ke database */
        databaseProvider.insertGuest(_guest).then((guest) {
          /* Menampilkan Toast */
          Fluttertoast.showToast(
              msg: "Tamu berhasil ditambahkan",
              toastLength: Toast.LENGTH_SHORT,
              gravity: ToastGravity.CENTER,
              timeInSecForIos: 1
          );
          /* Menutup database */
          databaseProvider.close();
          /* Menutup activity dan mengirim _guest ke Navigator */
          Navigator.pop(context, _guest);
        });
      });
    } else {
      /* Membuka database */
      databaseProvider.open().then((value){
        /* Memperbarui guest di database */
        databaseProvider.updateGuest(_guest).then((guest) {
          /* Menampilkan Toast */
          Fluttertoast.showToast(
              msg: "Acara berhasil diperbarui",
              toastLength: Toast.LENGTH_SHORT,
              gravity: ToastGravity.CENTER,
              timeInSecForIos: 1
          );
          /* Menutup database */
          databaseProvider.close();
          /* Menutup activity dan mengirim _guest ke Navigator */
          Navigator.pop(context, _guest);
        });
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Tambah Tamu"),
      ),
      body: Stack(
        children: <Widget>[
          Positioned.fill(
            child: Container(
              padding: EdgeInsets.all(8.0),
              color: Colors.brown.shade200,
              child: Card(
                elevation: 4.0,
                color: Colors.brown.shade100,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.all(
                    Radius.circular(8.0),
                  ),
                  side: BorderSide(
                    color: Colors.brown.shade400,
                    width: 1.0,
                    style: BorderStyle.solid,
                  ),
                ),
                child: SingleChildScrollView(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: <Widget>[
                      Container(
                        height: 70,
                        decoration: BoxDecoration(
                          border: Border(
                            bottom: BorderSide(
                              color: Colors.brown.shade600,
                              width: 1,
                              style: BorderStyle.solid,
                            ),
                          ),
                        ),
                        child: Padding(
                          padding: EdgeInsets.symmetric(vertical: 0,
                              horizontal: 20.0),
                          child: Row(
                            children: <Widget>[
                              Container(
                                width: 140,
                                child: Align(
                                  alignment: Alignment.centerLeft,
                                  child: Text("Waktu Kunjungan",
                                    style: TextStyle(
                                      fontSize: 16,
                                    ),
                                  ),
                                ),
                              ),
                              SizedBox(width: 20,),
                              Flexible(
                                child: TextField(
                                  controller: _controllerVisitTime,
                                  onTap: selectTime,
                                  textInputAction: TextInputAction.none,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      Container(
                        height: 70,
                        decoration: BoxDecoration(
                          border: Border(
                            bottom: BorderSide(
                              color: Colors.brown.shade600,
                              width: 1,
                              style: BorderStyle.solid,
                            ),
                          ),
                        ),
                        child: Padding(
                          padding: EdgeInsets.symmetric(vertical: 0,
                              horizontal: 20.0),
                          child: Row(
                            children: <Widget>[
                              Container(
                                width: 120,
                                child: Align(
                                  alignment: Alignment.centerLeft,
                                  child: Text("Nama Lengkap",
                                    style: TextStyle(
                                      fontSize: 16,
                                    ),
                                  ),
                                ),
                              ),
                              SizedBox(width: 20,),
                              Flexible(
                                child: TextField(
                                  keyboardType: TextInputType.text,
                                  controller: _controllerName,
                                  autocorrect: false,
                                  textCapitalization: TextCapitalization.words,
                                  onChanged: (value){
                                    setState(() {
                                      _selName = value;
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      Container(
                        height: 70,
                        decoration: BoxDecoration(
                          border: Border(
                            bottom: BorderSide(
                              color: Colors.brown.shade600,
                              width: 1,
                              style: BorderStyle.solid,
                            ),
                          ),
                        ),
                        child: Padding(
                          padding: EdgeInsets.symmetric(vertical: 0,
                              horizontal: 20.0),
                          child: Row(
                            children: <Widget>[
                              Container(
                                width: 100,
                                child: Align(
                                  alignment: Alignment.centerLeft,
                                  child: Text("No HP",
                                    style: TextStyle(
                                      fontSize: 16,
                                    ),
                                  ),
                                ),
                              ),
                              SizedBox(width: 20,),
                              Flexible(
                                child: TextField(
                                  keyboardType: TextInputType.phone,
                                  controller: _controllerPhone,
                                  autocorrect: false,
                                  onChanged: (value){
                                    setState(() {
                                      _selPhone = value;
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      Container(
                        height: 70,
                        decoration: BoxDecoration(
                          border: Border(
                            bottom: BorderSide(
                              color: Colors.brown.shade600,
                              width: 1,
                              style: BorderStyle.solid,
                            ),
                          ),
                        ),
                        child: Padding(
                          padding: EdgeInsets.symmetric(vertical: 0,
                              horizontal: 20.0),
                          child: Row(
                            children: <Widget>[
                              Container(
                                width: 100,
                                child: Align(
                                  alignment: Alignment.centerLeft,
                                  child: Text("Email",
                                    style: TextStyle(
                                      fontSize: 16,
                                    ),
                                  ),
                                ),
                              ),
                              SizedBox(width: 20,),
                              Flexible(
                                child: TextField(
                                  keyboardType: TextInputType.emailAddress,
                                  controller: _controllerEmail,
                                  autocorrect: false,
                                  onChanged: (value){
                                    setState(() {
                                      _selEmail = value;
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      Container(
                        height: 90,
                        decoration: BoxDecoration(
                          border: Border(
                            bottom: BorderSide(
                              color: Colors.brown.shade600,
                              width: 1,
                              style: BorderStyle.solid,
                            ),
                          ),
                        ),
                        child: Padding(
                          padding: EdgeInsets.symmetric(vertical: 0,
                              horizontal: 20.0),
                          child: Row(
                            children: <Widget>[
                              Container(
                                width: 100,
                                child: Align(
                                  alignment: Alignment.centerLeft,
                                  child: Text("Alamat",
                                    style: TextStyle(
                                      fontSize: 16,
                                    ),
                                  ),
                                ),
                              ),
                              SizedBox(width: 20,),
                              Flexible(
                                child: TextField(
                                  keyboardType: TextInputType.multiline,
                                  maxLines: 3,
                                  minLines: 2,
                                  controller: _controllerAddress,
                                  autocorrect: false,
                                  textCapitalization: TextCapitalization.sentences,
                                  onChanged: (value){
                                    setState(() {
                                      _selAddress = value;
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      Container(
                        height: 90,
                        decoration: BoxDecoration(
                          border: Border(
                            bottom: BorderSide(
                              color: Colors.brown.shade600,
                              width: 1,
                              style: BorderStyle.solid,
                            ),
                          ),
                        ),
                        child: Padding(
                          padding: EdgeInsets.symmetric(vertical: 0,
                              horizontal: 20.0),
                          child: Row(
                            children: <Widget>[
                              Container(
                                width: 100,
                                child: Align(
                                  alignment: Alignment.centerLeft,
                                  child: Text("Catatan",
                                    style: TextStyle(
                                      fontSize: 16,
                                    ),
                                  ),
                                ),
                              ),
                              SizedBox(width: 20,),
                              Flexible(
                                child: TextField(
                                  keyboardType: TextInputType.multiline,
                                  maxLines: 3,
                                  minLines: 2,
                                  controller: _controllerNote,
                                  autocorrect: false,
                                  textCapitalization: TextCapitalization.sentences,
                                  onChanged: (value){
                                    setState(() {
                                      _selNote = value;
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      SizedBox(
                        height: MediaQuery.of(context).viewInsets.bottom,
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: saveGuest,
        backgroundColor: Colors.brown.shade200,
        tooltip: 'Tambah Acara',
        child: Icon(Icons.save,
          color: Colors.black45,
        ),
      ),
    );
  }
}
Tampilan GuestActivity Saat Dijalankan
Tampilan GuestActivity Saat Dijalankan

Berikutnya guests.dart yang saya gunakan untuk membuat activty GuestsActivity, sebuah activity untuk menampilkan daftar tamu dari sebuah acara yang dipilih. Berikut ini kode sumbernya.

import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'model.dart';
import 'database.dart';
import 'guest.dart';

/* Activity  untuk menampilkan daftar guest/tamu */
class GuestsActivity extends StatefulWidget {
  GuestsActivity({Key key, this.event}) : super(key: key);

  final EventItem event;

  @override
  GuestsPage createState() => GuestsPage();
}

class GuestsPage extends State<GuestsActivity> {
  EventItem _event;
  GuestItem _guest;
  List<GuestItem> _listGuest = new List();
  DatabaseProvider databaseProvider = new DatabaseProvider();

  /* Inisialisasi awal */
  @override
  void initState(){
    /* Mengambil nilai widget.event yang dikirim Navigator
       dan mengisikan ke variabel _event
    */
    _event = widget.event;
    super.initState();
  }

  /* Metode untuk memuat ulang daftar guest */
  void _refreshList() {
    /* Membuka database */
    databaseProvider.open().then((_){
      /* Memanggil fungsi getListGuest untuk mengambil daftar guest
         berdasarkan eventId dari _event
      */
      databaseProvider.getListGuest(_event.eventId).then((list){
        if (list != null){
          /* Memperbarui variabel _listGuest */
          setState(() {
            _listGuest = list;
          });
          /* Menutup database */
          databaseProvider.close();
        }
      });
    });
  }

  /* Fungsi untuk mengonversi angka di bawah 10 menjadi 2 digit.
     Misalnya: 01, 02, 03, dst sampai 09
  */
  String sprintF(var number){
    return number.toString().padLeft(2, "0");
  }

  /* Mengonversi millisecond (unix time) menjadi format tanggal.
     Misalnya: 24 Maret 2019
  */
  String formatDate(int millis){
    var date = new DateTime.fromMillisecondsSinceEpoch(millis);
    int year = date.year;
    int month = date.month;
    int day = date.day;

    List<String> monthNames = ["", "Januari", "Februari", "Maret", "April",
      "Mei", "Juni", "Juli", "Agustus", "September",
      "Oktober", "November", "Desember"];

    return day.toString() + " " + monthNames[month] + " " + year.toString();
  }

  /* Mengonversi millisecond (unix time) menjadi format waktu.
     Misalnya: 08:09
  */
  String formatTime(int millis){
    var date = new DateTime.fromMillisecondsSinceEpoch(millis);
    int hour = date.hour;
    int minute = date.minute;

    return sprintF(hour) + ":" + sprintF(minute);
  }

  /* Fungsi untuk membuat widget item dari ListView */
  Widget createGuestItem(GuestItem guest){
    return Card(
      elevation: 4.0,
      child: Material(
        color: Colors.brown.shade400,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.all(
            Radius.circular(6.0),
          ),
        ),
        child: InkWell(
          /* Saat baris widget ditap */
          onTap: () {
            /* Simpan nilai guest pada variabel _guest */
            setState(() {
              _guest = guest;
            });
            /* Memanggil GuestsActivity untuk pengeditan guest */
            showGuestActivity();
          },
          child: Row(
            children: <Widget>[
              Container (
                padding: EdgeInsets.all(2.0),
                child: Icon(
                  Icons.person,
                  color: Colors.grey.shade800,
                  size: 36.0,
                ),
              ),
              Expanded(
                child: Container(
                  padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
                  decoration: BoxDecoration(
                    color: Colors.brown.shade200,
                  ),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: <Widget>[
                      Row(
                        children: <Widget>[
                          Expanded(
                            child: Text(guest.guestFullName,
                              style: TextStyle(
                                fontWeight: FontWeight.bold,
                                fontSize: 16.0,
                                color: Colors.black54,
                              ),
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                            ),
                          ),
                          InkWell(
                            /* Saat tombol hapus (tong sampah) ditap */
                            onTap: (){
                              if (_listGuest.length > 0){
                                /* Simpan nilai guest pada variabel _guest */
                                setState(() {
                                  _guest = guest;
                                });
                                /* Menampilkan dialog konfirmasi penghaspusan */
                                showDeleteDialog();
                              }
                            },
                            child: Padding(
                              padding: EdgeInsets.all(4.0),
                              child: Icon(Icons.delete,
                                color: Colors.black45,
                              ),
                            ),
                          ),
                        ],
                      ),
                      SizedBox(height: 8.0,),
                      Row(
                        children: <Widget>[
                          Icon(
                            Icons.access_time,
                            color: Colors.grey.shade800,
                            size: 16.0,
                          ),
                          SizedBox(width: 5.0,),
                          Text(formatDate(guest.guestVisitTime) + " " +
                              formatTime(guest.guestVisitTime),
                            style: TextStyle(
                              fontSize: 12.0,
                              color: Colors.black54,
                            ),
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ],
                      ),
                      SizedBox(height: 5.0,),
                      Row(
                        children: <Widget>[
                          Icon(
                            Icons.phone_android,
                            color: Colors.grey.shade800,
                            size: 16.0,
                          ),
                          SizedBox(width: 5.0,),
                          Text(guest.guestNoPhone,
                            style: TextStyle(
                              fontSize: 12.0,
                              color: Colors.black54,
                            ),
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ],
                      ),
                      SizedBox(height: 5.0,),
                      Row(
                        children: <Widget>[
                          Icon(
                            Icons.email,
                            color: Colors.grey.shade800,
                            size: 16.0,
                          ),
                          SizedBox(width: 5.0,),
                          Text(guest.guestEmail,
                            style: TextStyle(
                              fontSize: 12.0,
                              color: Colors.black54,
                            ),
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ],
                      ),
                      SizedBox(height: 5.0,),
                      Row(
                        children: <Widget>[
                          Icon(
                            Icons.location_on,
                            color: Colors.grey.shade800,
                            size: 16.0,
                          ),
                          SizedBox(width: 5.0,),
                          Text(guest.guestAddress,
                            style: TextStyle(
                              fontSize: 12.0,
                              color: Colors.black54,
                            ),
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  /* Fungsi untuk membangun daftar item dari ListView */
  Widget listViewBuilder(BuildContext context, int index){
    GuestItem guest = _listGuest[index];
    return createGuestItem(guest);
  }

  /* Metode untuk menampilkan GuestActivity */
  void showGuestActivity(){
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => GuestActivity(event: _event, guest: _guest,),
      ),
    ).then((value){
      /* memuat ulang ListView saat GuestsActivity tampil kembali */
      if (value != null)
        setState(() {
          _refreshList();
        });
    });
  }

  /* Metode untuk menampilkan dialog konfirmasi penghapusan guest */
  void showDeleteDialog() {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text("Hapus"),
          content: Text("Ingin menghapus tamu?"),
          actions: <Widget>[
            FlatButton(
              /* Saat mengetap/mengklik tombol Ya pada dialog */
              child: Text("Ya"),
              onPressed: () {
                /* Membuka database */
                databaseProvider.open().then((value){
                  /* Menghapus guest dari database */
                  databaseProvider.deleteGuest(_guest.guestId).then((guest) {
                    /* Menampilkan Toast setelah penghapusan */
                    Fluttertoast.showToast(
                        msg: "Tamu berhasil dihapus",
                        toastLength: Toast.LENGTH_SHORT,
                        gravity: ToastGravity.CENTER,
                        timeInSecForIos: 1
                    );
                    /* Menghapus item guest dari _listGuest.
                       Menggunakan setState agar item-item pada
                       _listGuest diperbarui sehingga daftar item
                       pada ListView ikut diperbarui
                    */
                    setState(() {
                      _listGuest.remove(guest);
                    });
                    /* Menutup database */
                    databaseProvider.close();
                    /* Menutup dialog */
                    Navigator.of(context).pop();
                  });
                });
              },
            ),
            FlatButton(
              child: Text("Tidak"),
              /* Saat mengetap/mengklik tombol Tidak pada dialog */
              onPressed: () {
                /* Menutup dialog */
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {

    Widget _loading = Center (
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          CircularProgressIndicator(),
          SizedBox(height: 10,),
          Text("Menyiapkan Data")
        ],
      ),
    );

    Widget  _blank = Center(
      child: Container(
        height: 40,
        child: Text("Belum ada tamu"),
      ),
    );

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.event.eventName),
        actions: <Widget>[
          IconButton(
              icon: Icon(Icons.refresh),
              tooltip: 'Refresh',
              /* Memuat data pada ListView saat tombol Refresh ditap */
              onPressed: _refreshList
          ),
        ],
      ),
      body: Container(
        padding: EdgeInsets.all(18.0),
        decoration: BoxDecoration(
          color: Colors.brown.shade200,
        ),
        child: Card(
          elevation: 4.0,
          color: Colors.brown.shade100,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(
              Radius.circular(8.0),
            ),
            side: BorderSide(
              color: Colors.brown.shade400,
              width: 1.0,
              style: BorderStyle.solid,
            ),
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisSize: MainAxisSize.max,
            children: <Widget>[
              Padding(
                padding: EdgeInsets.fromLTRB(
                    16.0, 16.0, 16.0, 8.0
                ),
                child: Text(
                  "Daftar Tamu",
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 16.0,
                    fontWeight: FontWeight.bold
                  ),
                ),
              ),
              Expanded(
                child: Padding(
                  padding: EdgeInsets.symmetric(
                    vertical: 8.0,
                    horizontal: 16.0,
                  ),
                  /* Menggunakan FutureBuilder untuk mengakses database */
                  child: FutureBuilder(
                    future: databaseProvider.open(),
                    builder: (BuildContext context, AsyncSnapshot snapshot){
                      /* Saat koneksi ke database berhasil terhubung */
                      if (snapshot.connectionState == ConnectionState.done) {
                        /* Menggunakan FutureBuilder untuk mengambil daftar guest */
                        return FutureBuilder(
                          future: databaseProvider.getListGuest(_event.eventId),
                          builder: (BuildContext context, AsyncSnapshot snapshot){
                            /* Saat pemanggilan fungsi mendapatkan data */
                            if (snapshot.hasData) {
                              if (snapshot.data != null) {
                                /* Mengisi variabel _listGuest dari data snapshot */
                                _listGuest = snapshot.data;
                                databaseProvider.close();

                                /* Saat _listEvent kosong */
                                if (_listGuest.length == 0){
                                  /* Tampilkan widget _blank */
                                  return _blank;

                                /* Saat _listGuest tidak kosong */
                                } else {
                                  /* Bangun widget ListView */
                                  return ListView.builder(
                                    itemCount: _listGuest.length,
                                    itemBuilder: listViewBuilder,
                                  );
                                }
                              } else {
                                return _blank;
                              }
                            } else {
                              return _blank;
                            }
                          },
                        );
                      } else {
                        return _loading;
                      }
                    },
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        /* Saat FloatingActionButton ditap */
        onPressed: (){
          setState(() {
            /* Null-kan nilai _guest */
            _guest = null;
            /* Memanggil GuestActivity */
            showGuestActivity();
          });
        },
        backgroundColor: Colors.brown.shade200,
        tooltip: 'Tambah Tamu',
        child: Icon(Icons.add_circle,
          color: Colors.black45,
        ),
      ),
    );
  }
}
Tampilan GuestsActivity Saat Dijalankan
Tampilan GuestsActivity Saat Dijalankan

Selanjutnya event.dart yang saya gunakan untuk membuat EventActivity yang akan digunakan untuk menambahkan acara baru atau mengedit acara yang sudah ada. Di bawah ini kode sumbernya.

import 'package:flutter/material.dart';
import 'model.dart';
import 'database.dart';
import 'package:fluttertoast/fluttertoast.dart';

/* Activity  untuk menambah atau mengedit event/acara */
class EventActivity extends StatefulWidget {
  const EventActivity({Key key, this.event}) : super(key: key);
  final EventItem event;

  @override
  EventPage createState() {
    return EventPage();
  }
}

class EventPage extends State<EventActivity>{
  EventItem _event;
  String _selDate = '';
  String _selTime = '';
  String _selName = '';
  String _selAddress = '';

  TextEditingController _controllerDate = new TextEditingController();
  TextEditingController _controllerTime = new TextEditingController();
  TextEditingController _controllerName = new TextEditingController();
  TextEditingController _controllerAddress = new TextEditingController();

  /* Inisialisasi awal */
  @override
  void initState() {
    /* Mengambil nilai dari widget.event dan mengisinya ke _event */
    _event = widget.event;

    /* Jika _event/widget.event tidak null
      (mode pengeditan event)
    */
    if (_event != null){
      /* mengambil data event sebelumnya */
      _selName = _event.eventName;
      _selAddress = _event.eventAddress;
      _selDate = formatDate(new DateTime.fromMillisecondsSinceEpoch(_event.eventDate));
      _selTime = formatTime(new TimeOfDay.fromDateTime(new DateTime.fromMillisecondsSinceEpoch(_event.eventTime)));

      /* Menampilkan pada TextField melalui perantara TextEditingController */
      _controllerName.text = _selName;
      _controllerAddress.text = _selAddress;
      _controllerDate.text = _selDate;
      _controllerTime.text = _selTime;
    }

    super.initState();
  }

  /* Fungsi untuk mengonversi angka di bawah 10 menjadi 2 digit.
     Misalnya: 01, 02, 03, dst sampai 09
  */
  String sprintF(var number){
    return number.toString().padLeft(2, "0");
  }

  /* Mengonversi tipe DateTime menjadi format tanggal.
     Misalnya: 2-02-2019
  */
  String formatDate(DateTime date){
    int year = date.year;
    int month = date.month;
    int day = date.day;

    return day.toString() + "-" + sprintF(month)+ "-" + year.toString();
  }

  /* Mengonversi tipe TimeOfDay menjadi format waktu.
     Misalnya: 08:09
  */
  String formatTime(TimeOfDay time){
    int hour = time.hour;
    int minute = time.minute;

    return sprintF(hour) + ":" + sprintF(minute);
  }

  /* Fungsi untuk menampilkan dialog DatePicker */
  Future selectDate() async {
    DateTime picked = await showDatePicker(
        context: context,
        initialDate: new DateTime.now(),
        firstDate: new DateTime(2018),
        lastDate: new DateTime(2030)
    );
    if(picked != null)
      setState(() {
        /* Menyimpan nilai pada _selDate */
        _selDate = formatDate(picked);
        /* Menampilkan pada TextField */
        _controllerDate.text = _selDate;
      });
  }

  /* Fungsi untuk menampilkan dialog TimePicker */
  Future selectTime() async {
    TimeOfDay picked = await showTimePicker(
      context: context,
      initialTime: new TimeOfDay(
        hour: DateTime.now().hour,
        minute: DateTime.now().minute),
      builder: (BuildContext context, Widget child) {
        return MediaQuery(
          /* Menggunakan format 24 jam (00-23) */
          data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true),
          child: child,
        );
      },
    );
    if(picked != null)
      setState(() {
        /* Menyimpan nilai pada _selTime */
        _selTime = formatTime(picked);
        /* Menampilkan pada TextField */
        _controllerTime.text = _selTime;
      });
  }

  /* Metode untuk menyimpan event */
  void saveEvent(){
    bool isNew = false;

    /* Jika _event null (tidak ada kiriman event dari Navigator) */
    if (_event == null){
      _event = new EventItem();
      _event.isCompleted = 0;

      /* Tandai sebagai event baru */
      isNew = true;
    }

    /* Mengisi variabel _event */
    _event.eventName = _selName;
    _event.eventAddress = _selAddress;

    int year = DateTime.now().year;
    int month = DateTime.now().month;
    int day = DateTime.now().day;

    /* Mengisi _event.createDate dengan millisecond saat ini
       (saat tombol ditap)
    */
    _event.createDate = DateTime.now().millisecondsSinceEpoch;

    /* Mengonversi data dari TextField */
    /* Jika _selDate tidak kosong (jika memilih tanggal) */
    if (_selDate.isNotEmpty){
      List<String> arrDate = _selDate.split("-");
      if (arrDate.length == 3){
        day = int.parse(arrDate[0]);
        month = int.parse(arrDate[1]);
        year = int.parse(arrDate[2]);
      }
    }

    var selDTDate = new DateTime(year, month, day, 0, 0, 0);
    _event.eventDate = selDTDate.millisecondsSinceEpoch;

    int hour = DateTime.now().hour;
    int minute = DateTime.now().minute;

    /* Mengonversi data dari TextField */
    /* Jika _selTime tidak kosong (jika memilih waktu) */
    if (_selTime.isNotEmpty){
      List<String> arrTime = _selTime.split(":");
      if (arrTime.length >= 2){
        hour = int.parse(arrTime[0]);
        minute = int.parse(arrTime[1]);
      }
    }

    var selDTTime = new DateTime(year, month, day, hour, minute, 0);
    _event.eventTime = selDTTime.millisecondsSinceEpoch;

    DatabaseProvider databaseProvider = new DatabaseProvider();
    /* Jika event merupakan event baru */
    if (isNew){
      /* Membuka database */
      databaseProvider.open().then((value){
        /* Menambahkan event ke database */
        databaseProvider.insertEvent(_event).then((event) {
          /* Menampilkan Toast */
          Fluttertoast.showToast(
              msg: "Acara berhasil ditambahkan",
              toastLength: Toast.LENGTH_SHORT,
              gravity: ToastGravity.CENTER,
              timeInSecForIos: 1
          );
          /* Menutup database */
          databaseProvider.close();
          /* Menutup activity */
          Navigator.pop(context, _event);
        });
      });
    /* Jika event bukan merupakan event baru (mode edit) */
    } else {
      /* Membuka database */
      databaseProvider.open().then((value){
        /* Memperbarui event di database */
        databaseProvider.updateEvent(_event).then((event) {
          /* Menampilkan Toast */
          Fluttertoast.showToast(
              msg: "Acara berhasil diperbarui",
              toastLength: Toast.LENGTH_SHORT,
              gravity: ToastGravity.CENTER,
              timeInSecForIos: 1
          );
          /* Menutup database */
          databaseProvider.close();
          /* Menutup activity dan mengirim _event ke Navigator */
          Navigator.pop(context, _event);
        });
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Tambah Acara"),
      ),
      body: Stack(
        /* Mnggunakan widget Stack agar dapat memanfaatkan widget Positioned.
        Positioned.fill digunakan untuk membuat tampilan satu layar penuh
        */
        children: <Widget>[
          Positioned.fill(
            child: Container(
              padding: EdgeInsets.all(8.0),
              color: Colors.brown.shade200,
              child: Card(
                elevation: 4.0,
                color: Colors.brown.shade100,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.all(
                    Radius.circular(8.0),
                  ),
                  side: BorderSide(
                    color: Colors.brown.shade400,
                    width: 1.0,
                    style: BorderStyle.solid,
                  ),
                ),
                child: SingleChildScrollView(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: <Widget>[
                      Container(
                        height: 70,
                        decoration: BoxDecoration(
                          border: Border(
                            bottom: BorderSide(
                              color: Colors.brown.shade600,
                              width: 1,
                              style: BorderStyle.solid,
                            ),
                          ),
                        ),
                        child: Padding(
                          padding: EdgeInsets.symmetric(vertical: 0,
                              horizontal: 20.0),
                          child: Row(
                            children: <Widget>[
                              Container(
                                width: 120,
                                child: Align(
                                  alignment: Alignment.centerLeft,
                                  child: Text("Tanggal",
                                    style: TextStyle(
                                      fontSize: 16,
                                    ),
                                  ),
                                ),
                              ),
                              SizedBox(width: 20,),
                              Flexible(
                                child: TextField(
                                  controller: _controllerDate,
                                  onTap: selectDate,
                                  textInputAction: TextInputAction.none,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      Container(
                        height: 70,
                        decoration: BoxDecoration(
                          border: Border(
                            bottom: BorderSide(
                              color: Colors.brown.shade600,
                              width: 1,
                              style: BorderStyle.solid,
                            ),
                          ),
                        ),
                        child: Padding(
                          padding: EdgeInsets.symmetric(vertical: 0,
                              horizontal: 20.0),
                          child: Row(
                            children: <Widget>[
                              Container(
                                width: 120,
                                child: Align(
                                  alignment: Alignment.centerLeft,
                                  child: Text("Waktu",
                                    style: TextStyle(
                                      fontSize: 16,
                                    ),
                                  ),
                                ),
                              ),
                              SizedBox(width: 20,),
                              Flexible(
                                child: TextField(
                                  controller: _controllerTime,
                                  onTap: selectTime,
                                  textInputAction: TextInputAction.none,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      Container(
                        height: 70,
                        decoration: BoxDecoration(
                          border: Border(
                            bottom: BorderSide(
                              color: Colors.brown.shade600,
                              width: 1,
                              style: BorderStyle.solid,
                            ),
                          ),
                        ),
                        child: Padding(
                          padding: EdgeInsets.symmetric(vertical: 0,
                              horizontal: 20.0),
                          child: Row(
                            children: <Widget>[
                              Container(
                                width: 120,
                                child: Align(
                                  alignment: Alignment.centerLeft,
                                  child: Text("Nama",
                                    style: TextStyle(
                                      fontSize: 16,
                                    ),
                                  ),
                                ),
                              ),
                              SizedBox(width: 20,),
                              Flexible(
                                child: TextField(
                                  keyboardType: TextInputType.text,
                                  controller: _controllerName,
                                  autocorrect: false,
                                  textCapitalization: TextCapitalization.words,
                                  onChanged: (value){
                                    setState(() {
                                      _selName = value;
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      Container(
                        height: 90,
                        decoration: BoxDecoration(
                          border: Border(
                            bottom: BorderSide(
                              color: Colors.brown.shade600,
                              width: 1,
                              style: BorderStyle.solid,
                            ),
                          ),
                        ),
                        child: Padding(
                          padding: EdgeInsets.symmetric(vertical: 0,
                              horizontal: 20.0),
                          child: Row(
                            children: <Widget>[
                              Container(
                                width: 120,
                                child: Align(
                                  alignment: Alignment.centerLeft,
                                  child: Text("Alamat",
                                    style: TextStyle(
                                      fontSize: 16,
                                    ),
                                  ),
                                ),
                              ),
                              SizedBox(width: 20,),
                              Flexible(
                                child: TextField(
                                  keyboardType: TextInputType.multiline,
                                  maxLines: 3,
                                  minLines: 2,
                                  controller: _controllerAddress,
                                  autocorrect: false,
                                  textCapitalization: TextCapitalization.sentences,
                                  onChanged: (value){
                                    setState(() {
                                      _selAddress = value;
                                    });
                                  },
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      SizedBox(
                        height: MediaQuery.of(context).viewInsets.bottom,
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: saveEvent,
        backgroundColor: Colors.brown.shade200,
        tooltip: 'Tambah Acara',
        child: Icon(Icons.save,
          color: Colors.black45,
        ),
      ),
    );
  }
}
 Tampilan EventActivity Saat Dijalankan
Tampilan EventActivity Saat Dijalankan

Terakhir main.dart sebagai MainActivity atau activity utama, juga digunakan untuk menampilkan daftar acara yang sudah dibuat.

import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'model.dart';
import 'database.dart';
import 'event.dart';
import 'guests.dart';

void main() => runApp(MainApp());

class MainApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Buku Tamu',
      theme: ThemeData(
        primarySwatch: Colors.brown,
      ),
      home: MainActivity(title: 'Buku Tamu'),
    );
  }
}

/* Activity utama, untuk menampilkan daftar event/acara */
class MainActivity extends StatefulWidget {
  MainActivity({Key key, this.title}) : super(key: key);

  final String title;

  @override
  MainPage createState() => MainPage();
}

class MainPage extends State<MainActivity> {
  EventItem _event;
  List<EventItem> _listEvent = new List();
  DatabaseProvider databaseProvider = new DatabaseProvider();

  /* Inisialisasi awal */
  @override
  void initState(){
    /* Memanggil metode createDefaultEvent */
    createDefaultEvent();

    super.initState();

    /* Memuat data pada ListView */
    _refreshList();
  }

  /* Metode untuk membuat event/acara otomatis
     saat tabel belum mempunyai satupun baris acara
  */
  void createDefaultEvent(){
    /* Membuka koneksi ke database */
    databaseProvider.open().then((_){
      /* Memanggil fungsi getListEvent untuk mengoleksi daftar event */
      databaseProvider.getListEvent().then((list){
        if (list != null){
          /* Ketika daftar event kosong */
          if (list.length == 0){
            /* Membuat event baru */
            EventItem eventDef = new EventItem();

            eventDef.eventName = "Perkawinan Jang Karim dan Nyi Imas";
            eventDef.eventAddress = "Garut";
            eventDef.isCompleted = 0;

            int currTime = DateTime.now().millisecondsSinceEpoch;

            eventDef.eventDate = currTime;
            eventDef.eventTime = currTime;
            eventDef.createDate = currTime;

            /* Memasukan event baru ke tabel */
            databaseProvider.insertEvent(eventDef).then((event) {
              /* Menutup database setelah proses input */
              databaseProvider.close();
            });
          }
        }
      });
    });
  }

  /* Metode untuk memuat ulang daftar event */
  void _refreshList() {
    /* Membuka database */
    databaseProvider.open().then((_){
      /* Memanggil fungsi getListEvent untuk mengambil daftar event */
      databaseProvider.getListEvent().then((list){
        if (list != null){
          /* Memperbarui variabel _listEvent */
          setState(() {
            _listEvent = list;
          });
          /* Menutup database */
          databaseProvider.close();
        }
      });
    });
  }

  /* Fungsi untuk mengonversi angka di bawah 10 menjadi 2 digit.
     Misalnya: 01, 02, 03, dst sampai 09
  */
  String sprintF(var number){
    return number.toString().padLeft(2, "0");
  }

  /* Mengonversi millisecond (unix time) menjadi format tanggal.
     Misalnya: 22 Januari 2019
  */
  String formatDate(int millis){
    if (millis != null){
      var date = new DateTime.fromMillisecondsSinceEpoch(millis);
      int year = date.year;
      int month = date.month;
      int day = date.day;

      List<String> monthNames = ["", "Januari", "Februari", "Maret", "April",
        "Mei", "Juni", "Juli", "Agustus", "September",
        "Oktober", "November", "Desember"];

      return day.toString() + " " + monthNames[month] + " " + year.toString();
    }
    return '';
  }

  /* Mengonversi millisecond (unix time) menjadi format waktu.
     Misalnya: 23:59
  */
  String formatTime(int millis){
    if (millis != null){
      var date = new DateTime.fromMillisecondsSinceEpoch(millis);
      int hour = date.hour;
      int minute = date.minute;

      return sprintF(hour) + ":" + sprintF(minute);
    }
    return '';
  }

  /* Fungsi untuk membuat widget item dari ListView */
  Widget createEventItem(EventItem event){
    return Card(
      elevation: 4.0,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.all(
          Radius.circular(6.0),
        ),
        side: BorderSide(
          color: Colors.brown.shade200,
          width: 1.0,
          style: BorderStyle.solid,
        ),
      ),
      child: Material(
        color: Colors.brown.shade200,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.all(
            Radius.circular(6.0),
          ),
          side: BorderSide(
            color: Colors.brown.shade200,
            width: 1.0,
            style: BorderStyle.solid,
          ),
        ),
        child: InkWell(
          /* Saat baris widget ditap */
          onTap: () {
            /* Simpan nilai event pada variabel _event */
            setState(() {
              _event = event;
            });
            /* Memanggil GuestsActivity */
            showGuestsActivity();
          },
          child: Padding(
            padding: EdgeInsets.all(8.0),
            child: Row(
              children: <Widget>[
                Icon(Icons.event_note,
                  color: Colors.grey.shade600,
                  size: 56.0,
                ),
                SizedBox(width: 10,),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: <Widget>[
                      Text(event.eventName,
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 16.0,
                          color: Colors.black54,
                        ),
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                      Text(formatDate(event.eventDate) + " " +
                          formatTime(event.eventTime),
                        style: TextStyle(
                          fontSize: 14.0,
                          color: Colors.black45,
                        ),
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                      ),
                      Text(event.eventAddress,
                        style: TextStyle(
                          fontSize: 14.0,
                          color: Colors.black45,
                        ),
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ],
                  ),
                ),
                SizedBox(width: 10,),
                Column(
                  children: <Widget>[
                    Material(
                      color: Colors.transparent,
                      child: InkWell(
                        /* Saat tombol edit (pensil) ditap */
                        onTap: (){
                          /* Simpan nilai event pada variabel _event */
                          setState(() {
                            _event = event;
                          });
                          /* Memanggil EventActivity untuk pengeditan event */
                          showEventActivity();
                        },
                        child: Padding(
                          padding: EdgeInsets.all(4.0),
                          child: Icon(Icons.edit,
                            color: Colors.black45,
                          ),
                        ),
                      ),
                    ),
                    Material(
                      color: Colors.transparent,
                      child: InkWell(
                        /* Saat tombol hapus (tong sampah) ditap */
                        onTap: (){
                          if (_listEvent.length > 0){
                            /* Simpan nilai event pada variabel _event */
                            setState(() {
                              _event = event;
                            });
                            /* Menampilkan dialog konfirmasi penghaspusan */
                            showDeleteDialog();
                          }
                        },
                        child: Padding(
                          padding: EdgeInsets.all(4.0),
                          child: Icon(Icons.delete,
                            color: Colors.black45,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  /* Fungsi untuk membangun daftar item dari ListView */
  Widget listViewBuilder(BuildContext context, int index){
    EventItem event = _listEvent[index];
    return createEventItem(event);
  }

  /* Metode untuk menampilkan EventActivity */
  void showEventActivity(){
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => EventActivity(event: _event,),
      ),
    ).then((value){
      /* memuat ulang ListView saat MainActivity tampil kembali */
      if (value != null)
      setState(() {
        _refreshList();
      });
    });
  }

  /* Metode untuk menampilkan GuestsActivity */
  void showGuestsActivity(){
    Navigator.push(
      context,
      MaterialPageRoute(
        /* Memangil GuestsActivity sambil mengirim obyek _event */
        builder: (context) => GuestsActivity(event: _event,),
      ),
    );
  }

  /* Metode untuk menampilkan dialog konfirmasi penghapusan event */
  void showDeleteDialog() {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text("Hapus"),
          content: Text("Ingin menghapus acara?"),
          actions: <Widget>[
            FlatButton(
              child: Text("Ya"),
              /* Saat mengetap/mengklik tombol Ya pada dialog */
              onPressed: () {
                /* Membuka database */
                databaseProvider.open().then((value){
                  /* Menghapus event dari database */
                  databaseProvider.deleteEvent(_event.eventId).then((event) {
                    /* Menampilkan Toast setelah penghapusan */
                    Fluttertoast.showToast(
                        msg: "Acara berhasil dihapus",
                        toastLength: Toast.LENGTH_SHORT,
                        gravity: ToastGravity.CENTER,
                        timeInSecForIos: 1
                    );
                    /* Menghapus item event dari _listEvent.
                       Menggunakan setState agar item-item pada
                       _listEvent diperbarui sehingga daftar item
                       pada ListView ikut diperbarui
                    */
                    setState(() {
                      _listEvent.remove(event);
                    });
                    /* Menutup database */
                    databaseProvider.close();
                    /* Menutup dialog */
                    Navigator.of(context).pop();
                  });
                });
              },
            ),
            FlatButton(
              child: Text("Tidak"),
              /* Saat mengetap/mengklik tombol Tidak pada dialog */
              onPressed: () {
                /* Menutup dialog */
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {

    Widget _loading = Center (
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          CircularProgressIndicator(),
          SizedBox(height: 10,),
          Text("Menyiapkan Data")
        ],
      ),
    );

    Widget  _blank = Center(
      child: Container(
        height: 40,
        child: Text("Belum ada acara tersedia"),
      ),
    );

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.refresh),
            tooltip: 'Refresh',
            /* Memuat data pada ListView saat tombol Refresh ditap */
            onPressed: _refreshList
          ),
        ],
      ),
      body: Container(
        padding: EdgeInsets.all(18.0),
        decoration: BoxDecoration(
          color: Colors.brown.shade200,
        ),
        child: Card(
          elevation: 4.0,
          color: Colors.brown.shade100,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(
              Radius.circular(8.0),
            ),
            side: BorderSide(
              color: Colors.brown.shade400,
              width: 1.0,
              style: BorderStyle.solid,
            ),
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisSize: MainAxisSize.max,
            children: <Widget>[
              Padding(
                padding: EdgeInsets.fromLTRB(
                  16.0, 16.0, 16.0, 8.0
                ),
                child: Text(
                  "Silakan memilih acara tersedia, atau tap pada tombol tambah di bawah untuk membuat acara baru",
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 16.0,
                  ),
                ),
              ),
              Expanded(
                child: Padding(
                  padding: EdgeInsets.symmetric(
                    vertical: 8.0,
                    horizontal: 16.0,
                  ),
                  /* Menggunakan FutureBuilder untuk mengakses database */
                  child: FutureBuilder(
                    future: databaseProvider.open(),
                    builder: (BuildContext context, AsyncSnapshot snapshot){
                      /* Saat koneksi ke database berhasil terhubung */
                      if (snapshot.connectionState == ConnectionState.done) {
                        return FutureBuilder(
                          /* Menggunakan FutureBuilder untuk mengambil daftar event */
                          future: databaseProvider.getListEvent(),
                          builder: (BuildContext context, AsyncSnapshot snapshot){
                            /* Saat pemanggilan fungsi mendapatkan data */
                            if (snapshot.hasData) {
                              if (snapshot.data != null) {
                                /* Mengisi variabel _listEvent dari data snapshot */
                                _listEvent = snapshot.data;
                                databaseProvider.close();

                                /* Saat _listEvent kosong */
                                if (_listEvent.length == 0){
                                  /* Tampilkan widget _blank */
                                  return _blank;

                                /* Saat _listEvent tidak kosong */
                                } else {
                                  /* Bangun widget ListView */
                                  return ListView.builder(
                                    itemCount: _listEvent.length,
                                    itemBuilder: listViewBuilder,
                                  );
                                }
                              } else {
                                return _blank;
                              }
                            } else {
                              return _blank;
                            }
                          },
                        );
                      } else {
                        return _loading;
                      }
                    },
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        /* Saat FloatingActionButton ditap */
        onPressed:(){
          /* Null-kan nilai _event */
          setState(() {
            _event = null;
          });
          /* Memanggil EventActivity */
          showEventActivity();
        },
        backgroundColor: Colors.brown.shade200,
        tooltip: 'Tambah Acara',
        child: Icon(Icons.add_box,
          color: Colors.black45,
        ),
      ),
    );
  }
}
Tampilan MainActivity Saat Dijalankan
Tampilan MainActivity Saat Dijalankan

Silakan membaca komentar-komentar yang ditulis pada kode sumber untuk memahami alurnya. Jika ada yang kurang jelas, silakan tanyakan pada kolom komentar di bawah artikel ini. Kode sumber aplikasi ini dapat Anda unduh di: https://github.com/InochiSoft/buku_tamu_flutter.