Kenali Firebase untuk Flutter

1. Sebelum memulai

Dalam codelab ini, Anda akan mempelajari beberapa dasar Firebase untuk membuat aplikasi seluler Flutter untuk Android dan iOS.

Prasyarat

Yang akan Anda pelajari

  • Cara membangun aplikasi RSVP dan chat buku tamu acara di Android, iOS, Web, dan macOS dengan Flutter.
  • Cara mengautentikasi pengguna dengan Firebase Authentication dan menyinkronkan data dengan Firestore.

Layar utama aplikasi di Android

Layar utama aplikasi di iOS

Yang Anda butuhkan

Salah satu perangkat berikut:

  • Perangkat Android atau iOS fisik yang terhubung ke komputer dan disetel ke mode developer.
  • Simulator iOS (Memerlukan alat Xcode).
  • Emulator Android (Memerlukan penyiapan di Android Studio).

Anda juga memerlukan hal berikut:

  • Browser pilihan Anda, seperti Google Chrome.
  • IDE atau editor teks pilihan Anda yang dikonfigurasi dengan plugin Dart dan Flutter, seperti Android Studio atau Visual Studio Code.
  • Versi stable terbaru Flutter atau beta jika Anda suka hidup di edge.
  • Akun Google untuk pembuatan dan pengelolaan project Firebase Anda.
  • Firebase CLI yang login ke Akun Google Anda.

2. Mendapatkan kode contoh

Download versi awal project Anda dari GitHub:

  1. Dari command line, clone repositori GitHub di direktori flutter-codelabs:
git clone https://github.com/flutter/codelabs.git flutter-codelabs

Direktori flutter-codelabs berisi kode untuk kumpulan codelab. Kode untuk codelab ini ada di direktori flutter-codelabs/firebase-get-to-know-flutter. Direktori tersebut berisi serangkaian snapshot yang menunjukkan tampilan project Anda di akhir setiap langkah. Misalnya, Anda berada di langkah kedua.

  1. Temukan file yang cocok untuk langkah kedua:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02

Jika Anda ingin melewati atau melihat tampilan suatu langkah setelah suatu langkah, lihat direktori yang dinamai berdasarkan langkah yang Anda minati.

Mengimpor aplikasi awal

  • Buka atau impor direktori flutter-codelabs/firebase-get-to-know-flutter/step_02 di IDE pilihan Anda. Direktori ini berisi kode awal untuk codelab, yang terdiri dari aplikasi pertemuan Flutter yang belum berfungsi.

Menemukan file yang perlu diperbaiki

Kode dalam aplikasi ini tersebar di beberapa direktori. Pemisahan fungsi ini membuat pekerjaan lebih mudah karena mengelompokkan kode berdasarkan fungsionalitas.

  • Temukan file berikut:
    • lib/main.dart: File ini berisi titik entri utama dan widget aplikasi.
    • lib/home_page.dart: File ini berisi widget halaman beranda.
    • lib/src/widgets.dart: File ini berisi beberapa widget untuk membantu menstandarkan gaya aplikasi. Widget tersebut menyusun layar aplikasi awal.
    • lib/src/authentication.dart: File ini berisi sebagian penerapan Authentication dengan sekumpulan widget untuk membuat pengalaman pengguna login untuk autentikasi berbasis email Firebase. Widget untuk alur autentikasi ini belum digunakan di aplikasi awal, tetapi akan segera ditambahkan.

Anda menambahkan file lain sesuai kebutuhan untuk membangun seluruh aplikasi.

Tinjau file lib/main.dart

Aplikasi ini memanfaatkan paket google_fonts untuk menjadikan Roboto sebagai font default di seluruh aplikasi. Anda dapat menjelajahi fonts.google.com dan menggunakan font yang Anda temukan di berbagai bagian aplikasi.

Anda menggunakan widget helper dari file lib/src/widgets.dart dalam bentuk Header, Paragraph, dan IconAndDetail. Widget ini menghilangkan kode duplikat untuk mengurangi kekacauan dalam tata letak halaman yang dijelaskan di HomePage. Hal ini juga memungkinkan tampilan dan nuansa yang konsisten.

Berikut tampilan aplikasi Anda di Android, iOS, Web, dan macOS:

Layar utama aplikasi di Android

Layar utama aplikasi di iOS

Layar utama aplikasi di web

Layar utama aplikasi di macOS

3. Membuat dan mengonfigurasi project Firebase

Tampilan informasi acara bagus untuk dilihat oleh tamu Anda, tetapi tidak terlalu berguna bagi siapa pun. Anda perlu menambahkan beberapa fungsi dinamis ke aplikasi. Untuk melakukannya, Anda perlu menghubungkan Firebase ke aplikasi Anda. Untuk memulai Firebase, Anda perlu membuat dan mengonfigurasi project Firebase.

Membuat project Firebase

  1. Login ke Firebase.
  2. Di konsol, klik Add Project atau Create a project.
  3. Di kolom Project name, masukkan Firebase-Flutter-Codelab, lalu klik Continue.

4395e4e67c08043a.pngS

  1. Klik opsi pembuatan project. Jika diminta, setujui persyaratan Firebase, tetapi lewati penyiapan Google Analytics karena Anda tidak akan menggunakannya untuk aplikasi ini.

b7138cde5f2c7b61.pngS

Untuk mempelajari project Firebase lebih lanjut, lihat artikel Memahami project Firebase.

Aplikasi ini menggunakan produk Firebase berikut, yang tersedia untuk aplikasi web:

  • Authentication: Memungkinkan pengguna login ke aplikasi Anda.
  • Firestore: Menyimpan data terstruktur di cloud dan mendapatkan notifikasi instan saat data berubah.
  • Aturan Keamanan Firebase: Mengamankan database Anda.

Beberapa produk ini memerlukan konfigurasi khusus atau Anda harus mengaktifkannya di Firebase console.

Aktifkan autentikasi login dengan email

  1. Di panel Project overview Firebase console, luaskan menu Build.
  2. Klik Authentication > Mulai > Metode login > Email/Sandi > Aktifkan > Simpan.

58e3e3e23c2f16a4.pngS

Mengaktifkan Firestore

Aplikasi web menggunakan Firestore untuk menyimpan pesan chat dan menerima pesan chat baru.

Aktifkan Firestore:

  • Di menu Build, klik Firestore Database > Create database.

99e8429832d23fa3.pngS

  1. Pilih Mulai dalam mode pengujian, lalu baca pernyataan penyangkalan tentang aturan keamanan. Mode pengujian memastikan bahwa Anda dapat menulis database dengan bebas selama pengembangan.

6be00e26c72ea032.pngS

  1. Klik Next, lalu pilih lokasi untuk database Anda. Anda dapat menggunakan nilai default. Anda tidak dapat mengubah lokasi nanti.

278656eefcfb0216.pngS

  1. Klik Enable.

4. Mengonfigurasi Firebase

Untuk menggunakan Firebase dengan Flutter, Anda harus menyelesaikan tugas berikut untuk mengonfigurasi project Flutter agar menggunakan library FlutterFire dengan benar:

  1. Tambahkan dependensi FlutterFire ke project Anda.
  2. Daftarkan platform yang diinginkan di project Firebase.
  3. Download file konfigurasi khusus platform, lalu tambahkan ke kode.

Di direktori level teratas aplikasi Flutter Anda, ada subdirektori android, ios, macos dan web, yang masing-masing menyimpan file konfigurasi khusus platform untuk iOS dan Android.

Mengonfigurasi dependensi

Anda perlu menambahkan library FlutterFire untuk dua produk Firebase yang Anda gunakan dalam aplikasi ini: Authentication dan Firestore.

  • Dari command line, tambahkan dependensi berikut:
$ flutter pub add firebase_core

Paket firebase_core adalah kode umum yang diperlukan untuk semua plugin Firebase Flutter.

$ flutter pub add firebase_auth

Paket firebase_auth memungkinkan integrasi dengan Authentication.

$ flutter pub add cloud_firestore

Paket cloud_firestore memungkinkan akses ke penyimpanan data Firestore.

$ flutter pub add provider

Paket firebase_ui_auth menyediakan sekumpulan widget dan utilitas untuk meningkatkan kecepatan developer dengan alur autentikasi.

$ flutter pub add firebase_ui_auth

Anda telah menambahkan paket yang diperlukan, tetapi Anda juga perlu mengonfigurasi project iOS, Android, macOS, dan Web runner agar dapat menggunakan Firebase dengan tepat. Anda juga akan menggunakan paket provider yang memungkinkan pemisahan logika bisnis dari logika tampilan.

Menginstal FlutterFire CLI

FlutterFire CLI bergantung pada Firebase CLI yang mendasarinya.

  1. Jika Anda belum melakukannya, instal Firebase CLI di komputer Anda.
  2. Instal FlutterFire CLI:
$ dart pub global activate flutterfire_cli

Setelah diinstal, perintah flutterfire akan tersedia secara global.

Mengonfigurasi aplikasi Anda

CLI akan mengekstrak informasi dari project Firebase Anda dan aplikasi project terpilih untuk menghasilkan semua konfigurasi untuk platform tertentu.

Di root aplikasi Anda, jalankan perintah configure:

$ flutterfire configure

Perintah konfigurasi memandu Anda melalui proses berikut:

  1. Pilih project Firebase berdasarkan file .firebaserc atau dari Firebase Console.
  2. Menentukan platform untuk konfigurasi, seperti Android, iOS, macOS, dan web.
  3. Mengidentifikasi aplikasi Firebase tempat mengekstrak konfigurasi. Secara default, CLI akan mencoba mencocokkan aplikasi Firebase secara otomatis berdasarkan konfigurasi project Anda saat ini.
  4. Buat file firebase_options.dart di project Anda.

Mengonfigurasi macOS

Flutter di macOS membangun aplikasi yang sepenuhnya di-sandbox. Karena aplikasi ini terintegrasi dengan jaringan untuk berkomunikasi dengan server Firebase, Anda perlu mengonfigurasi aplikasi dengan hak istimewa klien jaringan.

macos/Runner/DebugProfile.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

macos/Runner/Release.hak

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

Untuk mengetahui informasi selengkapnya, lihat Dukungan desktop untuk Flutter.

5. Menambahkan fungsi RSVP

Setelah menambahkan Firebase ke aplikasi, Anda dapat membuat tombol RSVP yang mendaftarkan orang dengan Authentication. Untuk native Android, native iOS, dan Web, ada paket FirebaseUI Auth bawaan, tetapi Anda harus membangun kemampuan ini untuk Flutter.

Project yang Anda ambil sebelumnya menyertakan sekumpulan widget yang mengimplementasikan antarmuka pengguna untuk sebagian besar alur autentikasi. Anda akan menerapkan logika bisnis untuk mengintegrasikan Authentication dengan aplikasi.

Menambahkan logika bisnis dengan paket Provider

Gunakan paket provider untuk menyediakan objek status aplikasi terpusat di seluruh hierarki widget Flutter aplikasi:

  1. Buat file baru bernama app_state.dart dengan konten berikut:

lib/app_state.dart

import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

class ApplicationState extends ChangeNotifier {
  ApplicationState() {
    init();
  }

  bool _loggedIn = false;
  bool get loggedIn => _loggedIn;

  Future<void> init() async {
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform);

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
      } else {
        _loggedIn = false;
      }
      notifyListeners();
    });
  }
}

Pernyataan import memperkenalkan Firebase Core dan Auth, menarik paket provider yang membuat objek status aplikasi tersedia di seluruh hierarki widget, dan menyertakan widget autentikasi dari paket firebase_ui_auth.

Objek status aplikasi ApplicationState ini memiliki satu tanggung jawab utama untuk langkah ini, yaitu memberi tahu hierarki widget bahwa ada update pada status terautentikasi.

Anda hanya menggunakan penyedia untuk mengomunikasikan status status login pengguna ke aplikasi. Untuk mengizinkan pengguna login, gunakan UI yang disediakan oleh paket firebase_ui_auth, yang merupakan cara bagus untuk mem-bootstrap layar login dengan cepat di aplikasi Anda.

Mengintegrasikan alur autentikasi

  1. Ubah impor di bagian atas file lib/main.dart:

lib/main.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';               // new
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';                 // new

import 'app_state.dart';                                 // new
import 'home_page.dart';
  1. Hubungkan status aplikasi dengan inisialisasi aplikasi, lalu tambahkan alur autentikasi ke HomePage:

lib/main.dart

void main() {
  // Modify from here...
  WidgetsFlutterBinding.ensureInitialized();

  runApp(ChangeNotifierProvider(
    create: (context) => ApplicationState(),
    builder: ((context, child) => const App()),
  ));
  // ...to here.
}

Modifikasi pada fungsi main() membuat paket penyedia bertanggung jawab atas pembuatan instance objek status aplikasi dengan widget ChangeNotifierProvider. Anda menggunakan class provider khusus ini karena objek status aplikasi memperluas class ChangeNotifier, yang memungkinkan paket provider mengetahui kapan harus menampilkan ulang widget dependen.

  1. Update aplikasi Anda untuk menangani navigasi ke berbagai layar yang disediakan FirebaseUI untuk Anda, dengan membuat konfigurasi GoRouter:

lib/main.dart

// Add GoRouter configuration outside the App class
final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
      routes: [
        GoRoute(
          path: 'sign-in',
          builder: (context, state) {
            return SignInScreen(
              actions: [
                ForgotPasswordAction(((context, email) {
                  final uri = Uri(
                    path: '/sign-in/forgot-password',
                    queryParameters: <String, String?>{
                      'email': email,
                    },
                  );
                  context.push(uri.toString());
                })),
                AuthStateChangeAction(((context, state) {
                  final user = switch (state) {
                    SignedIn state => state.user,
                    UserCreated state => state.credential.user,
                    _ => null
                  };
                  if (user == null) {
                    return;
                  }
                  if (state is UserCreated) {
                    user.updateDisplayName(user.email!.split('@')[0]);
                  }
                  if (!user.emailVerified) {
                    user.sendEmailVerification();
                    const snackBar = SnackBar(
                        content: Text(
                            'Please check your email to verify your email address'));
                    ScaffoldMessenger.of(context).showSnackBar(snackBar);
                  }
                  context.pushReplacement('/');
                })),
              ],
            );
          },
          routes: [
            GoRoute(
              path: 'forgot-password',
              builder: (context, state) {
                final arguments = state.uri.queryParameters;
                return ForgotPasswordScreen(
                  email: arguments['email'],
                  headerMaxExtent: 200,
                );
              },
            ),
          ],
        ),
        GoRoute(
          path: 'profile',
          builder: (context, state) {
            return ProfileScreen(
              providers: const [],
              actions: [
                SignedOutAction((context) {
                  context.pushReplacement('/');
                }),
              ],
            );
          },
        ),
      ],
    ),
  ],
);
// end of GoRouter configuration

// Change MaterialApp to MaterialApp.router and add the routerConfig
class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Firebase Meetup',
      theme: ThemeData(
        buttonTheme: Theme.of(context).buttonTheme.copyWith(
              highlightColor: Colors.deepPurple,
            ),
        primarySwatch: Colors.deepPurple,
        textTheme: GoogleFonts.robotoTextTheme(
          Theme.of(context).textTheme,
        ),
        visualDensity: VisualDensity.adaptivePlatformDensity,
        useMaterial3: true,
      ),
      routerConfig: _router, // new
    );
  }
}

Setiap layar memiliki jenis tindakan berbeda yang terkait dengannya berdasarkan status baru alur autentikasi. Setelah sebagian besar perubahan status dalam autentikasi, Anda dapat mengubah rute kembali ke layar pilihan, baik itu layar utama maupun layar lain, seperti profil.

  1. Dalam metode build class HomePage, integrasikan status aplikasi dengan widget AuthFunc:

lib/home_page.dart

import 'package:firebase_auth/firebase_auth.dart' // new
    hide EmailAuthProvider, PhoneAuthProvider;    // new
import 'package:flutter/material.dart';           // new
import 'package:provider/provider.dart';          // new

import 'app_state.dart';                          // new
import 'src/authentication.dart';                 // new
import 'src/widgets.dart';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          // Add from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.signOut();
                }),
          ),
          // to here
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
        ],
      ),
    );
  }
}

Anda membuat instance widget AuthFunc dan menggabungkannya dalam widget Consumer. Widget Konsumen adalah cara biasa digunakan paket provider untuk membuat ulang bagian hierarki saat status aplikasi berubah. Widget AuthFunc adalah widget tambahan yang Anda uji.

Menguji alur autentikasi

cdf2d25e436bd48d.pngS

  1. Di aplikasi, ketuk tombol RSVP untuk memulai SignInScreen.

2a2cd6d69d172369.pngS

  1. Masukkan alamat email. Jika Anda sudah terdaftar, sistem akan meminta Anda untuk memasukkan sandi. Jika tidak, sistem akan meminta Anda untuk melengkapi formulir pendaftaran.

e5e65065dba36b54.pngS

  1. Masukkan sandi yang kurang dari enam karakter untuk memeriksa alur penanganan error. Jika sudah terdaftar, Anda akan melihat sandinya.
  2. Masukkan sandi yang salah untuk memeriksa alur penanganan error.
  3. Masukkan sandi yang benar. Anda akan melihat pengalaman login, yang menawarkan pengguna kemampuan untuk logout.

4ed811a25b0cf816.pngS

6. Menulis pesan ke Firestore

Senang rasanya mengetahui bahwa pengguna akan datang, tetapi Anda perlu memberi tamu hal lain untuk dilakukan di aplikasi. Bagaimana jika mereka dapat meninggalkan pesan di buku tamu? Mereka dapat menyampaikan mengapa mereka senang untuk datang atau siapa yang ingin mereka temui.

Untuk menyimpan pesan chat yang ditulis pengguna di aplikasi, Anda menggunakan Firestore.

Model data

Firestore adalah database NoSQL, dan data yang tersimpan di database dibagi menjadi beberapa koleksi, dokumen, kolom, dan subkoleksi. Anda menyimpan setiap pesan chat sebagai dokumen dalam koleksi guestbook, yang merupakan koleksi tingkat atas.

7c20dc8424bb1d84.pngS

Menambahkan pesan ke Firestore

Di bagian ini, Anda akan menambahkan fungsi bagi pengguna untuk menulis pesan ke database. Pertama, Anda menambahkan kolom formulir dan tombol kirim, lalu menambahkan kode yang menghubungkan elemen-elemen ini dengan database.

  1. Buat file baru bernama guest_book.dart, tambahkan widget stateful GuestBook untuk membuat elemen UI kolom pesan dan tombol kirim:

lib/guest_book.dart

import 'dart:async';

import 'package:flutter/material.dart';

import 'src/widgets.dart';

class GuestBook extends StatefulWidget {
  const GuestBook({required this.addMessage, super.key});

  final FutureOr<void> Function(String message) addMessage;

  @override
  State<GuestBook> createState() => _GuestBookState();
}

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Form(
        key: _formKey,
        child: Row(
          children: [
            Expanded(
              child: TextFormField(
                controller: _controller,
                decoration: const InputDecoration(
                  hintText: 'Leave a message',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Enter your message to continue';
                  }
                  return null;
                },
              ),
            ),
            const SizedBox(width: 8),
            StyledButton(
              onPressed: () async {
                if (_formKey.currentState!.validate()) {
                  await widget.addMessage(_controller.text);
                  _controller.clear();
                }
              },
              child: Row(
                children: const [
                  Icon(Icons.send),
                  SizedBox(width: 4),
                  Text('SEND'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Ada beberapa lokasi menarik di sini. Pertama, buat instance formulir sehingga Anda dapat memvalidasi bahwa pesan tersebut benar-benar berisi konten dan menampilkan pesan error kepada pengguna jika memang tidak ada. Untuk memvalidasi formulir, Anda mengakses status formulir di belakang formulir dengan GlobalKey. Untuk mengetahui informasi selengkapnya tentang Kunci dan cara menggunakannya, lihat Kapan harus Menggunakan Kunci.

Perhatikan juga cara widget ditata, Anda memiliki Row dengan TextFormField dan StyledButton, yang berisi Row. Perhatikan juga bahwa TextFormField digabungkan dalam widget Expanded, yang memaksa TextFormField untuk mengisi ruang ekstra di baris. Untuk lebih memahami mengapa hal ini diperlukan, lihat Memahami batasan.

Sekarang setelah Anda memiliki widget yang memungkinkan pengguna memasukkan teks untuk ditambahkan ke Buku Tamu, Anda perlu menampilkannya di layar.

  1. Edit isi HomePage untuk menambahkan dua baris berikut di akhir turunan ListView:
const Header("What we'll be doing"),
const Paragraph(
  'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
const Header('Discussion'),
GuestBook(addMessage: (message) => print(message)),

Meskipun cukup untuk menampilkan widget, hal ini tidak cukup untuk melakukan sesuatu yang berguna. Anda akan segera memperbarui kode ini agar dapat berfungsi.

Pratinjau aplikasi

Layar utama aplikasi di Android dengan integrasi chat

Layar utama aplikasi di iOS dengan integrasi chat

Layar utama aplikasi di web dengan integrasi chat

Layar utama aplikasi di macOS dengan integrasi chat

Saat pengguna mengklik SEND, cuplikan kode berikut akan terpicu. Kode ini menambahkan konten kolom input pesan ke koleksi guestbook database. Secara khusus, metode addMessageToGuestBook menambahkan konten pesan ke dokumen baru dengan ID yang dibuat secara otomatis dalam koleksi guestbook.

Perlu diingat bahwa FirebaseAuth.instance.currentUser.uid adalah referensi ke ID unik yang dibuat secara otomatis yang diberikan Authentication untuk semua pengguna yang login.

  • Di file lib/app_state.dart, tambahkan metode addMessageToGuestBook. Anda akan menghubungkan kemampuan ini dengan antarmuka pengguna di langkah berikutnya.

lib/app_state.dart

import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

  // Add from here...
  Future<DocumentReference> addMessageToGuestBook(String message) {
    if (!_loggedIn) {
      throw Exception('Must be logged in');
    }

    return FirebaseFirestore.instance
        .collection('guestbook')
        .add(<String, dynamic>{
      'text': message,
      'timestamp': DateTime.now().millisecondsSinceEpoch,
      'name': FirebaseAuth.instance.currentUser!.displayName,
      'userId': FirebaseAuth.instance.currentUser!.uid,
    });
  }
  // ...to here.
}

Menghubungkan UI dan database

Anda memiliki UI tempat pengguna dapat memasukkan teks yang ingin mereka tambahkan ke Buku Tamu dan Anda memiliki kode untuk menambahkan entri ke Firestore. Sekarang yang perlu Anda lakukan adalah menghubungkan keduanya.

  • Di file lib/home_page.dart, buat perubahan berikut pada widget HomePage:

lib/home_page.dart

import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'app_state.dart';
import 'guest_book.dart';                         // new
import 'src/authentication.dart';
import 'src/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          Consumer<ApplicationState>(
            builder: (context, appState, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.signOut();
                }),
          ),
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
          // Modify from here...
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (appState.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // ...to here.
        ],
      ),
    );
  }
}

Anda mengganti dua baris yang Anda tambahkan di awal langkah ini dengan implementasi penuh. Anda akan menggunakan kembali Consumer<ApplicationState> untuk membuat status aplikasi tersedia untuk bagian hierarki yang Anda render. Hal ini memungkinkan Anda bereaksi terhadap seseorang yang memasukkan pesan di UI dan memublikasikannya dalam database. Di bagian berikutnya, Anda akan menguji apakah pesan yang ditambahkan dipublikasikan dalam database.

Menguji pengiriman pesan

  1. Jika perlu, login ke aplikasi.
  2. Masukkan pesan, seperti Hey there!, lalu klik KIRIM.

Tindakan ini akan menulis pesan ke database Firestore Anda. Namun, Anda tidak melihat pesan di aplikasi Flutter yang sebenarnya karena Anda masih perlu menerapkan pengambilan data, yang akan Anda lakukan di langkah berikutnya. Namun, di dasbor Database Firebase console, Anda dapat melihat pesan yang ditambahkan di koleksi guestbook. Jika Anda mengirim lebih banyak pesan, Anda akan menambahkan lebih banyak dokumen ke koleksi guestbook Anda. Misalnya, lihat cuplikan kode berikut:

713870af0b3b63c.pngS

7. Membaca pesan

Sangat menyenangkan bahwa tamu dapat menulis pesan ke database, tetapi mereka belum dapat melihatnya di aplikasi. Saatnya memperbaikinya!

Menyinkronkan pesan

Untuk menampilkan pesan, Anda perlu menambahkan pemroses yang terpicu saat data berubah, lalu membuat elemen UI yang menampilkan pesan baru. Anda akan menambahkan kode ke status aplikasi yang memproses pesan yang baru ditambahkan dari aplikasi.

  1. Buat file baru guest_book_message.dart, tambahkan class berikut untuk menampilkan tampilan terstruktur data yang Anda simpan di Firestore.

lib/guest_book_message.dart

class GuestBookMessage {
  GuestBookMessage({required this.name, required this.message});

  final String name;
  final String message;
}
  1. Dalam file lib/app_state.dart, tambahkan impor berikut:

lib/app_state.dart

import 'dart:async';                                     // new

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';
import 'guest_book_message.dart';                        // new
  1. Di bagian ApplicationState tempat Anda menentukan status dan pengambil, tambahkan baris berikut:

lib/app_state.dart

  bool _loggedIn = false;
  bool get loggedIn => _loggedIn;

  // Add from here...
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // ...to here.
  1. Di bagian inisialisasi ApplicationState, tambahkan baris berikut untuk berlangganan kueri atas koleksi dokumen saat pengguna login dan berhenti berlangganan saat mereka logout:

lib/app_state.dart

  Future<void> init() async {
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform);

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);
    
    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
      } else {
        _loggedIn = false;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
      }
      notifyListeners();
    });
  }

Bagian ini penting karena di sinilah Anda membuat kueri pada koleksi guestbook, serta menangani proses berlangganan dan berhenti berlangganan koleksi ini. Anda memproses aliran data, tempat Anda merekonstruksi cache lokal pesan dalam koleksi guestbook dan juga menyimpan referensi ke langganan ini sehingga Anda dapat berhenti berlangganan di lain waktu. Ada banyak hal yang terjadi di sini, jadi Anda harus mempelajarinya dalam debugger untuk memeriksa apa yang terjadi guna mendapatkan model mental yang lebih jelas. Untuk mengetahui informasi selengkapnya, lihat Mendapatkan pembaruan realtime dengan Firestore.

  1. Di file lib/guest_book.dart, tambahkan impor berikut:
import 'guest_book_message.dart';
  1. Di widget GuestBook, tambahkan daftar pesan sebagai bagian dari konfigurasi untuk menghubungkan status yang berubah ini ke antarmuka pengguna:

lib/guest_book.dart

class GuestBook extends StatefulWidget {
  // Modify the following line:
  const GuestBook({
    super.key, 
    required this.addMessage, 
    required this.messages,
  });

  final FutureOr<void> Function(String message) addMessage;
  final List<GuestBookMessage> messages; // new

  @override
  _GuestBookState createState() => _GuestBookState();
}
  1. Di _GuestBookState, ubah metode build sebagai berikut untuk menampilkan konfigurasi ini:

lib/guest_book.dart

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  // Modify from here...
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // ...to here.
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Form(
            key: _formKey,
            child: Row(
              children: [
                Expanded(
                  child: TextFormField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: 'Leave a message',
                    ),
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return 'Enter your message to continue';
                      }
                      return null;
                    },
                  ),
                ),
                const SizedBox(width: 8),
                StyledButton(
                  onPressed: () async {
                    if (_formKey.currentState!.validate()) {
                      await widget.addMessage(_controller.text);
                      _controller.clear();
                    }
                  },
                  child: Row(
                    children: const [
                      Icon(Icons.send),
                      SizedBox(width: 4),
                      Text('SEND'),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
        // Modify from here...
        const SizedBox(height: 8),
        for (var message in widget.messages)
          Paragraph('${message.name}: ${message.message}'),
        const SizedBox(height: 8),
      ],
      // ...to here.
    );
  }
}

Gabungkan konten metode build() sebelumnya dengan widget Column, lalu tambahkan koleksi di bagian akhir turunan Column guna menghasilkan Paragraph baru untuk setiap pesan dalam daftar pesan.

  1. Perbarui isi HomePage untuk membuat GuestBook dengan benar menggunakan parameter messages baru:

lib/home_page.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      if (appState.loggedIn) ...[
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages, // new
        ),
      ],
    ],
  ),
),

Menguji sinkronisasi pesan

Firestore secara otomatis dan langsung menyinkronkan data dengan klien yang berlangganan database.

Menguji sinkronisasi pesan:

  1. Di aplikasi, temukan pesan yang sudah Anda buat sebelumnya di database.
  2. Menulis pesan baru. Mereka akan langsung muncul.
  3. Buka ruang kerja Anda di beberapa jendela atau tab. Pesan disinkronkan secara real time di seluruh jendela dan tab.
  4. Opsional: Pada menu Database di Firebase console, hapus, ubah, atau tambahkan pesan baru secara manual. Semua perubahan akan muncul di UI.

Selamat! Anda membaca dokumen Firestore di aplikasi.

Pratinjau aplikasi

Layar utama aplikasi di Android dengan integrasi chat

Layar utama aplikasi di iOS dengan integrasi chat

Layar utama aplikasi di web dengan integrasi chat

Layar utama aplikasi di macOS dengan integrasi chat

8. Menyiapkan aturan keamanan dasar

Anda awalnya menyiapkan Firestore untuk menggunakan mode pengujian, yang berarti bahwa database Anda terbuka untuk pembacaan dan penulisan. Namun, Anda hanya boleh menggunakan mode pengujian selama tahap awal pengembangan. Sebagai praktik terbaik, Anda harus menyiapkan aturan keamanan untuk database saat mengembangkan aplikasi. Keamanan adalah bagian integral dari struktur dan perilaku aplikasi.

Dengan Aturan Keamanan Firebase, Anda dapat mengontrol akses ke dokumen dan koleksi di database Anda. Sintaksis aturan yang fleksibel memungkinkan Anda membuat aturan yang cocok dengan apa pun, mulai dari semua penulisan hingga seluruh database hingga operasi pada dokumen tertentu.

Siapkan aturan keamanan dasar:

  1. Di menu Develop Firebase console, klik Database > Rules. Anda akan melihat aturan keamanan default berikut dan peringatan tentang aturan yang bersifat publik:

7767a2d2e64e7275.pngS

  1. Identifikasi koleksi tempat aplikasi menulis data:

Di match /databases/{database}/documents, identifikasi koleksi yang ingin Anda amankan:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
     // You'll add rules here in the next step.
  }
}

Karena Anda menggunakan UID Authentication sebagai kolom di setiap dokumen buku tamu, Anda bisa mendapatkan UID Authentication dan memverifikasi bahwa siapa pun yang mencoba menulis ke dokumen tersebut memiliki UID Authentication yang cocok.

  1. Tambahkan aturan baca dan tulis ke kumpulan aturan Anda:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
        if request.auth.uid == request.resource.data.userId;
    }
  }
}

Sekarang, hanya pengguna yang login yang dapat membaca pesan di buku tamu, tetapi hanya penulis pesan yang dapat mengedit pesan.

  1. Tambahkan validasi data untuk memastikan semua kolom yang diharapkan ada dalam dokumen:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
      if request.auth.uid == request.resource.data.userId
          && "name" in request.resource.data
          && "text" in request.resource.data
          && "timestamp" in request.resource.data;
    }
  }
}

9. Langkah bonus: Praktikkan apa yang telah Anda pelajari

Mencatat status RSVP peserta

Saat ini, aplikasi Anda hanya memungkinkan orang untuk melakukan chat jika mereka tertarik dengan acara tersebut. Selain itu, satu-satunya cara untuk mengetahui apakah seseorang akan datang adalah saat mereka mengatakannya dalam chat.

Pada langkah ini, Anda akan menjadi teratur dan memberitahukan jumlah orang yang akan datang. Anda menambahkan beberapa kemampuan ke status aplikasi. Yang pertama adalah kemampuan pengguna yang masuk untuk menentukan apakah mereka akan hadir. Yang kedua adalah penghitung berapa banyak orang yang hadir.

  1. Dalam file lib/app_state.dart, tambahkan baris berikut ke bagian pengakses ApplicationState sehingga kode UI dapat berinteraksi dengan status ini:

lib/app_state.dart

int _attendees = 0;
int get attendees => _attendees;

Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
  final userDoc = FirebaseFirestore.instance
      .collection('attendees')
      .doc(FirebaseAuth.instance.currentUser!.uid);
  if (attending == Attending.yes) {
    userDoc.set(<String, dynamic>{'attending': true});
  } else {
    userDoc.set(<String, dynamic>{'attending': false});
  }
}
  1. Perbarui metode init() ApplicationState sebagai berikut:

lib/app_state.dart

  Future<void> init() async {
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform);

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);

    // Add from here...
    FirebaseFirestore.instance
        .collection('attendees')
        .where('attending', isEqualTo: true)
        .snapshots()
        .listen((snapshot) {
      _attendees = snapshot.docs.length;
      notifyListeners();
    });
    // ...to here.

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
        _emailVerified = user.emailVerified;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // Add from here...
        _attendingSubscription = FirebaseFirestore.instance
            .collection('attendees')
            .doc(user.uid)
            .snapshots()
            .listen((snapshot) {
          if (snapshot.data() != null) {
            if (snapshot.data()!['attending'] as bool) {
              _attending = Attending.yes;
            } else {
              _attending = Attending.no;
            }
          } else {
            _attending = Attending.unknown;
          }
          notifyListeners();
        });
        // ...to here.
      } else {
        _loggedIn = false;
        _emailVerified = false;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        _attendingSubscription?.cancel(); // new
      }
      notifyListeners();
    });
  }

Kode ini menambahkan kueri yang selalu berlangganan untuk menentukan jumlah tamu dan kueri kedua yang hanya aktif saat pengguna masuk untuk menentukan apakah pengguna hadir.

  1. Tambahkan enumerasi berikut di bagian atas file lib/app_state.dart.

lib/app_state.dart

enum Attending { yes, no, unknown }
  1. Buat file baru yes_no_selection.dart, tentukan widget baru yang berfungsi seperti tombol pilihan:

lib/yes_no_selection.dart

import 'package:flutter/material.dart';

import 'app_state.dart';
import 'src/widgets.dart';

class YesNoSelection extends StatelessWidget {
  const YesNoSelection(
      {super.key, required this.state, required this.onSelection});
  final Attending state;
  final void Function(Attending selection) onSelection;

  @override
  Widget build(BuildContext context) {
    switch (state) {
      case Attending.yes:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              FilledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              TextButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      case Attending.no:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              TextButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              FilledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      default:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              StyledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              StyledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
    }
  }
}

Dimulai dalam status tidak tentu dengan tidak memilih Yes atau No. Setelah pengguna memilih apakah mereka akan hadir, Anda akan menampilkan opsi yang disorot dengan tombol terisi dan opsi lainnya menyusut dengan rendering datar.

  1. Perbarui metode build() HomePage untuk memanfaatkan YesNoSelection, aktifkan pengguna yang login untuk menentukan apakah dia akan hadir, dan menampilkan jumlah tamu acara:

lib/home_page.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // Add from here...
      switch (appState.attendees) {
        1 => const Paragraph('1 person going'),
        >= 2 => Paragraph('${appState.attendees} people going'),
        _ => const Paragraph('No one going'),
      },
      // ...to here.
      if (appState.loggedIn) ...[
        // Add from here...
        YesNoSelection(
          state: appState.attending,
          onSelection: (attending) => appState.attending = attending,
        ),
        // ...to here.
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages,
        ),
      ],
    ],
  ),
),

Tambahkan aturan

Anda sudah menyiapkan beberapa aturan, sehingga data yang Anda tambahkan dengan tombol akan ditolak. Anda perlu memperbarui aturan untuk mengizinkan penambahan ke koleksi attendees.

  1. Di koleksi attendees, ambil UID Autentikasi yang Anda gunakan sebagai nama dokumen dan verifikasi bahwa uid pengirim sama dengan dokumen yang mereka tulis:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

Hal ini memungkinkan semua orang membaca daftar peserta karena tidak ada data pribadi di sana, tetapi hanya pembuat yang dapat memperbaruinya.

  1. Tambahkan validasi data untuk memastikan semua kolom yang diharapkan ada dalam dokumen:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId
          && "attending" in request.resource.data;

    }
  }
}
  1. Opsional: Di aplikasi, klik tombol untuk melihat hasil di dasbor Firestore pada Firebase console.

Pratinjau aplikasi

Layar utama aplikasi di Android

Layar utama aplikasi di iOS

Layar utama aplikasi di web

Layar utama aplikasi di macOS

10. Selamat!

Anda telah menggunakan Firebase untuk membangun aplikasi web yang interaktif dan real-time!

Pelajari lebih lanjut