1. Sebelum memulai
Dalam codelab ini, Anda akan mempelajari beberapa dasar-dasar Firebase untuk membuat aplikasi seluler Flutter untuk Android dan iOS.
Prasyarat
- Pemahaman tentang Flutter
- Flutter SDK
- Editor teks pilihan Anda
Yang akan Anda pelajari
- Cara mem-build aplikasi RSVP acara dan chat buku tamu di Android, iOS, Web, dan macOS dengan Flutter.
- Cara mengautentikasi pengguna dengan Firebase Authentication dan menyinkronkan data dengan Firestore.
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).
- Android Emulator (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.
- Flutter versi
stable
terbaru ataubeta
jika Anda suka menggunakan versi terbaru. - Akun Google untuk pembuatan dan pengelolaan project Firebase Anda.
Firebase
CLI login ke Akun Google Anda.
2. Mendapatkan kode contoh
Download project versi awal Anda dari GitHub:
- 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 ini berisi serangkaian snapshot yang menunjukkan tampilan project Anda di akhir setiap langkah. Misalnya, Anda berada di langkah kedua.
- Temukan file yang cocok untuk langkah kedua:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
Jika Anda ingin melanjutkan atau melihat tampilan sesuatu setelah suatu langkah, lihat di direktori yang diberi nama sesuai 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 meetup Flutter yang belum berfungsi.
Menemukan file yang perlu dikerjakan
Kode dalam aplikasi ini tersebar di beberapa direktori. Pemisahan fungsi ini mempermudah pekerjaan karena mengelompokkan kode berdasarkan fungsi.
- 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. File ini menyusun layar aplikasi awal.lib/src/authentication.dart
: File ini berisi implementasi sebagian Authentication dengan serangkaian widget untuk membuat pengalaman pengguna login untuk autentikasi berbasis email Firebase. Widget ini untuk alur autentikasi belum digunakan di aplikasi awal, tetapi Anda akan segera menambahkannya.
Anda menambahkan file tambahan sesuai kebutuhan untuk mem-build sisa aplikasi.
Meninjau 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 sana 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 adalah tampilan aplikasi Anda di Android, iOS, Web, dan macOS:
3. Membuat dan mengonfigurasi project Firebase
Tampilan informasi peristiwa sangat bagus untuk tamu Anda, tetapi tidak terlalu berguna untuk siapa pun. Anda perlu menambahkan beberapa fungsi dinamis ke aplikasi. Untuk melakukannya, Anda perlu menghubungkan Firebase ke aplikasi. Untuk memulai Firebase, Anda perlu membuat dan mengonfigurasi project Firebase.
Membuat project Firebase
- Login ke Firebase.
- Di konsol, klik Tambahkan Project atau Buat project.
- Di kolom Project name, masukkan Firebase-Flutter-Codelab, lalu klik Continue.
- Klik opsi pembuatan project. Jika diminta, setujui persyaratan Firebase, tetapi lewati penyiapan Google Analytics karena Anda tidak akan menggunakannya untuk aplikasi ini.
Untuk mempelajari project Firebase lebih lanjut, lihat Memahami project Firebase.
Aplikasi menggunakan produk Firebase berikut, yang tersedia untuk aplikasi web:
- Autentikasi: 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.
Mengaktifkan autentikasi login dengan email
- Di panel Project overview Firebase console, luaskan menu Build.
- Klik Authentication > Get Started > Sign-in method > Email/Password > Enable > Save.
Menyiapkan Firestore
Aplikasi web menggunakan Firestore untuk menyimpan pesan chat dan menerima pesan chat baru.
Berikut cara menyiapkan Firestore di project Firebase Anda:
- Di panel kiri Firebase console, luaskan Build, lalu pilih Firestore database.
- Klik Create database.
- Biarkan Database ID ditetapkan ke
(default)
. - Pilih lokasi untuk database Anda, lalu klik Berikutnya.
Untuk aplikasi yang sebenarnya, Anda harus memilih lokasi yang dekat dengan pengguna. - Klik Mulai dalam mode pengujian. Baca pernyataan penyangkalan tentang aturan keamanan.
Di codelab ini, Anda akan menambahkan Aturan Keamanan untuk mengamankan data. Jangan mendistribusikan atau mengekspos aplikasi ke publik tanpa menambahkan Aturan Keamanan untuk database Anda. - Klik Buat.
4. Mengonfigurasi Firebase
Untuk menggunakan Firebase dengan Flutter, Anda harus menyelesaikan tugas-tugas berikut untuk mengonfigurasi project Flutter agar menggunakan library FlutterFire
dengan benar:
- Tambahkan dependensi
FlutterFire
ke project Anda. - Daftarkan platform yang diinginkan di project Firebase.
- Download file konfigurasi khusus platform, lalu tambahkan file ke kode.
Di direktori level teratas aplikasi Flutter, 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 di 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 serangkaian 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 runner iOS, Android, macOS, dan Web untuk menggunakan Firebase dengan tepat. Anda juga menggunakan paket provider
yang memungkinkan pemisahan logika bisnis dari logika tampilan.
Menginstal FlutterFire CLI
FlutterFire CLI bergantung pada Firebase CLI yang mendasarinya.
- Jika Anda belum melakukannya, instal Firebase CLI di komputer Anda.
- Instal FlutterFire CLI:
$ dart pub global activate flutterfire_cli
Setelah diinstal, perintah flutterfire
akan tersedia secara global.
Mengonfigurasi aplikasi
CLI mengekstrak informasi dari project Firebase dan aplikasi project yang dipilih untuk membuat semua konfigurasi untuk platform tertentu.
Di root aplikasi Anda, jalankan perintah configure
:
$ flutterfire configure
Perintah konfigurasi akan memandu Anda melalui proses berikut:
- Pilih project Firebase berdasarkan file
.firebaserc
atau dari Firebase Console. - Tentukan platform untuk konfigurasi, seperti Android, iOS, macOS, dan web.
- Identifikasi aplikasi Firebase yang akan diekstrak konfigurasinya. Secara default, CLI mencoba mencocokkan aplikasi Firebase secara otomatis berdasarkan konfigurasi project Anda saat ini.
- Buat file
firebase_options.dart
di project Anda.
Mengonfigurasi macOS
Flutter di macOS mem-build aplikasi dengan sandbox penuh. 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.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>
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 Autentikasi. Untuk native Android, native iOS, dan Web, ada paket FirebaseUI Auth
bawaan, tetapi Anda perlu mem-build kemampuan ini untuk Flutter.
Project yang Anda ambil sebelumnya menyertakan serangkaian widget yang mengimplementasikan antarmuka pengguna untuk sebagian besar alur autentikasi. Anda menerapkan logika bisnis untuk mengintegrasikan Autentikasi dengan aplikasi.
Menambahkan logika bisnis dengan paket Provider
Gunakan paket provider
untuk membuat objek status aplikasi terpusat tersedia di seluruh hierarki widget Flutter aplikasi:
- 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, mengambil 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 yang diautentikasi.
Anda hanya menggunakan penyedia untuk menyampaikan status login pengguna ke aplikasi. Untuk mengizinkan pengguna login, Anda menggunakan UI yang disediakan oleh paket firebase_ui_auth
, yang merupakan cara yang bagus untuk mem-bootstrap layar login dengan cepat di aplikasi Anda.
Mengintegrasikan alur autentikasi
- 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';
- 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
tertentu ini karena objek status aplikasi memperluas class ChangeNotifier
, yang memungkinkan paket provider
mengetahui kapan harus menampilkan kembali widget dependen.
- Perbarui 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 yang berbeda yang terkait dengannya berdasarkan status baru alur autentikasi. Setelah sebagian besar perubahan status dalam autentikasi, Anda dapat mengalihkan kembali ke layar yang diinginkan, baik layar utama maupun layar lain, seperti profil.
- Dalam metode build class
HomePage
, integrasikan status aplikasi dengan widgetAuthFunc
:
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 paket provider
dapat digunakan untuk mem-build ulang bagian hierarki saat status aplikasi berubah. Widget AuthFunc
adalah widget tambahan yang Anda uji.
Menguji alur autentikasi
- Di aplikasi, ketuk tombol RSVP untuk memulai
SignInScreen
.
- Masukkan alamat email. Jika Anda sudah terdaftar, sistem akan meminta Anda memasukkan sandi. Jika tidak, sistem akan meminta Anda untuk melengkapi formulir pendaftaran.
- Masukkan sandi yang kurang dari enam karakter untuk memeriksa alur penanganan error. Jika terdaftar, Anda akan melihat sandi untuk akun tersebut.
- Masukkan sandi yang salah untuk memeriksa alur penanganan error.
- Masukkan sandi yang benar. Anda akan melihat pengalaman login, yang menawarkan kemampuan logout kepada pengguna.
6. Menulis pesan ke Firestore
Sangat menyenangkan 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 membagikan alasan mereka antusias untuk datang atau siapa yang mereka harap dapat mereka temui.
Untuk menyimpan pesan chat yang ditulis pengguna di aplikasi, Anda menggunakan Firestore.
Model data
Firestore adalah database NoSQL, dan data yang disimpan dalam database dibagi menjadi koleksi, dokumen, kolom, dan subkoleksi. Anda menyimpan setiap pesan chat sebagai dokumen dalam koleksi guestbook
, yang merupakan koleksi tingkat teratas.
Menambahkan pesan ke Firestore
Di bagian ini, Anda akan menambahkan fungsi bagi pengguna untuk menulis pesan ke database. Pertama, tambahkan kolom formulir dan tombol kirim, lalu tambahkan kode yang menghubungkan elemen ini dengan database.
- Buat file baru bernama
guest_book.dart
, tambahkan widget statefulGuestBook
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, Anda membuat instance formulir sehingga dapat memvalidasi bahwa pesan benar-benar berisi konten dan menampilkan pesan error kepada pengguna jika tidak ada. Untuk memvalidasi formulir, Anda mengakses status formulir di balik formulir dengan GlobalKey
. Untuk mengetahui informasi selengkapnya tentang Kunci dan cara menggunakannya, lihat Kapan Harus Menggunakan Kunci.
Perhatikan juga cara widget disusun, Anda memiliki Row
dengan TextFormField
dan StyledButton
, yang berisi Row
. Perhatikan juga bahwa TextFormField
digabungkan dalam widget Expanded
, yang memaksa TextFormField
untuk mengisi ruang tambahan di baris. Untuk lebih memahami alasan hal ini diperlukan, lihat Memahami batasan.
Setelah memiliki widget yang memungkinkan pengguna memasukkan beberapa teks untuk ditambahkan ke Buku Tamu, Anda harus menampilkannya di layar.
- Edit isi
HomePage
untuk menambahkan dua baris berikut di akhir turunanListView
:
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, kode ini tidak cukup untuk melakukan hal yang berguna. Anda akan segera mengupdate kode ini agar berfungsi.
Pratinjau aplikasi
Saat pengguna mengklik KIRIM, tindakan ini akan memicu cuplikan kode berikut. Tindakan ini akan 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 di koleksi guestbook
.
Perhatikan bahwa FirebaseAuth.instance.currentUser.uid
adalah referensi ke ID unik yang dibuat otomatis yang diberikan Authentication untuk semua pengguna yang login.
- Dalam file
lib/app_state.dart
, tambahkan metodeaddMessageToGuestBook
. 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 ditambahkan ke Buku Tamu dan Anda memiliki kode untuk menambahkan entri ke Firestore. Sekarang, Anda hanya perlu menghubungkan keduanya.
- Dalam file
lib/home_page.dart
, buat perubahan berikut pada widgetHomePage
:
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 ditambahkan di awal langkah ini dengan implementasi penuh. Anda kembali menggunakan 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 di database. Di bagian berikutnya, Anda akan menguji apakah pesan yang ditambahkan dipublikasikan di database.
Menguji pengiriman pesan
- Jika perlu, login ke aplikasi.
- 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 mengirim lebih banyak pesan, Anda akan menambahkan lebih banyak dokumen ke koleksi guestbook
. Misalnya, lihat cuplikan kode berikut:
7. Membaca pesan
Sangat bagus 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 dipicu saat data berubah, lalu membuat elemen UI yang menampilkan pesan baru. Anda menambahkan kode ke status aplikasi yang memproses pesan yang baru ditambahkan dari aplikasi.
- Buat file baru
guest_book_message.dart
, tambahkan class berikut untuk mengekspos 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;
}
- Di 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
- 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.
- Di bagian inisialisasi
ApplicationState
, tambahkan baris berikut untuk berlangganan kueri melalui 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 di koleksi guestbook
, dan menangani langganan dan berhenti berlangganan koleksi ini. Anda memproses streaming, tempat Anda merekonstruksi cache lokal pesan dalam koleksi guestbook
dan juga menyimpan referensi ke langganan ini sehingga Anda dapat berhenti berlangganan nanti. Ada banyak hal yang terjadi di sini, jadi Anda harus mempelajarinya di debugger untuk memeriksa apa yang terjadi guna mendapatkan model mental yang lebih jelas. Untuk informasi selengkapnya, lihat Mendapatkan update real-time dengan Firestore.
- Di file
lib/guest_book.dart
, tambahkan impor berikut:
import 'guest_book_message.dart';
- 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();
}
- Di
_GuestBookState
, ubah metodebuild
sebagai berikut untuk mengekspos 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.
);
}
}
Anda menggabungkan konten sebelumnya dari metode build()
dengan widget Column
, lalu menambahkan koleksi untuk di bagian akhir turunan Column
untuk membuat Paragraph
baru untuk setiap pesan dalam daftar pesan.
- Perbarui isi
HomePage
untuk membuatGuestBook
dengan benar menggunakan parametermessages
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 otomatis dan langsung menyinkronkan data dengan klien yang berlangganan database.
Menguji sinkronisasi pesan:
- Di aplikasi, temukan pesan yang Anda buat sebelumnya di database.
- Menulis pesan baru. Halaman tersebut akan muncul seketika.
- Buka ruang kerja Anda di beberapa jendela atau tab. Pesan disinkronkan secara real time di seluruh jendela dan tab.
- Opsional: Di menu Database Firebase console, hapus, ubah, atau tambahkan pesan baru secara manual. Semua perubahan akan muncul di UI.
Selamat! Anda telah membaca dokumen Firestore di aplikasi.
Pratinjau aplikasi
8. Menyiapkan aturan keamanan dasar
Anda awalnya menyiapkan Firestore untuk menggunakan mode pengujian, yang berarti database Anda terbuka untuk operasi baca dan tulis. 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 merupakan bagian integral dari struktur dan perilaku aplikasi Anda.
Aturan Keamanan Firebase memungkinkan Anda mengontrol akses ke dokumen dan koleksi di database. Dengan sintaksis aturan yang fleksibel, Anda dapat membuat aturan yang cocok dengan apa pun, dari semua penulisan ke seluruh database hingga operasi pada dokumen tertentu.
Siapkan aturan keamanan dasar:
- Di menu Develop Firebase console, klik Database > Rules. Anda akan melihat aturan keamanan default berikut dan peringatan bahwa aturan tersebut bersifat publik:
- 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 Autentikasi sebagai kolom di setiap dokumen buku tamu, Anda bisa mendapatkan UID Autentikasi dan memverifikasi bahwa siapa pun yang mencoba menulis ke dokumen memiliki UID Autentikasi yang cocok.
- 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.
- Tambahkan validasi data untuk memastikan bahwa 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
Merekam status RSVP tamu
Saat ini, aplikasi Anda hanya mengizinkan pengguna untuk melakukan chat jika mereka tertarik dengan acara tersebut. Selain itu, satu-satunya cara untuk mengetahui apakah seseorang akan hadir adalah jika mereka mengatakannya di chat.
Pada langkah ini, Anda akan mengatur dan memberi tahu orang-orang jumlah orang yang akan datang. Anda menambahkan beberapa kemampuan ke status aplikasi. Yang pertama adalah kemampuan pengguna yang login untuk menentukan apakah mereka akan hadir. Yang kedua adalah penghitung jumlah orang yang hadir.
- Di file
lib/app_state.dart
, tambahkan baris berikut ke bagian pengaksesApplicationState
agar 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});
}
}
- 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 login untuk menentukan apakah pengguna hadir.
- Tambahkan enumerasi berikut di bagian atas file
lib/app_state.dart
.
lib/app_state.dart
enum Attending { yes, no, unknown }
- 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'),
),
],
),
);
}
}
}
Status ini dimulai dalam status tidak ditentukan dengan Ya atau Tidak tidak dipilih. Setelah pengguna memilih apakah mereka akan hadir, Anda menampilkan opsi tersebut yang ditandai dengan tombol yang terisi dan opsi lainnya akan menghilang dengan rendering datar.
- Perbarui metode
build()
HomePage
untuk memanfaatkanYesNoSelection
, memungkinkan pengguna yang login untuk menentukan apakah mereka akan hadir, dan menampilkan jumlah peserta untuk 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 telah menyiapkan beberapa aturan, sehingga data yang Anda tambahkan dengan tombol akan ditolak. Anda perlu memperbarui aturan untuk mengizinkan penambahan ke koleksi attendees
.
- Di koleksi
attendees
, ambil UID Authentication yang Anda gunakan sebagai nama dokumen dan pastikanuid
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 tamu karena tidak ada data pribadi di sana, tetapi hanya kreator yang dapat memperbaruinya.
- Tambahkan validasi data untuk memastikan bahwa 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;
}
}
}
- Opsional: Di aplikasi, klik tombol untuk melihat hasilnya di dasbor Firestore di Firebase console.
Pratinjau aplikasi
10. Selamat!
Anda telah menggunakan Firebase untuk membuat aplikasi web interaktif dan real-time.