Flutterize : Membuat Todo List App Menggunakan Flutter dan Cloud Firestore (PART 3)

Flutterize : Membuat Todo List App Menggunakan Flutter dan Cloud Firestore (PART 3)

Setelah pada artikel sebelemnya kita sudah bisa membuat operasi insert, selanjutnya kita akan membuat operasi delete pada aplikasi yang kita buat sebelumnya.

Pertama, tambahkan jadikan Dismissible Widget menjadi parent widget dari ListTile. Berikan parameter key dengan isi documentID, background diisi oleh Container Widget dengan memberikan parameter color merah. jangan lupa berikan parameter onDismissed untuk handle widget ketika di swipe kesamping supaya melakukan proses delete pada Cloud Firestore.

Sehingga kodenya menjadi seperti ini.

return ListView.builder(
  itemCount: snapshot.data.documents.length,
  itemBuilder: (context, index) {
    return Dismissible(
      key: Key(snapshot.data.documents[index].documentID),
      background: Container(color: Colors.red,),
      onDismissed: (direction) async {},
      child: ListTile(
        title: Text(snapshot.data.documents[index]['task']),
        subtitle: Text(snapshot.data.documents[index]['description']),
      ),
    );
  },
);

Selanjutnya, berikan proses delete pada parameter onDismissed.

Firestore.instance.collection('todo').document(snapshot.data.documents[index].documentID).delete();

Sehingga kode menjadi seperti ini.

return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
return Dismissible(
key: Key(snapshot.data.documents[index].documentID),
background: Container(color: Colors.red,),
onDismissed: (direction) async {
Firestore.instance.collection('todo').document(snapshot.data.documents[index].documentID).delete();
},
child: ListTile(
title: Text(snapshot.data.documents[index]['task']),
subtitle: Text(snapshot.data.documents[index]['description']),
),
);
},
);

Maka aplikasi nya akan menjadi seperti ini.

Untuk proses edit nya akan ada di artikel selanjutnya.

See Ya’

Link Project : Github

Flutterize : Membuat Todo List App Menggunakan Flutter dan Cloud Firestore (PART 2)

Flutterize : Membuat Todo List App Menggunakan Flutter dan Cloud Firestore (PART 2)

Setelah kemarin kita berhasil menampilkan list data dari database yang sudah dibuat di Cloud Firestore, pada postingan ini kita akan membuat operasi insert ke dalam database Cloud Firestore.

Jika belum melihat postingan sebelumnya, silahkan dilihat terlebih dahulu. Link disediakan di bawah.
Flutterize : Membuat Todo List App Menggunakan Flutter dan Cloud Firestore (PART 1)

Pertama, buat Floating Button di pojok kanan bawah untuk berpindah ke screen untuk insert data.

floatingActionButton: FloatingActionButton(
  child: Icon(Icons.add),
  onPressed: () {},
),

Kedua, buat class baru untuk menampung tampilan form insert.

class InsertScreen extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return InsertScreenState();
  }
}

class InsertScreenState extends State<InsertScreen>{
  @override
  Widget build(BuildContext context) {
    return Scaffold();
  }
}

Selanjutnya, buat App Bar. Sehingga kodenya menjadi seperti ini.

class InsertScreenState extends State<InsertScreen>{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Add New Task"),
        leading: IconButton(
          onPressed: () => Navigator.pop(context),
          icon: Icon(Icons.arrow_back),
        ),
      ),
    );
  }
}

Selanjutnya, buat form untuk memasukkan value yang akan di kirim ke database Cloud Firestore.

Buat controller untuk field nya terlebih dahulu.

TextEditingController titleTaskController = TextEditingController();
TextEditingController descTaskController = TextEditingController();

Setelah itu buat layoutnya.

body: SingleChildScrollView(
  padding: EdgeInsets.all(20),
  child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          "Task Name",
          style: TextStyle(
            fontWeight: FontWeight.w600,
          ),
        ),
        SizedBox(
          height: 8,
        ),
        TextFormField(
          validator: (value) {
            if (value.isEmpty || value.trim().length == 0) {
              return "Task name cannot be empty";
            }
            return null;
          },
          controller: titleTaskController,
          style: TextStyle(
            color: Colors.black,
          ),
          decoration: InputDecoration(
            border: OutlineInputBorder(),
            hintText: "Insert Task Name....",
          ),
        ),
        SizedBox(
          height: 12,
        ),
        Text(
          "Task Description",
          style: TextStyle(
            fontWeight: FontWeight.w600,
          ),
        ),
        SizedBox(
          height: 8,
        ),
        TextFormField(
          controller: descTaskController,
          validator: (value) {
            if (value.isEmpty || value.trim().length == 0) {
              return "Task description cannot be empty";
            }
            return null;
          },
          style: TextStyle(
            color: Colors.black,
          ),
          maxLines: 4,
          decoration: InputDecoration(
            border: OutlineInputBorder(),
            hintText: "Insert Task Description....",
          ),
        ),
        SizedBox(
          height: 24,
        ),
        FlatButton(
          minWidth: MediaQuery.of(context).size.width,
          padding: EdgeInsets.all(10),
          color: Colors.blue,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
          child: Text(
            "Submit",
            style: TextStyle(
              color: Colors.white,
              fontSize: 20,
            ),
          ),
          onPressed: () {
            if (insertFormKey.currentState.validate()){

            } else {

            }
          },
        ),
      ],
  ),
),

Jangan lupa memasukkan layout tersebut kedalam widget Form supaya bisa membuat validasi form. Sehingga kode menjadi seperti ini.

body: SingleChildScrollView(
  padding: EdgeInsets.all(20),
  child: Form(
    autovalidateMode: AutovalidateMode.always,
    key: insertFormKey,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          "Task Name",
          style: TextStyle(
            fontWeight: FontWeight.w600,
          ),
        ),
        SizedBox(
          height: 8,
        ),
        TextFormField(
          validator: (value) {
            if (value.isEmpty || value.trim().length == 0) {
              return "Task name cannot be empty";
            }
            return null;
          },
          controller: titleTaskController,
          style: TextStyle(
            color: Colors.black,
          ),
          decoration: InputDecoration(
            border: OutlineInputBorder(),
            hintText: "Insert Task Name....",
          ),
        ),
        SizedBox(
          height: 12,
        ),
        Text(
          "Task Description",
          style: TextStyle(
            fontWeight: FontWeight.w600,
          ),
        ),
        SizedBox(
          height: 8,
        ),
        TextFormField(
          controller: descTaskController,
          validator: (value) {
            if (value.isEmpty || value.trim().length == 0) {
              return "Task description cannot be empty";
            }
            return null;
          },
          style: TextStyle(
            color: Colors.black,
          ),
          maxLines: 4,
          decoration: InputDecoration(
            border: OutlineInputBorder(),
            hintText: "Insert Task Description....",
          ),
        ),
        SizedBox(
          height: 24,
        ),
        FlatButton(
          minWidth: MediaQuery.of(context).size.width,
          padding: EdgeInsets.all(10),
          color: Colors.blue,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
          child: Text(
            "Submit",
            style: TextStyle(
              color: Colors.white,
              fontSize: 20,
            ),
          ),
          onPressed: () async {},
        ),
      ],
    ),
  ),
),

Tambahkan juga key form nya.

final insertFormKey = GlobalKey<FormState>();

Setelah itu, tambahkan library rflutter_alert dan juga progress_dialog di pubspec.yaml.

rflutter_alert: any
progress_dialog: any

Buat layout untuk loading, success alert, dan juga error alertnya.

ProgressDialog progressDialog(BuildContext ctx) {
  ProgressDialog loadingDialog = ProgressDialog(
    ctx,
    type: ProgressDialogType.Normal,
    isDismissible: false,
  );
  loadingDialog.style(
    message: "Loading",
    progressWidget: Container(
      padding: EdgeInsets.all(8.0),
      child: CircularProgressIndicator(
        backgroundColor: Colors.blue,
      ),
    ),
    backgroundColor: Colors.white,
    elevation: 10.0,
    insetAnimCurve: Curves.easeInOut,
    messageTextStyle: TextStyle(
      color: Colors.blue,
    ),
  );
  return loadingDialog;
}

successAlert(String title, String subtitle, BuildContext ctx) {
  return Alert(
    context: ctx,
    title: title,
    desc: subtitle,
    type: AlertType.success,
    buttons: [
      DialogButton(
        onPressed: () {
          Navigator.pop(ctx);
        },
        child: Text(
          "Ok",
          style: TextStyle(color: Colors.white, fontSize: 20),
        ),
      ),
    ],
    style: AlertStyle(
      animationType: AnimationType.grow,
      isCloseButton: false,
      isOverlayTapDismiss: false,
      descStyle: TextStyle(fontWeight: FontWeight.bold),
      descTextAlign: TextAlign.center,
      animationDuration: Duration(milliseconds: 400),
      alertBorder: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10),
        side: BorderSide(
          color: Colors.grey,
        ),
      ),
      titleStyle: TextStyle(
        color: Colors.blue,
      ),
      alertAlignment: Alignment.center,
    ),
  ).show();
}

errorAlert(String title, String subtitle, BuildContext ctx) {
  return Alert(
    context: ctx,
    title: title,
    desc: subtitle,
    type: AlertType.warning,
    buttons: [
      DialogButton(
        onPressed: () => Navigator.pop(ctx),
        child: Text(
          "OK",
          style: TextStyle(color: Colors.white, fontSize: 20),
        ),
      ),
    ],
    style: AlertStyle(
      animationType: AnimationType.grow,
      isCloseButton: false,
      isOverlayTapDismiss: false,
      descStyle: TextStyle(fontWeight: FontWeight.bold),
      descTextAlign: TextAlign.center,
      animationDuration: Duration(milliseconds: 400),
      alertBorder: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10),
        side: BorderSide(
          color: Colors.grey,
        ),
      ),
      titleStyle: TextStyle(
        color: Colors.red,
      ),
      alertAlignment: Alignment.center,
    ),
  ).show();
}

Selanjutnya, buat proses insert dari form yang sudah kita buat tadi. Tempatkan proses nya ketika tombol submit di tekan.

if (insertFormKey.currentState.validate()){
  progressDialog(context).show();
  DocumentReference result = await Firestore.instance.collection('todo').add(<String, dynamic>{
    'task' : titleTaskController.text.toString(),
    'description' : descTaskController.text.toString(),
  });
  if (result.documentID != null){
    progressDialog(context).hide();
    successAlert("Success", "Success Insert Task", context);
  } else {
    progressDialog(context).hide();
    errorAlert("Failed", "Failed to Insert Task", context);
  }
} else {
  errorAlert("Failed", "Please fill all the fields", context);
}

Setelah itu tambahkan route dari MyHomePage menuju InsertScreen pada FloatingActionButton.

floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InsertScreen(),
)),
),

Nah Setelah sampai sini, bisa dicoba. Namun list nya tidak akan berubah kecuali aplikasi di restart.

Solusinya adalah dengan merubah parent widget ListView dari FutureBuilder menjadi StreamBuilder. Berikut adalah kodenya.

body: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('todo').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.blue,
),
);
}
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data.documents[index]['task']),
subtitle: Text(snapshot.data.documents[index]['description']),
);
},
);
},
),

Maka aplikasi nya akan menjadi seperti ini.

Untuk proses edit, dan delete nya akan ada di artikel selanjutnya.

See Ya’

Link Project : Github

Flutterize : Membuat Todo List App Menggunakan Flutter dan Cloud Firestore (PART 1)

Flutterize : Membuat Todo List App Menggunakan Flutter dan Cloud Firestore (PART 1)

Pada kesempatan kali ini, kita akan membuat aplikasi Todo List menggunakan Flutter dan Firebase Cloud Firestore. Khusus untuk post kali ini, kita akan membahas mengenai cara membaca data atau get data dari Firestore. Untuk proses Insert, Edit, dan Delete nya akan ada di post selanjutnya.

Sebelum lanjut pembuatan aplikasi nya, kita akan berkenalan dahulu dengan Cloud Firestore ini.

Cloud Firestore adalah database yang fleksibel dan skalabel untuk pengembangan seluler, web, dan server di Firebase dan Google Cloud Platform.

– Documentasi Firestore

Dengan kata lain, Firestore merupakan cloud database yang bermodelkan NoSQL. Firestore menyimpan data sebagai kumpulan dokumen. Sedangkan Realtime Database menyimpan data sebagai JSON. Cloud Firestore juga menampilkan kueri yang lebih kaya dan lebih cepat serta menskalakan lebih jauh daripada Realtime Database.

Menyiapkan Projek di Firebase

Siapkan akun Google di Firebase nya. Masuk ke Console.

Pertama, buat project di Firebase terlebih dahulu.

Selanjutnya, buat inisialisasi projeknya dan berikan konfigurasi dasar seperti dibawah.

Selanjutnya, kita akan di bawa ke dashboard dari console Firebase. Tambahkan projek sesuai dengan platform yang akan dibuat. Untuk kali ini, kita akan memilih platform android.

Karena kita akan membuat aplikasi android, maka kita pilih platformnya. Dilangkah pertama ini, masukkan nama package dan juga nama aplikasinya (optional). Untuk sementara, kode sertifikat SHA-1 nya di kosongkan.

Di step kedua, download file JSON yang telah di generate oleh Firebase. File ini akan disimpan di folder android/app/ .

Di step ketiga akan di beritahu untuk menambahkan Firebase SDK. (step ini akan di bahas selanjutnya).

Selesaikan pendaftaran aplikasi androidnya.

Inisialisasi Cloud Firestore

Selanjutnya, pilih Cloud Firestore yang ada di sidebar console, lalu pilih Buat database. Atur Firestore ke dalam metode pengujian. Lokasi server bisa disesuaikan dengan preferensi.

Instalasi Plugin

Beralih ke projek flutternya yang sudah dibuat menggunakan Android Studio atau Visual Studio Code. Tambahkan file google-services.json yang telah kita download ke direktori project flutter nya. Masukkan ke android/app/

Tambahkan dependency google services di direktori android/build.gradle.

dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.gms:google-services:4.3.3'
    }

Pada direktori android/app/build.gradle apply plugin google services dan juga tambahkan dependency google services serta firebase core.

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.gms.google-services'
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.google.gms:google-services:4.3.3'
    implementation 'com.google.firebase:firebase-core:17.4.4'
}

Jangan lupa juga menambahkan setting untuk multi dex supaya tidak muncul error seperti ini.

Solusi nya adalah dengan mengatur multi dex menjadi enabled. Pengaturannya terdapat pada android/app/build.gradle

defaultConfig {
applicationId "com.quifar.todo_list_firestore"
minSdkVersion 16
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.gms:google-services:4.3.3'
implementation 'com.google.firebase:firebase-core:17.4.4'
implementation 'com.android.support:multidex:1.0.3'
implementation "androidx.multidex:multidex:2.0.1"
}

Tambahkan dependency Flutter cloud_firestore dan juga firebase_core pada file pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.1
  cloud_firestore: ^0.13.5
  firebase_core: ^0.4.0+9

Selanjutnya pada teminal (sesuaikan dengan direktori project), ketikkan command dibawah.

flutter pub get

Selanjutnya, kita pindah ke Firestore di Console Firebase. Lalu pilih mulai koleksi. Isi nama koleksi sesuai dengan keinginan.

Lalu tambahkan dokumen pertamanya. Klik ID Otomatis untuk menggunakan id unik otomatis. Jangan lupa Tambahkan kolom beserta isinya untuk inisialisasi koleksi.

Kembali ke projek Flutternya. Bersihkan file lib/main.dart dari komentar supaya lebih rapih. Serta jadikan class _MyHomePageState menjadi seperti ini.

Coding

Selanjutnya tambahkan plugin cloud_firestore import

import 'package:cloud_firestore/cloud_firestore.dart';

pada parameter body kita akan menggunakan Widget FutureBuilder untuk mengambil data dari koleksi Firestore yang sudah kita buat. Parameter future nya diisikan code yang akan return hasil query dari Cloud Firestore.

body: FutureBuilder(
  future: Firestore.instance.collection('todo').getDocuments(),
),

Selanjutnya, tambahkan builder pada widget tersebut untuk membuat Loading CircleProgressIndicator dan juga ListView untuk menampung hasil query yang kita buat sebelumnya.

builder: (context, snapshot) {
  if (!snapshot.hasData) {
    return Center(
      child: CircularProgressIndicator(
        backgroundColor: Colors.blue,
      ),
    );
  }
},

Kode diatas akan mengatasi jika querynya belum atau tidak memiliki data maka akan ditampilkan Loading CircleProgressIndicator.

Selanjutnya, kita akan membuat kode yang akan mengatasi ketika query memiliki hasil atau data. Buat return menjadi ListView.builder seperti ini.

body: FutureBuilder(
  future: Firestore.instance.collection('todo').getDocuments(),
  builder: (context, snapshot) {
    if (!snapshot.hasData) {
      return Center(
        child: CircularProgressIndicator(
          backgroundColor: Colors.blue,
        ),
      );
    }
    return ListView.builder();
  },
),

Lalu berikan parameter itemCount dan isi dengan panjang dari datanya.

return ListView.builder(
  itemCount: snapshot.data.documents.length,
);

Selanjutnya, kita buat layout dari ListView tersebut. Kita akan menggunakan widget ListTile.

return ListView.builder(
  itemCount: snapshot.data.documents.length,
  itemBuilder: (context, index) {
    return ListTile();
  },
);

Di dalam ListTile kita akan menampilkan kolom ‘title’ dan juga ‘description’.

return ListView.builder(
  itemCount: snapshot.data.documents.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(snapshot.data.documents[index]['task']),
      subtitle: Text(snapshot.data.documents[index]['description']),
    );
  },
);

Pada hasil akhir, kodenya akan menjadi seperti ini.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

void main() async {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;

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

class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: Firestore.instance.collection('todo').getDocuments(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.blue,
),
);
}
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data.documents[index]['task']),
subtitle: Text(snapshot.data.documents[index]['description']),
);
},
);
},
),
);
}
}

Hasil Akhir

Tampilan nya akan menjadi seperti ini.

Untuk proses insert, edit, dan delete nya akan ada di artikel selanjutnya.

See Ya’

Link Project : Github