Kenali Firebase untuk Flutter

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

1. Sebelum Anda mulai

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

Prasyarat

Codelab ini mengasumsikan Anda sudah familiar dengan Flutter, dan Anda telah menginstal Flutter SDK , dan editor .

Apa yang akan Anda buat

Dalam codelab ini Anda akan membuat aplikasi obrolan RSVP dan buku tamu acara di Android, iOS, Web, dan macOS menggunakan Flutter. Anda akan mengautentikasi pengguna dengan Firebase Authentication dan menyinkronkan data menggunakan Cloud Firestore.

Apa yang Anda butuhkan?

Anda dapat menjalankan codelab ini menggunakan salah satu perangkat berikut:

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

Selain yang di atas, Anda juga membutuhkan:

  • Peramban pilihan Anda, seperti Chrome.
  • IDE atau editor teks pilihan Anda, seperti Android Studio atau VS Code yang dikonfigurasi dengan plugin Dart dan Flutter.
  • Flutter versi stable terbaru (atau beta jika Anda senang hidup di tepian).
  • Akun Google, seperti akun gmail, untuk membuat dan mengelola proyek Firebase Anda.
  • Alat baris perintah firebase , masuk ke akun gmail Anda.
  • Kode sampel codelab. Lihat langkah selanjutnya untuk cara mendapatkan kodenya.

2. Dapatkan kode sampel

Mari kita mulai dengan mengunduh versi awal proyek kita dari GitHub.

Kloning repositori GitHub dari baris perintah:

git clone https://github.com/flutter/codelabs.git flutter-codelabs

Atau, jika Anda menginstal alat cli GitHub :

gh repo clone flutter/codelabs flutter-codelabs

Kode sampel harus dikloning ke direktori flutter-codelabs , yang berisi kode untuk kumpulan codelabs. Kode untuk codelab ini ada di flutter-codelabs/firebase-get-to-know-flutter .

Struktur direktori di bawah flutter-codelabs/firebase-get-to-know-flutter adalah serangkaian snapshot di mana Anda seharusnya berada di akhir setiap langkah yang disebutkan. Ini adalah Langkah 2, jadi menemukan file yang cocok semudah:

cd flutter-codelabs/firebase-get-to-know-flutter/step_02

Jika Anda ingin melompat ke depan, atau melihat seperti apa tampilan setelah suatu langkah, lihat di direktori yang dinamai sesuai dengan langkah yang Anda minati.

Impor aplikasi pemula

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

Temukan file untuk dikerjakan

Kode dalam aplikasi ini tersebar di beberapa direktori. Pemisahan fungsi ini dirancang untuk mempermudah pengerjaan, dengan mengelompokkan kode berdasarkan fungsionalitas.

Temukan file berikut dalam proyek:

  • lib/main.dart : File ini berisi titik masuk utama dan widget aplikasi.
  • lib/src/widgets.dart : File ini berisi beberapa widget untuk membantu menstandardisasi gaya aplikasi. Ini digunakan untuk membuat layar aplikasi starter.
  • lib/src/authentication.dart : File ini berisi implementasi sebagian dari FirebaseUI Auth dengan seperangkat widget untuk membuat pengalaman pengguna login untuk otentikasi berbasis email Firebase. Widget untuk alur autentikasi ini belum digunakan di aplikasi pemula, tetapi Anda akan segera menghubungkannya.

Anda akan menambahkan file tambahan yang diperlukan untuk membangun sisa aplikasi.

Meninjau file lib/main.dart

Aplikasi ini memanfaatkan paket google_fonts untuk memungkinkan kami menjadikan Roboto sebagai font default di seluruh aplikasi. Latihan untuk pembaca yang termotivasi adalah menjelajahi fonts.google.com dan menggunakan font yang Anda temukan di sana di berbagai bagian aplikasi.

Anda menggunakan widget pembantu dari lib/src/widgets.dart dalam bentuk Header , Paragraph dan IconAndDetail . Widget ini mengurangi kekacauan dalam tata letak halaman yang dijelaskan di HomePage dengan menghilangkan kode duplikat. Ini memiliki manfaat tambahan untuk memungkinkan tampilan dan nuansa yang konsisten.

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

Pratinjau aplikasi

3. Buat dan siapkan proyek Firebase

Menampilkan informasi acara sangat bagus untuk tamu Anda, tetapi hanya menampilkan acara tidak terlalu berguna bagi siapa pun. Mari tambahkan beberapa fungsi dinamis ke aplikasi ini. Untuk ini, Anda harus menghubungkan Firebase ke aplikasi Anda. Untuk memulai Firebase, Anda harus membuat dan menyiapkan proyek Firebase.

Buat proyek Firebase

  1. Masuk ke Firebase .
  2. Di Firebase console, klik Tambahkan Proyek (atau Buat proyek ), dan beri nama proyek Firebase Anda dengan Firebase-Flutter-Codelab .

4395e4e67c08043a.png

  1. Klik melalui opsi pembuatan proyek. Terima persyaratan Firebase jika diminta. Lewati penyiapan Google Analytics, karena Anda tidak akan menggunakan Analytics untuk aplikasi ini.

b7138cde5f2c7b61.png

Untuk mempelajari lebih lanjut tentang proyek Firebase, lihat Memahami proyek Firebase .

Aplikasi yang Anda buat menggunakan beberapa produk Firebase yang tersedia untuk aplikasi web:

  • Firebase Authentication untuk mengizinkan pengguna Anda masuk ke aplikasi Anda.
  • Cloud Firestore untuk menyimpan data terstruktur di cloud dan mendapatkan notifikasi instan saat data berubah.
  • Aturan Keamanan Firebase untuk mengamankan database Anda.

Beberapa produk ini memerlukan konfigurasi khusus atau perlu diaktifkan menggunakan Firebase console.

Aktifkan login email untuk Firebase Authentication

Untuk mengizinkan pengguna masuk ke aplikasi web, Anda akan menggunakan metode masuk Email/Sandi untuk codelab ini:

  1. Di Firebase console, perluas menu Build di panel kiri.
  2. Klik Otentikasi , lalu klik tombol Mulai , lalu tab Metode masuk (atau klik di sini untuk langsung membuka tab Metode masuk ).
  3. Klik Email/Kata Sandi dalam daftar Penyedia masuk, atur sakelar Aktifkan ke posisi aktif, lalu klik Simpan . 58e3e3e23c2f16a4.png

Aktifkan Cloud Firestore

Aplikasi web menggunakan Cloud Firestore untuk menyimpan pesan obrolan dan menerima pesan obrolan baru.

Aktifkan Cloud Firestore:

  1. Di bagian Build Firebase console, klik Cloud Firestore .
  2. Klik Buat basis data . 99e8429832d23fa3.png
  1. Pilih opsi Mulai dalam mode uji . Baca penafian tentang aturan keamanan. Mode uji memastikan bahwa Anda dapat dengan bebas menulis ke database selama pengembangan. Klik Berikutnya . 6be00e26c72ea032.png
  1. Pilih lokasi untuk database Anda (Anda bisa menggunakan default). Perhatikan bahwa lokasi ini tidak dapat diubah nanti. 278656eefcfb0216.png
  2. Klik Aktifkan .

4. Konfigurasi Firebase

Untuk menggunakan Firebase dengan Flutter, Anda harus mengikuti proses untuk mengonfigurasi proyek Flutter agar dapat menggunakan library FlutterFire dengan benar:

  • Tambahkan dependensi FlutterFire ke proyek Anda
  • Daftarkan platform yang diinginkan pada proyek Firebase
  • Unduh file konfigurasi khusus platform, dan tambahkan ke kode.

Di direktori tingkat atas aplikasi Flutter Anda, ada subdirektori bernama android , ios , macos dan web . Direktori ini masing-masing menyimpan file konfigurasi khusus platform untuk iOS dan Android.

Konfigurasikan dependensi

Anda perlu menambahkan pustaka FlutterFire untuk dua produk Firebase yang Anda gunakan di aplikasi ini - Firebase Auth dan Cloud Firestore. Jalankan tiga perintah berikut untuk menambahkan dependensi.

$ flutter pub add firebase_core

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

$ flutter pub add firebase_auth

firebase_auth memungkinkan integrasi dengan kemampuan Otentikasi Firebase.

$ flutter pub add cloud_firestore

cloud_firestore memungkinkan akses ke penyimpanan data Cloud Firestore.

$ flutter pub add provider

Paket firebase_ui_auth menyediakan seperangkat widget dan utilitas khusus untuk meningkatkan kecepatan pengembang dengan alur autentikasi.

$ flutter pub add firebase_ui_auth

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

Memasang flutterfire

FlutterFire CLI bergantung pada Firebase CLI yang mendasarinya. Jika Anda belum melakukannya, pastikan Firebase CLI diinstal pada mesin Anda.

Selanjutnya, instal FlutterFire CLI dengan menjalankan perintah berikut:

$ dart pub global activate flutterfire_cli

Setelah diinstal, perintah flutterfire akan tersedia secara global.

Mengonfigurasi aplikasi Anda

CLI mengekstrak informasi dari proyek Firebase Anda dan aplikasi proyek yang dipilih untuk menghasilkan semua konfigurasi untuk platform tertentu.

Di root aplikasi Anda, jalankan perintah configure:

$ flutterfire configure

Perintah konfigurasi akan memandu Anda melalui sejumlah proses:

  1. Memilih proyek Firebase (berdasarkan file .firebaserc atau dari Firebase Console).
  2. Tanyakan platform apa (misalnya Android, iOS, macOS & web) yang ingin Anda konfigurasi.
  3. Identifikasi aplikasi Firebase untuk platform yang dipilih yang harus digunakan untuk mengekstrak konfigurasi. Secara default, CLI akan mencoba mencocokkan aplikasi Firebase secara otomatis berdasarkan konfigurasi proyek Anda saat ini.
  4. Buat file firebase_options.dart di proyek Anda.

Konfigurasikan macOS

Flutter di macOS membangun aplikasi yang sepenuhnya terkotak pasir. Karena aplikasi ini terintegrasi menggunakan jaringan untuk berkomunikasi dengan server Firebase, Anda perlu mengonfigurasi aplikasi Anda 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.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/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

Lihat Kepemilikan dan Kotak Pasir Aplikasi untuk detail selengkapnya.

5. Tambahkan login pengguna (RSVP)

Sekarang setelah Anda menambahkan Firebase ke aplikasi, Anda dapat menyiapkan tombol RSVP yang mendaftarkan orang menggunakan Firebase Authentication . Untuk bawaan Android, bawaan iOS, dan Web ada paket FirebaseUI Auth yang sudah dibuat sebelumnya, tetapi untuk Flutter Anda perlu membangun kemampuan ini.

Proyek yang Anda ambil di Langkah 2 menyertakan satu set widget yang mengimplementasikan antarmuka pengguna untuk sebagian besar alur autentikasi. Anda akan menerapkan logika bisnis untuk mengintegrasikan Firebase Authentication ke dalam aplikasi.

Logika Bisnis dengan Penyedia

Anda akan menggunakan paket provider untuk membuat objek status aplikasi terpusat tersedia di seluruh pohon aplikasi widget Flutter. Untuk memulainya, ubah impor di bagian atas lib/main.dart :

lib/main.dart

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

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

Baris import memperkenalkan Firebase Core dan Auth, menarik paket provider yang Anda gunakan untuk membuat objek status aplikasi tersedia melalui pohon widget, dan menyertakan widget autentikasi dari firebase_ui_auth .

Objek status aplikasi ini, ApplicationState , memiliki satu tanggung jawab utama untuk langkah ini yaitu memperingatkan pohon widget bahwa ada pembaruan ke status terotentikasi. Tambahkan kelas berikut ke akhir lib/main.dart :

lib/main.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();
    });
  }
}

Kami menggunakan penyedia di sini untuk berkomunikasi dengan aplikasi tentang status login pengguna, tidak lebih. Untuk login pengguna, kita akan menggunakan UI yang disediakan oleh firebase_ui_auth yang merupakan cara yang bagus untuk mem-bootstrap layar login untuk aplikasi Anda dengan cepat.

Mengintegrasikan alur Otentikasi

Sekarang setelah Anda memulai status aplikasi, sekarang saatnya untuk menghubungkan status aplikasi ke inisialisasi aplikasi dan menambahkan aliran otentikasi ke HomePage . Perbarui titik masuk utama untuk mengintegrasikan status aplikasi melalui paket provider :

lib/main.dart

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

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

Modifikasi fungsi main membuat paket penyedia bertanggung jawab untuk membuat instance objek status aplikasi menggunakan widget ChangeNotifierProvider . Anda menggunakan kelas penyedia khusus ini karena objek status aplikasi memperluas ChangeNotifier dan ini memungkinkan paket provider mengetahui kapan harus menampilkan ulang widget yang bergantung.

Karena kami menggunakan FirebaseUI untuk Flutter, kami akan memperbarui aplikasi kami untuk menangani navigasi ke berbagai layar yang disediakan FirebaseUI untuk kami. Untuk melakukan ini, kami menambahkan properti initialRoute dan menambahkan layar pilihan kami yang dapat kami rutekan di bawah properti routes . Perubahannya akan terlihat seperti ini:

lib/main.dart

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //Start adding here
      initialRoute: '/home',
      routes: {
        '/home': (context) {
          return const HomePage();
        },
        '/sign-in': ((context) {
          return SignInScreen(
            actions: [
              ForgotPasswordAction(((context, email) {
                Navigator.of(context)
                    .pushNamed('/forgot-password', arguments: {'email': email});
              })),
              AuthStateChangeAction(((context, state) {
                if (state is SignedIn || state is UserCreated) {
                  var user = (state is SignedIn)
                      ? state.user
                      : (state as UserCreated).credential.user;
                  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);
                  }
                  Navigator.of(context).pushReplacementNamed('/home');
                }
              })),
            ],
          );
        }),
        '/forgot-password': ((context) {
          final arguments = ModalRoute.of(context)?.settings.arguments
              as Map<String, dynamic>?;

          return ForgotPasswordScreen(
            email: arguments?['email'] as String,
            headerMaxExtent: 200,
          );
        }),
        '/profile': ((context) {
          return ProfileScreen(
            providers: [],
            actions: [
              SignedOutAction(
                ((context) {
                  Navigator.of(context).pushReplacementNamed('/home');
                }),
              ),
            ],
          );
        })
      },
      // end adding here
      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,
      ),
    );
  }
}

Setiap layar memiliki jenis tindakan berbeda yang terkait dengannya berdasarkan status baru aliran otentikasi. Setelah sebagian besar perubahan status dalam autentikasi, kami dapat merutekan ulang kembali ke layar pilihan, apakah itu layar beranda atau layar lain seperti profil. Terakhir, integrasikan status aplikasi dengan AuthFunc dengan memperbarui metode pembuatan HomePage :

lib/main.dart

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

  @override
  Widget build(BuildContext context) {
    return Consumer<ApplicationState>(
        builder: (context, appState, child) => 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 membungkusnya dalam widget Consumer . Widget Konsumen cara yang biasa digunakan paket provider untuk membangun kembali bagian dari pohon ketika status aplikasi berubah. Widget AuthFunc adalah widget tambahan yang sekarang akan Anda uji.

Menguji alur Otentikasi

cdf2d25e436bd48d.png

Inilah awal dari alur otentikasi, di mana pengguna dapat mengetuk tombol RSVP, untuk memulai SignInScreen .

2a2cd6d69d172369.png

Setelah memasukkan email, sistem mengkonfirmasi apakah pengguna sudah terdaftar, dalam hal ini pengguna dimintai kata sandi, atau jika pengguna tidak terdaftar, maka mereka pergi melalui formulir pendaftaran.

e5e65065dba36b54.png

Pastikan untuk mencoba memasukkan kata sandi pendek (kurang dari enam karakter) untuk memeriksa alur penanganan kesalahan. Jika pengguna terdaftar, mereka akan melihat kata sandi untuknya.

Pada halaman ini pastikan untuk memasukkan kata sandi yang salah untuk memeriksa penanganan kesalahan pada halaman ini. Terakhir, setelah pengguna masuk, Anda akan melihat pengalaman masuk yang menawarkan pengguna kemampuan untuk keluar lagi.

4ed811a25b0cf816.png

Dan dengan itu, Anda telah menerapkan alur otentikasi. Selamat!

6. Tulis pesan ke Cloud Firestore

Mengetahui bahwa pengguna akan datang adalah hal yang bagus, tetapi mari berikan tamu hal lain untuk dilakukan di aplikasi. Bagaimana jika mereka bisa meninggalkan pesan di buku tamu? Mereka dapat berbagi mengapa mereka bersemangat untuk datang atau siapa yang ingin mereka temui.

Untuk menyimpan pesan obrolan yang ditulis pengguna di aplikasi, Anda akan menggunakan Cloud Firestore .

Model data

Cloud Firestore adalah database NoSQL, dan data yang disimpan dalam database dibagi menjadi koleksi, dokumen, kolom, dan subkoleksi. Anda akan menyimpan setiap pesan obrolan sebagai dokumen dalam koleksi tingkat atas yang disebut guestbook .

7c20dc8424bb1d84.png

Tambahkan pesan ke Firestore

Di bagian ini, Anda akan menambahkan fungsionalitas bagi pengguna untuk menulis pesan baru ke database. Pertama, Anda menambahkan elemen UI (bidang formulir dan tombol kirim), lalu Anda menambahkan kode yang mengaitkan elemen-elemen ini ke database.

Pertama, tambahkan impor untuk paket cloud_firestore dan dart:async .

lib/main.dart

import 'dart:async';                                    // new

import 'package:cloud_firestore/cloud_firestore.dart';  // new
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';

import 'firebase_options.dart';
import 'src/authentication.dart';
import 'src/widgets.dart';

Untuk membuat elemen UI bidang pesan dan tombol kirim, tambahkan widget stateful GuestBook baru di bagian bawah lib/main.dart .

lib/main.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 tempat menarik di sini. Pertama, Anda membuat instance Formulir sehingga Anda dapat memvalidasi pesan yang sebenarnya memiliki beberapa konten, dan menunjukkan kepada pengguna pesan kesalahan jika tidak ada. Cara untuk memvalidasi formulir melibatkan mengakses status formulir di belakang formulir, dan untuk ini Anda menggunakan GlobalKey . Untuk informasi lebih lanjut tentang Tombol, dan cara menggunakannya, silakan lihat Flutter Widgets 101 episode "Kapan Menggunakan Tombol" .

Perhatikan juga cara widget ditata, Anda memiliki Row , dengan TextFormField dan StyledButton , yang berisi Row . Perhatikan juga TextFormField dibungkus dalam widget yang Expanded , ini memaksa TextFormField untuk mengambil ruang ekstra di baris. Untuk lebih memahami mengapa ini diperlukan, silakan baca Memahami kendala .

Sekarang setelah Anda memiliki widget yang memungkinkan pengguna memasukkan beberapa teks untuk ditambahkan ke Buku Tamu, Anda harus menampilkannya di layar. Untuk melakukannya, edit isi HomePage untuk menambahkan dua baris berikut di bagian bawah anak-anak 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 ini cukup untuk menampilkan Widget, itu tidak cukup untuk melakukan sesuatu yang berguna. Anda akan segera memperbarui kode ini agar berfungsi.

Pratinjau aplikasi

Pengguna yang mengeklik tombol KIRIM akan memicu cuplikan kode di bawah ini. Itu menambahkan isi bidang input pesan ke koleksi guestbook dari database. Secara khusus, metode addMessageToGuestBook menambahkan konten pesan ke dokumen baru (dengan ID yang dibuat secara otomatis) ke koleksi guestbook .

Perhatikan bahwa FirebaseAuth.instance.currentUser.uid adalah referensi ke ID unik yang dibuat secara otomatis yang diberikan oleh Firebase Authentication untuk semua pengguna yang masuk.

Buat perubahan lain pada file lib/main.dart . Tambahkan metode addMessageToGuestBook . Anda akan menghubungkan antarmuka pengguna dan kemampuan ini bersama-sama di langkah berikutnya.

lib/main.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 ke database

Anda memiliki UI tempat pengguna dapat memasukkan teks yang ingin mereka tambahkan ke Buku Tamu, dan Anda memiliki kode untuk menambahkan entri ke Cloud Firestore. Sekarang yang perlu Anda lakukan adalah menyambungkan keduanya. Di lib/main.dart buat perubahan berikut pada widget HomePage .

lib/main.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 telah mengganti dua baris yang Anda tambahkan kembali di awal langkah ini dengan implementasi penuh. Anda kembali menggunakan Consumer<ApplicationState> untuk membuat status aplikasi tersedia untuk bagian pohon yang Anda render. Ini memungkinkan Anda untuk bereaksi terhadap seseorang yang memasukkan pesan di UI, dan memublikasikannya ke dalam database. Di bagian berikutnya Anda akan menguji apakah pesan yang ditambahkan dipublikasikan ke dalam database.

Uji pengiriman pesan

  1. Pastikan Anda masuk ke aplikasi.
  2. Masukkan pesan seperti "Hai!", lalu klik KIRIM .

Tindakan ini menulis pesan ke database Cloud Firestore Anda. Namun, Anda belum akan melihat pesan tersebut di aplikasi Flutter Anda yang sebenarnya karena Anda masih perlu menerapkan pengambilan data. Anda akan melakukannya di langkah berikutnya.

Tetapi Anda dapat melihat pesan yang baru ditambahkan di konsol Firebase.

Di konsol Firebase, di dasbor Database , Anda akan melihat koleksi guestbook dengan pesan yang baru ditambahkan. Jika Anda terus mengirim pesan, koleksi buku tamu Anda akan berisi banyak dokumen, seperti ini:

Konsol Firebase

713870af0b3b63c.png

7. Baca pesan

Sangat menyenangkan bahwa para tamu dapat menulis pesan ke database, tetapi mereka belum dapat melihatnya di aplikasi. Mari kita perbaiki itu!

Sinkronkan pesan

Untuk menampilkan pesan, Anda harus menambahkan listener yang memicu saat data berubah, lalu membuat elemen UI yang menampilkan pesan baru. Anda akan menambahkan kode ke status aplikasi yang mendengarkan pesan yang baru ditambahkan dari aplikasi.

Tepat di atas widget GuestBook , kelas nilai berikut. Kelas ini menampilkan tampilan terstruktur dari data yang Anda simpan di Cloud Firestore.

lib/main.dart

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

Di bagian ApplicationState tempat Anda menentukan status dan getter, tambahkan baris baru berikut:

lib/main.dart

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

  // Add from here
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // to here.

Dan akhirnya, di bagian inisialisasi ApplicationState , tambahkan yang berikut ini untuk berlangganan kueri atas kumpulan dokumen saat pengguna masuk, dan berhenti berlangganan saat mereka keluar.

lib/main.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 atas koleksi guestbook , dan menangani berlangganan dan berhenti berlangganan koleksi ini. Anda mendengarkan aliran, tempat Anda merekonstruksi cache lokal dari pesan dalam koleksi guestbook , dan juga menyimpan referensi ke langganan ini sehingga Anda dapat berhenti berlangganan nanti. Ada banyak hal yang terjadi di sini, dan ada baiknya menghabiskan waktu di debugger untuk memeriksa apa yang terjadi ketika mendapatkan model mental yang lebih jelas.

Untuk informasi selengkapnya, lihat dokumentasi Cloud Firestore .

Di widget GuestBook Anda perlu menghubungkan status perubahan ini ke antarmuka pengguna. Anda memodifikasi widget dengan menambahkan daftar pesan sebagai bagian dari konfigurasinya.

lib/main.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();
}

Selanjutnya, kami mengekspos konfigurasi baru ini di _GuestBookState dengan memodifikasi metode build sebagai berikut.

lib/main.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.
    );
  }
}

Anda membungkus konten sebelumnya dari metode build dengan widget Column , lalu di ekor anak-anak Column Anda menambahkan koleksi untuk menghasilkan Paragraph baru untuk setiap pesan dalam daftar pesan.

Terakhir, Anda sekarang perlu memperbarui isi HomePage untuk menyusun GuestBook dengan benar dengan parameter messages baru.

lib/main.dart

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

Uji sinkronisasi pesan

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

  1. Pesan yang Anda buat sebelumnya di database harus ditampilkan di aplikasi. Jangan ragu untuk menulis pesan baru; mereka harus muncul secara instan.
  2. Jika Anda membuka ruang kerja di beberapa jendela atau tab, pesan akan disinkronkan secara real time di seluruh tab.
  3. (Opsional) Anda dapat mencoba menghapus, memodifikasi, atau menambahkan pesan baru secara manual langsung di bagian Database di Firebase console; perubahan apa pun akan muncul di UI.

Selamat! Anda sedang membaca dokumen Cloud Firestore di aplikasi Anda!

Ulasan aplikasi

8. Siapkan aturan keamanan dasar

Anda awalnya menyiapkan Cloud Firestore untuk menggunakan mode pengujian, artinya database Anda terbuka untuk membaca dan menulis. Namun, Anda hanya boleh menggunakan mode uji selama tahap pengembangan yang sangat awal. Sebagai praktik terbaik, Anda harus menyiapkan aturan keamanan untuk database saat mengembangkan aplikasi. Keamanan harus menjadi bagian integral dari struktur dan perilaku aplikasi Anda.

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

Anda dapat menulis aturan keamanan untuk Cloud Firestore di Firebase console:

  1. Di bagian Kembangkan konsol Firebase, klik Database , lalu pilih tab Aturan (atau klik di sini untuk langsung membuka tab Aturan ).
  2. Anda akan melihat aturan keamanan default berikut, bersama dengan peringatan tentang aturan yang bersifat publik.

7767a2d2e64e7275.png

Identifikasi koleksi

Pertama, identifikasi koleksi tempat aplikasi menulis data.

Dalam 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.
  }
}

Tambahkan aturan keamanan

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

Tambahkan aturan baca dan tulis ke set aturan Anda seperti yang ditunjukkan di bawah ini:

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, untuk buku tamu, hanya pengguna yang masuk yang dapat membaca pesan (pesan apa pun!), tetapi hanya pembuat pesan yang dapat mengedit pesan.

Tambahkan aturan validasi

Tambahkan validasi data untuk memastikan bahwa semua bidang 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: Latih apa yang telah Anda pelajari

Catat status RSVP peserta

Saat ini, aplikasi Anda hanya memungkinkan orang untuk mulai mengobrol jika mereka tertarik dengan acara tersebut. Juga, satu-satunya cara Anda tahu jika seseorang datang adalah jika mereka mempostingnya di obrolan. Mari kita mengatur dan memberi tahu orang-orang berapa banyak orang yang datang.

Anda akan menambahkan beberapa kemampuan baru ke status aplikasi. Yang pertama adalah kemampuan bagi pengguna yang masuk untuk mencalonkan apakah mereka hadir atau tidak. Kemampuan kedua adalah penghitung berapa banyak orang yang benar-benar hadir.

Di lib/main.dart , tambahkan berikut ini ke bagian pengakses untuk mengaktifkan kode UI untuk berinteraksi dengan status ini:

lib/main.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});
  }
}

Perbarui metode init ApplicationState sebagai berikut:

lib/main.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) {
        _loginState = ApplicationLoginState.loggedIn;
        _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 {
        _loginState = ApplicationLoginState.loggedOut;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        _attendingSubscription?.cancel(); // new
      }
      notifyListeners();
    });
  }

Di atas menambahkan kueri selalu berlangganan untuk mengetahui jumlah peserta, dan kueri kedua yang hanya aktif saat pengguna masuk untuk mengetahui apakah pengguna hadir. Selanjutnya, tambahkan enumerasi berikut setelah deklarasi GuestBookMessage :

lib/main.dart

enum Attending { yes, no, unknown }

Anda sekarang akan menentukan widget baru yang berfungsi seperti tombol radio lama. Ini dimulai dalam keadaan tak tentu, dengan tidak ya atau tidak dipilih, tetapi setelah pengguna memilih apakah mereka hadir atau tidak, maka Anda menunjukkan opsi yang disorot dengan tombol yang diisi, dan opsi lainnya surut dengan rendering datar.

lib/main.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: [
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                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),
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                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'),
              ),
            ],
          ),
        );
    }
  }
}

Selanjutnya, Anda perlu memperbarui metode pembuatan HomePage untuk memanfaatkan YesNoSelection , memungkinkan pengguna yang masuk untuk mencalonkan jika mereka hadir. Anda juga akan menampilkan jumlah peserta untuk acara ini.

lib/main.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // Add from here
      if (appState.attendees >= 2)
        Paragraph('${appState.attendees} people going')
      else if (appState.attendees == 1)
        const Paragraph('1 person going')
      else
        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

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

Untuk koleksi attendees , karena Anda menggunakan UID Otentikasi sebagai nama dokumen, Anda dapat mengambilnya dan memverifikasi bahwa uid pengirim sama dengan dokumen yang mereka tulis. Anda akan mengizinkan semua orang membaca daftar peserta (karena tidak ada data pribadi di sana), tetapi hanya pembuatnya yang dapat memperbaruinya.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

Tambahkan aturan validasi

Tambahkan validasi data untuk memastikan bahwa semua bidang 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;

    }
  }
}

(Opsional) Anda sekarang dapat melihat hasil mengklik tombol. Buka dasbor Cloud Firestore Anda di Firebase console.

Pratinjau aplikasi

10. Selamat!

Anda telah menggunakan Firebase untuk membuat aplikasi web waktu nyata yang interaktif!

Apa yang telah kita bahas

  • Otentikasi Firebase
  • Cloud Firestore
  • Aturan Keamanan Firebase

Langkah selanjutnya

  • Ingin mempelajari lebih lanjut tentang produk Firebase lainnya? Mungkin Anda ingin menyimpan file gambar yang diunggah pengguna? Atau mengirim pemberitahuan ke pengguna Anda? Lihat dokumentasi Firebase . Ingin mempelajari lebih lanjut tentang plugin Flutter untuk Firebase? Lihat FlutterFire untuk informasi lebih lanjut.
  • Ingin mempelajari lebih lanjut tentang Cloud Firestore? Mungkin Anda ingin belajar tentang subkoleksi dan transaksi? Buka codelab web Cloud Firestore untuk codelab yang membahas lebih dalam tentang Cloud Firestore. Atau lihat serial YouTube ini untuk mengenal Cloud Firestore !

Belajarlah lagi

Bagaimana hasilnya?

Kami akan menyukai tanggapan Anda! Silakan isi formulir (sangat) singkat di sini .