1. Başlamadan önce
Bu kod laboratuvarında, Android ve iOS için Flutter mobil uygulamaları oluşturmak için Firebase'in bazı temellerini öğreneceksiniz.
Önkoşullar
Bu codelab, Flutter'a aşina olduğunuzu ve Flutter SDK'yı ve bir düzenleyiciyi yüklediğinizi varsayar.
ne yaratacaksın
Bu kod laboratuvarında, Flutter kullanarak Android, iOS, Web ve macOS üzerinde bir etkinlik LCV ve ziyaretçi defteri sohbet uygulaması oluşturacaksınız. Firebase Authentication ile kullanıcıların kimliğini doğrulayacak ve Cloud Firestore'u kullanarak verileri senkronize edeceksiniz.
Neye ihtiyacın olacak
Bu codelab'i aşağıdaki cihazlardan herhangi birini kullanarak çalıştırabilirsiniz:
- Bilgisayarınıza bağlı ve geliştirici moduna ayarlanmış fiziksel bir cihaz (Android veya iOS).
- iOS simülatörü. ( Xcode araçlarının yüklenmesini gerektirir.)
- Android öykünücüsü. ( Android Studio'da kurulum gerektirir.)
Yukarıdakilere ek olarak, şunlara da ihtiyacınız olacak:
- Chrome gibi seçtiğiniz bir tarayıcı.
- Dart ve Flutter eklentileriyle yapılandırılmış Android Studio veya VS Code gibi seçtiğiniz bir IDE veya metin düzenleyicisi.
- Flutter'ın en son
stable
sürümü (ya da uçlarda yaşamayı seviyorsanızbeta
). - Firebase projenizi oluşturmak ve yönetmek için gmail hesabı gibi bir Google hesabı.
-
firebase
komut satırı aracı , gmail hesabınıza giriş yaptı. - Codelab'ın örnek kodu. Kodun nasıl alınacağını öğrenmek için sonraki adıma bakın.
2. Örnek kodu alın
Projemizin ilk sürümünü GitHub'dan indirerek başlayalım.
GitHub deposunu komut satırından klonlayın:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Alternatif olarak, GitHub'ın cli aracını yüklediyseniz:
gh repo clone flutter/codelabs flutter-codelabs
Örnek kod, bir codelabs koleksiyonunun kodunu içeren flutter-codelabs
dizinine klonlanmalıdır. Bu codelab'in kodu flutter-codelabs/firebase-get-to-know-flutter
.
flutter-codelabs/firebase-get-to-know-flutter
altındaki dizin yapısı, adlandırılmış her adımın sonunda nerede olmanız gerektiğine dair bir dizi anlık görüntüdür. Bu 2. Adımdır, bu nedenle eşleşen dosyaları bulmak şu kadar kolaydır:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
İleri atlamak veya bir adımdan sonra bir şeyin nasıl görünmesi gerektiğini görmek istiyorsanız, ilgilendiğiniz adımın adını taşıyan dizine bakın.
Başlangıç uygulamasını içe aktarın
flutter-codelabs/firebase-get-to-know-flutter/step_02
dizinini tercih ettiğiniz IDE'ye açın veya içe aktarın. Bu dizin, henüz işlevsel olmayan bir Flutter buluşma uygulamasından oluşan kod laboratuvarı için başlangıç kodunu içerir.
Üzerinde çalışılacak dosyaları bulun
Bu uygulamadaki kod, birden çok dizine yayılmıştır. Bu işlevsellik ayrımı, kodu işlevselliğe göre gruplayarak üzerinde çalışmayı kolaylaştırmak için tasarlanmıştır.
Projede aşağıdaki dosyaları bulun:
-
lib/main.dart
: Bu dosya, ana giriş noktasını ve uygulama pencere öğesini içerir. -
lib/src/widgets.dart
: Bu dosya, uygulamanın stilini standartlaştırmaya yardımcı olacak bir avuç widget içerir. Bunlar, başlangıç uygulamasının ekranını oluşturmak için kullanılır. -
lib/src/authentication.dart
: Bu dosya, Firebase e-posta tabanlı kimlik doğrulaması için bir oturum açma kullanıcı deneyimi oluşturmak için bir dizi widget ile kısmen FirebaseUI Auth uygulamasını içerir. Yetkilendirme akışına yönelik bu widget'lar henüz başlangıç uygulamasında kullanılmamaktadır, ancak bunları yakında bağlayacaksınız.
Uygulamanın geri kalanını oluşturmak için gerektiği kadar ek dosyalar ekleyeceksiniz.
lib/main.dart
dosyasını gözden geçirme
Bu uygulama, Roboto'yu tüm uygulama boyunca varsayılan yazı tipi yapmamızı sağlamak için google_fonts
paketinden yararlanır. Motive okuyucu için bir alıştırma, fonts.google.com'u keşfetmek ve orada keşfettiğiniz yazı tiplerini uygulamanın farklı bölümlerinde kullanmaktır.
lib/src/widgets.dart
adresindeki yardımcı pencere öğelerini Header
, Paragraph
ve IconAndDetail
. Bu widget'lar, yinelenen kodu ortadan kaldırarak HomePage
açıklanan sayfa düzenindeki dağınıklığı azaltır. Bu, tutarlı bir görünüm ve his sağlamanın ek avantajına sahiptir.
Uygulamanız Android, iOS, Web ve macOS'ta şöyle görünür:
Uygulama önizlemesi
3. Bir Firebase projesi oluşturun ve kurun
Etkinlik bilgilerini görüntülemek konuklarınız için harikadır, ancak yalnızca etkinlikleri göstermek hiç kimse için pek yararlı değildir. Bu uygulamaya bazı dinamik işlevler ekleyelim. Bunun için Firebase'i uygulamanıza bağlamanız gerekir. Firebase'i kullanmaya başlamak için bir Firebase projesi oluşturmanız ve ayarlamanız gerekir.
Bir Firebase projesi oluşturun
- Firebase'de oturum açın.
- Firebase konsolunda, Proje Ekle'yi (veya Bir proje oluşturun ) tıklayın ve Firebase projenizi Firebase-Flutter-Codelab olarak adlandırın .
- Proje oluşturma seçeneklerine tıklayın. İstenirse Firebase şartlarını kabul edin. Bu uygulama için Analytics kullanmayacağınız için Google Analytics kurulumunu atlayın.
Firebase projeleri hakkında daha fazla bilgi edinmek için Firebase projelerini anlama bölümüne bakın.
Oluşturduğunuz uygulama, web uygulamaları için kullanılabilen birkaç Firebase ürünü kullanıyor:
- Kullanıcılarınızın uygulamanızda oturum açmasına izin vermek için Firebase Kimlik Doğrulaması .
- Bulutta yapılandırılmış verileri kaydetmek ve veriler değiştiğinde anında bildirim almak için Cloud Firestore .
- Veritabanınızın güvenliğini sağlamak için Firebase Güvenlik Kuralları .
Bu ürünlerden bazıları özel yapılandırmaya ihtiyaç duyar veya Firebase konsolu kullanılarak etkinleştirilmesi gerekir.
Firebase Kimlik Doğrulaması için e-posta ile oturum açmayı etkinleştirin
Kullanıcıların web uygulamasında oturum açmasına izin vermek için bu codelab için E-posta/Parola ile oturum açma yöntemini kullanacaksınız:
- Firebase konsolunda, sol paneldeki Oluştur menüsünü genişletin.
- Kimlik Doğrulama 'yı ve ardından Başlarken düğmesini, ardından Oturum açma yöntemi sekmesini (veya doğrudan Oturum açma yöntemi sekmesine gitmek için burayı tıklayın ) tıklayın.
- Oturum açma sağlayıcıları listesinde E-posta/Parola'ya tıklayın, Etkinleştir anahtarını açık konuma getirin ve ardından Kaydet'e tıklayın .
Cloud Firestore'u Etkinleştir
Web uygulaması, sohbet mesajlarını kaydetmek ve yeni sohbet mesajları almak için Cloud Firestore'u kullanır.
Cloud Firestore'u etkinleştirin:
- Firebase konsolunun Oluştur bölümünde, Cloud Firestore öğesini tıklayın.
- Veritabanı oluştur 'u tıklayın.
- Test modunda başlat seçeneğini seçin. Güvenlik kurallarıyla ilgili sorumluluk reddini okuyun. Test modu, geliştirme sırasında veritabanına özgürce yazabilmenizi sağlar. İleri'yi tıklayın.
- Veritabanınız için konumu seçin (Yalnızca varsayılanı kullanabilirsiniz). Bu konumun daha sonra değiştirilemeyeceğini unutmayın.
- Etkinleştir'i tıklayın.
4. Firebase yapılandırması
Firebase'i Flutter ile kullanmak için Flutter projesini FlutterFire kitaplıklarını doğru kullanacak şekilde yapılandırmak için bir süreci izlemeniz gerekir:
- Projenize FlutterFire bağımlılıklarını ekleyin
- İstediğiniz platformu Firebase projesine kaydedin
- Platforma özel yapılandırma dosyasını indirin ve koda ekleyin.
Flutter uygulamanızın üst düzey dizininde android
, ios
, macos
ve web
adlı alt dizinler vardır. Bu dizinler, sırasıyla iOS ve Android için platforma özgü yapılandırma dosyalarını tutar.
Bağımlılıkları yapılandır
Bu uygulamada kullandığınız iki Firebase ürünü için FlutterFire kitaplıklarını eklemeniz gerekir - Firebase Auth ve Cloud Firestore. Bağımlılıkları eklemek için aşağıdaki üç komutu çalıştırın.
$ flutter pub add firebase_core Resolving dependencies... + firebase_core 1.10.5 + firebase_core_platform_interface 4.2.2 + firebase_core_web 1.5.2 + flutter_web_plugins 0.0.0 from sdk flutter + js 0.6.3 test_api 0.4.3 (0.4.8 available) Changed 5 dependencies!
firebase_core
, tüm Firebase Flutter eklentileri için gereken ortak koddur.
$ flutter pub add firebase_auth Resolving dependencies... + firebase_auth 3.3.3 + firebase_auth_platform_interface 6.1.8 + firebase_auth_web 3.3.4 + intl 0.17.0 test_api 0.4.3 (0.4.8 available) Changed 4 dependencies!
firebase_auth
, Firebase'in Kimlik Doğrulama özelliğiyle entegrasyon sağlar.
$ flutter pub add cloud_firestore Resolving dependencies... + cloud_firestore 3.1.4 + cloud_firestore_platform_interface 5.4.9 + cloud_firestore_web 2.6.4 test_api 0.4.3 (0.4.8 available) Changed 3 dependencies!
cloud_firestore
, Cloud Firestore veri deposuna erişim sağlar.
$ flutter pub add provider Resolving dependencies... + nested 1.0.0 + provider 6.0.1 test_api 0.4.3 (0.4.8 available) Changed 2 dependencies!
Gerekli paketleri eklerken, Firebase'i uygun şekilde kullanmak için iOS, Android, macOS ve Web runner projelerini de yapılandırmanız gerekir. İş mantığının görüntü mantığından ayrılmasını sağlayacak provider
paketini de kullanıyorsunuz.
flutterfire
yükleme
FlutterFire CLI, temeldeki Firebase CLI'ye bağlıdır. Henüz yapmadıysanız, makinenizde Firebase CLI'nin kurulu olduğundan emin olun.
Ardından, aşağıdaki komutu çalıştırarak FlutterFire CLI'yi kurun:
$ dart pub global activate flutterfire_cli
Yüklendikten sonra, flutterfire
komutu küresel olarak kullanılabilir olacaktır.
Uygulamalarınızı yapılandırma
CLI, belirli bir platform için tüm yapılandırmayı oluşturmak üzere Firebase projenizden ve seçilen proje uygulamalarından bilgi alır.
Uygulamanızın kökünde, configure komutunu çalıştırın:
$ flutterfire configure
Konfigürasyon komutu bir dizi işlemde size rehberlik edecektir:
- Bir Firebase projesi seçme (.firebaserc dosyasına göre veya Firebase Konsolundan).
- Hangi platformlar için (örn. Android, iOS, macOS ve web) yapılandırmak istediğinizi belirtin.
- Yapılandırmayı çıkarmak için seçilen platformlar için hangi Firebase uygulamalarının kullanılması gerektiğini belirleyin. Varsayılan olarak, CLI, mevcut proje yapılandırmanıza göre Firebase uygulamalarını otomatik olarak eşleştirmeye çalışır.
- Projenizde bir firebase_options.dart dosyası oluşturun.
macOS'u yapılandırın
MacOS'ta Flutter, tamamen korumalı uygulamalar oluşturur. Bu uygulama, Firebase sunucularıyla iletişim kurmak için ağı kullanarak entegre olduğundan, uygulamanızı ağ istemcisi ayrıcalıklarıyla yapılandırmanız gerekecektir.
macos/Runner/DebugProfile.yetkilendirmeleri
<?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.yetkilendirmeleri
<?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>
Daha fazla ayrıntı için Yetkiler ve Uygulama Korumalı Alanı'na bakın.
5. Kullanıcı oturum açma (RSVP) ekleyin
Firebase'i uygulamaya eklediğinize göre, kişileri Firebase Authentication kullanarak kaydeden bir LCV düğmesi ayarlayabilirsiniz. Android yerel, iOS yerel ve Web için önceden oluşturulmuş FirebaseUI Auth paketleri vardır, ancak Flutter için bu özelliği oluşturmanız gerekir.
Adım 2'de aldığınız proje, kimlik doğrulama akışının çoğu için kullanıcı arabirimini uygulayan bir dizi pencere öğesi içeriyordu. Firebase Authentication'ı uygulamaya entegre etmek için iş mantığını uygulayacaksınız.
Sağlayıcı ile İş Mantığı
provider
paketini, uygulamanın Flutter widget'ları ağacı boyunca merkezileştirilmiş bir uygulama durumu nesnesini kullanılabilir hale getirmek için kullanacaksınız. Başlangıç olarak, lib/main.dart
üst kısmındaki içe aktarmaları değiştirin:
lib/main.dart
import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.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';
import
satırları, Firebase Core ve Auth'u tanıtır, uygulama durumu nesnesini pencere öğesi ağacı aracılığıyla kullanılabilir hale getirmek için kullandığınız provider
paketini çeker ve lib/src
kimlik doğrulama pencere öğelerini içerir.
Bu uygulama durumu nesnesi ApplicationState
, bu adım için iki ana sorumluluğa sahiptir, ancak sonraki adımlarda uygulamaya daha fazla yetenek ekledikçe ek sorumluluklar kazanacaktır. İlk sorumluluk, Firebase kitaplığını Firebase.initializeApp()
çağrısı ile başlatmaktır ve ardından yetkilendirme akışının ele alınması vardır. lib/main.dart
sonuna aşağıdaki sınıfı ekleyin:
lib/main.dart
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
} else {
_loginState = ApplicationLoginState.loggedOut;
}
notifyListeners();
});
}
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
void startLoginFlow() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> verifyEmail(
String email,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
var methods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
if (methods.contains('password')) {
_loginState = ApplicationLoginState.password;
} else {
_loginState = ApplicationLoginState.register;
}
_email = email;
notifyListeners();
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
Future<void> signInWithEmailAndPassword(
String email,
String password,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void cancelRegistration() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> registerAccount(
String email,
String displayName,
String password,
void Function(FirebaseAuthException e) errorCallback) async {
try {
var credential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
await credential.user!.updateDisplayName(displayName);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void signOut() {
FirebaseAuth.instance.signOut();
}
}
Bu sınıfta birkaç önemli noktayı belirtmekte fayda var. Kullanıcı kimliği doğrulanmadan başlar, uygulama, e-posta adresinin dosyada olup olmadığına bağlı olarak kullanıcının e-posta adresini isteyen bir form gösterir, uygulama ya kullanıcıdan kaydolmasını ister ya da şifresini ister ve ardından her şeyin yolunda gittiğini varsayarsa, kullanıcı kimliği doğrulanır.
Mevcut bir hesabı olan ve giriş yapmakta sorun yaşayan bir kullanıcının durumunu ele almadığından, bunun FirebaseUI Auth akışının tam bir uygulaması olmadığına dikkat edilmelidir. motive okuyucu
Kimlik Doğrulama akışını entegre etme
Artık uygulama durumunun başlangıcına sahip olduğunuza göre, uygulama durumunu uygulama başlatmaya bağlamanın ve kimlik doğrulama akışını HomePage
Sayfaya eklemenin zamanı geldi. provider
paketi aracılığıyla uygulama durumunu entegre etmek için ana giriş noktasını güncelleyin:
lib/main.dart
void main() {
// Modify from here
runApp(
ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: (context, _) => App(),
),
);
// to here.
}
main
işlevde yapılan değişiklik, sağlayıcı paketini ChangeNotifierProvider
pencere aracını kullanarak uygulama durumu nesnesini başlatmaktan sorumlu kılar. Uygulama durumu nesnesi ChangeNotifier
genişlettiği ve bu, provider
paketinin bağımlı pencere öğelerinin ne zaman yeniden görüntüleneceğini bilmesini sağladığı için bu belirli sağlayıcı sınıfını kullanıyorsunuz. Son olarak, HomePage
build
yöntemini güncelleyerek uygulama durumunu Authentication
ile entegre edin:
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'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.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!',
),
],
),
);
}
}
Authentication
pencere aracının örneğini oluşturursunuz ve bunu bir Consumer
pencere aracına sararsınız. Tüketici pencere öğesi, uygulama durumu değiştiğinde, provider
paketinin ağacın bir bölümünü yeniden oluşturmak için kullanılabileceği olağan yoldur. Authentication
pencere öğesi, şimdi test edeceğiniz kimlik doğrulama kullanıcı arabirimidir.
Kimlik Doğrulama akışını test etme
Kullanıcının e-posta formunu başlatmak için LCV düğmesine dokunabileceği kimlik doğrulama akışının başlangıcı burada.
E-postayı girdikten sonra, sistem kullanıcının kayıtlı olup olmadığını onaylar, bu durumda kullanıcıdan bir şifre istenir, alternatif olarak kullanıcı kayıtlı değilse, kayıt formuna geçer.
Hata işleme akışını kontrol etmek için kısa bir parola (altı karakterden az) girmeyi denediğinizden emin olun. Kullanıcı kayıtlıysa, bunun yerine şifreyi görecektir.
Bu sayfada, bu sayfadaki hata işlemeyi kontrol etmek için yanlış şifreler girdiğinizden emin olun. Son olarak, kullanıcı oturum açtıktan sonra, kullanıcıya tekrar oturumu kapatma olanağı sunan oturum açma deneyimini göreceksiniz.
Ve bununla bir kimlik doğrulama akışı uyguladınız. Tebrikler!
6. Cloud Firestore'a mesaj yazın
Kullanıcıların geldiğini bilmek harika, ancak konuklara uygulamada yapacakları başka bir şey verelim. Ya bir ziyaretçi defterine mesaj bırakabilirlerse? Gelmek için neden heyecanlı olduklarını veya kiminle tanışmayı umduklarını paylaşabilirler.
Kullanıcıların uygulamada yazdığı sohbet mesajlarını saklamak için Cloud Firestore'u kullanacaksınız.
Veri örneği
Cloud Firestore bir NoSQL veritabanıdır ve veritabanında depolanan veriler koleksiyonlara, belgelere, alanlara ve alt koleksiyonlara bölünür. Sohbetin her mesajını, guestbook
adı verilen üst düzey bir koleksiyonda bir belge olarak depolayacaksınız.
Firestore'a mesaj ekleyin
Bu bölümde, kullanıcıların veritabanına yeni mesajlar yazması için işlevsellik ekleyeceksiniz. İlk olarak, UI öğelerini (form alanı ve gönder düğmesi) eklersiniz ve ardından bu öğeleri veritabanına bağlayan kodu eklersiniz.
İlk olarak, cloud_firestore
paketi ve dart:async
için içe aktarmaları ekleyin.
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';
Bir mesaj alanının ve bir gönder düğmesinin kullanıcı arabirimi öğelerini oluşturmak için, lib/main.dart
GuestBook
yeni bir durum bilgisi olan Konuk Defteri'ni ekleyin.
lib/main.dart
class GuestBook extends StatefulWidget {
const GuestBook({required this.addMessage});
final FutureOr<void> Function(String message) addMessage;
@override
_GuestBookState 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'),
],
),
),
],
),
),
);
}
}
Burada bir iki ilgi çekici nokta var. İlk olarak, bir Form başlatıyorsunuz, böylece mesajın gerçekten bir içeriği olduğunu doğrulayabilirsiniz ve eğer yoksa kullanıcıya bir hata mesajı gösterebilirsiniz. Bir formu doğrulamanın yolu, formun arkasındaki form durumuna erişmeyi içerir ve bunun için bir GlobalKey
. Tuşlar ve bunların nasıl kullanılacağı hakkında daha fazla bilgi için lütfen Flutter Widgets 101 bölümü "Anahtarlar Ne Zaman Kullanılır" bölümüne bakın.
Ayrıca, pencere öğelerinin düzenlenme biçimine de dikkat edin, TextFormField
ve StyledButton
içeren bir Row
'a sahip olduğunuza ve kendisinin de bir Row
içerdiğine dikkat edin. Ayrıca TextFormField
öğesinin bir Expanded
pencere aracına sarıldığını unutmayın; bu, TextFormField
satırda fazladan yer kaplamaya zorlar. Bunun neden gerekli olduğunu daha iyi anlamak için lütfen Kısıtlamaları anlama bölümünü okuyun.
Artık, kullanıcının Ziyaretçi Defterine eklemek üzere bir metin girmesini sağlayan bir widget'ınız olduğuna göre, onu ekrana getirmeniz gerekir. Bunu yapmak için, ListView
'ın alt öğelerinin altına aşağıdaki iki satırı eklemek için HomePage
gövdesini düzenleyin:
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)),
Bu, Widget'ı görüntülemek için yeterli olsa da, yararlı bir şey yapmak için yeterli değildir. Bu kodu, işlevsel hale getirmek için kısa süre içinde güncelleyeceksiniz.
Uygulama önizlemesi
GÖNDER düğmesini tıklayan bir kullanıcı aşağıdaki kod parçacığını tetikleyecektir. Mesaj giriş alanının içeriğini veritabanının guestbook
koleksiyonuna ekler. Özellikle, addMessageToGuestBook
yöntemi, mesaj içeriğini yeni bir belgeye (otomatik olarak oluşturulan bir kimlikle) guestbook
koleksiyonuna ekler.
FirebaseAuth.instance.currentUser.uid
öğesinin, Firebase Authentication'ın oturum açmış tüm kullanıcılar için verdiği otomatik olarak oluşturulan benzersiz kimliğin bir referansı olduğunu unutmayın.
lib/main.dart
dosyasında başka bir değişiklik yapın. addMessageToGuestBook
yöntemini ekleyin. Bir sonraki adımda kullanıcı arayüzünü ve bu yeteneği birbirine bağlayacaksınız.
lib/main.dart
class ApplicationState extends ChangeNotifier {
// Current content of ApplicationState elided ...
// Add from here
Future<DocumentReference> addMessageToGuestBook(String message) {
if (_loginState != ApplicationLoginState.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
}
UI'yi veritabanına bağlama
Kullanıcının Ziyaretçi Defterine eklemek istediği metni girebileceği bir kullanıcı arayüzünüz var ve girişi Cloud Firestore'a eklemek için kodunuz var. Şimdi yapmanız gereken tek şey ikisini birbirine bağlamak. lib/main.dart
HomePage
widget'ında aşağıdaki değişikliği yapın.
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, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.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.loginState == ApplicationLoginState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// To here.
],
),
);
}
}
Bu adımın başında eklediğiniz iki satırı tam uygulama ile değiştirdiniz. Uygulama durumunu, oluşturmakta olduğunuz ağacın parçası için kullanılabilir kılmak için tekrar Consumer<ApplicationState>
kullanıyorsunuz. Bu, kullanıcı arayüzüne mesaj giren birine tepki vermenizi ve bunu veritabanında yayınlamanızı sağlar. Bir sonraki bölümde, eklenen mesajların veritabanında yayınlanıp yayınlanmadığını test edeceksiniz.
Mesaj göndermeyi test et
- Uygulamada oturum açtığınızdan emin olun.
- "Merhaba!" gibi bir mesaj girin ve ardından GÖNDER 'i tıklayın.
Bu eylem, mesajı Cloud Firestore veritabanınıza yazar. Ancak, yine de verileri almayı uygulamanız gerektiğinden, mesajı gerçek Flutter uygulamanızda görmezsiniz. Bunu bir sonraki adımda yapacaksınız.
Ancak yeni eklenen mesajı Firebase konsolunda görebilirsiniz.
Firebase konsolunda, Veritabanı panosunda , yeni eklediğiniz mesajınızla birlikte guestbook
koleksiyonunu görmelisiniz. Mesaj göndermeye devam ederseniz, ziyaretçi defteri koleksiyonunuz aşağıdaki gibi birçok belge içerecektir:
Firebase konsolu
7. Mesajları okuyun
Konukların veri tabanına mesaj yazabilmeleri çok hoş, ancak henüz uygulamada göremiyorlar. Hadi düzeltelim!
Mesajları senkronize et
Mesajları görüntülemek için, veriler değiştiğinde tetiklenen dinleyiciler eklemeniz ve ardından yeni mesajları gösteren bir UI öğesi oluşturmanız gerekir. Uygulamadan yeni eklenen mesajları dinleyen uygulama durumuna kod ekleyeceksiniz.
GuestBook
widget'ının hemen üzerinde aşağıdaki değer sınıfı bulunur. Bu sınıf, Cloud Firestore'da depoladığınız verilerin yapılandırılmış bir görünümünü sunar.
lib/main.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
ApplicationState
durumu ve alıcıları tanımladığınız bölümüne aşağıdaki yeni satırları ekleyin:
lib/main.dart
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
// Add from here
StreamSubscription<QuerySnapshot>? _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// to here.
Ve son olarak, ApplicationState
başlatma bölümünde, bir kullanıcı oturum açtığında belge koleksiyonuyla ilgili bir sorguya abone olmak ve oturumu kapattığında abonelikten çıkmak için aşağıdakileri ekleyin.
lib/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
// Add from here
_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();
});
// to here.
} else {
_loginState = ApplicationLoginState.loggedOut;
// Add from here
_guestBookMessages = [];
_guestBookSubscription?.cancel();
// to here.
}
notifyListeners();
});
}
Bu bölüm, guestbook
koleksiyonu üzerinde bir sorgu oluşturduğunuz ve bu koleksiyona abone olmayı ve abonelikten çıkmayı ele aldığınız yer olduğu için önemlidir. guestbook
koleksiyonundaki mesajların yerel önbelleğini yeniden oluşturduğunuz akışı dinlersiniz ve ayrıca daha sonra aboneliğinizi iptal edebilmeniz için bu aboneliğe bir referans depolarsınız. Burada çok şey oluyor ve ne zaman daha net bir zihinsel model elde edileceğini incelemek için bir hata ayıklayıcıda biraz zaman harcamaya değer.
Daha fazla bilgi için Cloud Firestore belgelerine bakın.
GuestBook
widget'ında, bu değişen durumu kullanıcı arayüzüne bağlamanız gerekir. Widget'ı, yapılandırmasının bir parçası olarak bir mesaj listesi ekleyerek değiştirirsiniz.
lib/main.dart
class GuestBook extends StatefulWidget {
// Modify the following line
const GuestBook({required this.addMessage, required this.messages});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
_GuestBookState createState() => _GuestBookState();
}
Ardından, build
yöntemini aşağıdaki gibi değiştirerek bu yeni yapılandırmayı _GuestBookState
içinde ortaya çıkarıyoruz.
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.
);
}
}
Oluşturma yönteminin önceki içeriğini bir Column
pencere aracıyla sararsınız ve ardından Sütun'un alt Column
kuyruğuna, iletiler listesindeki her ileti için yeni bir Paragraph
oluşturmak üzere bir koleksiyon eklersiniz.
Son olarak, şimdi yeni messages
parametresiyle HomePage
doğru bir şekilde oluşturmak için Ana GuestBook
gövdesini güncellemeniz gerekiyor.
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
),
],
],
),
),
Mesajları senkronize etmeyi test edin
Cloud Firestore, verileri veritabanına abone olan istemcilerle otomatik olarak ve anında senkronize eder.
- Veritabanında daha önce oluşturduğunuz mesajlar uygulamada görüntülenmelidir. Yeni mesajlar yazmaktan çekinmeyin; anında görünmelidirler.
- Çalışma alanınızı birden çok pencerede veya sekmede açarsanız, mesajlar sekmeler arasında gerçek zamanlı olarak eşitlenir.
- (İsteğe bağlı) Doğrudan Firebase konsolunun Veritabanı bölümünden manuel olarak silmeyi, değiştirmeyi veya yeni mesajlar eklemeyi deneyebilirsiniz; herhangi bir değişiklik kullanıcı arayüzünde görünmelidir.
Tebrikler! Uygulamanızda Cloud Firestore belgelerini okuyorsunuz!
Uygulama p incelemesi
8. Temel güvenlik kurallarını ayarlayın
Başlangıçta Cloud Firestore'u test modunu kullanacak şekilde ayarladınız; bu, veritabanınızın okuma ve yazma işlemlerine açık olduğu anlamına gelir. Ancak test modunu yalnızca geliştirmenin çok erken aşamalarında kullanmalısınız. En iyi uygulama olarak, uygulamanızı geliştirirken veritabanınız için güvenlik kuralları oluşturmalısınız. Güvenlik, uygulamanızın yapısı ve davranışının ayrılmaz bir parçası olmalıdır.
Güvenlik Kuralları, veritabanınızdaki belgelere ve koleksiyonlara erişimi denetlemenize olanak tanır. Esnek kurallar sözdizimi, tüm veritabanına tüm yazmalardan belirli bir belgedeki işlemlere kadar her şeyle eşleşen kurallar oluşturmanıza olanak tanır.
Firebase konsolunda Cloud Firestore için güvenlik kuralları yazabilirsiniz:
- Firebase konsolunun Geliştirme bölümünde Veritabanı öğesine tıklayın ve ardından Kurallar sekmesini seçin (veya doğrudan Kurallar sekmesine gitmek için burayı tıklayın).
- Kuralların herkese açık olduğuna dair bir uyarıyla birlikte aşağıdaki varsayılan güvenlik kurallarını görmelisiniz.
Koleksiyonları tanımlayın
İlk olarak, uygulamanın veri yazdığı koleksiyonları tanımlayın.
match /databases/{database}/documents
, güvenliğini sağlamak istediğiniz koleksiyonu tanımlayın:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
// You'll add rules here in the next step.
}
}
Güvenlik kuralları ekle
Her konuk defteri belgesinde bir alan olarak Kimlik Doğrulama UID'sini kullandığınız için, Kimlik Doğrulama UID'sini alabilir ve belgeye yazmaya çalışan herkesin eşleşen bir Kimlik Doğrulama UID'si olduğunu doğrulayabilirsiniz.
Aşağıda gösterildiği gibi okuma ve yazma kurallarını kural kümenize ekleyin:
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;
}
}
}
Şimdi, ziyaretçi defteri için, yalnızca oturum açmış kullanıcılar mesajları okuyabilir (herhangi bir mesaj!), ancak yalnızca bir mesajın yazarı bir mesajı düzenleyebilir.
Doğrulama kuralları ekle
Belgede beklenen tüm alanların mevcut olduğundan emin olmak için veri doğrulaması ekleyin:
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. Bonus adım: Öğrendiklerinizi uygulayın
Bir katılımcının LCV durumunu kaydedin
Şu anda uygulamanız, etkinlikle ilgilenen kişilerin sohbet etmeye başlamasına izin veriyor. Ayrıca, birinin gelip gelmediğini anlamanın tek yolu, onu sohbette yayınlamasıdır. Organize olalım ve kaç kişinin geleceğini insanlara bildirelim.
Uygulama durumuna birkaç yeni yetenek ekleyeceksiniz. Birincisi, oturum açmış bir kullanıcının katılıp katılmadıklarını aday gösterme yeteneğidir. İkinci yetenek, gerçekte kaç kişinin katıldığının bir sayacıdır.
lib/main.dart
içinde, UI kodunun bu durumla etkileşime girmesini sağlamak için erişimciler bölümüne aşağıdakini ekleyin:
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});
}
}
ApplicationState
init
yöntemini aşağıdaki gibi güncelleyin:
lib/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// 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();
});
}
Yukarıdakiler, katılımcı sayısını bulmak için her zaman abone olunan bir sorguyu ve yalnızca bir kullanıcı oturum açtığında etkin olan ikinci bir sorguyu, kullanıcının katılıp katılmadığını öğrenmek için ekler. Ardından, GuestBookMessage
bildiriminden sonra aşağıdaki numaralandırmayı ekleyin:
lib/main.dart
enum Attending { yes, no, unknown }
Şimdi eski radyo düğmeleri gibi davranan yeni bir widget tanımlayacaksınız. Ne evet ne de hayır seçili olmadan belirsiz bir durumda başlar, ancak kullanıcı katılıp katılmayacağını seçtiğinde, o seçeneği dolu bir düğmeyle vurgulanmış olarak gösterirsiniz ve diğer seçenek düz bir işleme ile geri çekilir.
lib/main.dart
class YesNoSelection extends StatelessWidget {
const YesNoSelection({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'),
),
],
),
);
}
}
}
Daha sonra, YesNoSelection
yararlanmak için HomePage
oluşturma yöntemini güncellemeniz gerekir, bu da oturum açmış bir kullanıcının katılıp katılmadığını belirlemesini sağlar. Ayrıca bu etkinlik için katılımcı sayısını da göstereceksiniz.
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.loginState == ApplicationLoginState.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,
),
],
],
),
),
Kural ekle
Halihazırda ayarlanmış bazı kurallarınız olduğundan, düğmelerle eklediğiniz yeni veriler reddedilecektir. attendees
koleksiyonuna eklemeye izin vermek için kuralları güncellemeniz gerekir.
attendees
koleksiyonu için, belge adı olarak Kimlik Doğrulama uid
kullandığınızdan, onu alabilir ve göndericinin kullanıcı kimliğinin yazdıkları belgeyle aynı olduğunu doğrulayabilirsiniz. Herkesin katılımcı listesini okumasına izin vereceksiniz (orada özel veri olmadığından), ancak yalnızca içerik oluşturucu onu güncelleyebilmelidir.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId;
}
}
}
Doğrulama kuralları ekle
Belgede beklenen tüm alanların mevcut olduğundan emin olmak için veri doğrulaması ekleyin:
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;
}
}
}
(İsteğe bağlı) Artık düğmelere tıklamanın sonuçlarını görüntüleyebilirsiniz. Firebase konsolunda Cloud Firestore kontrol panelinize gidin.
Uygulama önizlemesi
10. Tebrikler!
Etkileşimli, gerçek zamanlı bir web uygulaması oluşturmak için Firebase'i kullandınız!
Neyi kapsadık
- Firebase Kimlik Doğrulaması
- Bulut Firestore
- Firebase Güvenlik Kuralları
Sonraki adımlar
- Diğer Firebase ürünleri hakkında daha fazla bilgi edinmek ister misiniz? Belki de kullanıcıların yüklediği resim dosyalarını saklamak istiyorsunuz? Veya kullanıcılarınıza bildirim göndermek mi? Firebase belgelerine göz atın. Firebase için Flutter eklentileri hakkında daha fazla bilgi edinmek ister misiniz? Daha fazla bilgi için FlutterFire'a göz atın.
- Cloud Firestore hakkında daha fazla bilgi edinmek ister misiniz? Belki alt koleksiyonlar ve işlemler hakkında bilgi edinmek istersiniz? Cloud Firestore'da daha derinlere inen bir kod laboratuvarı için Cloud Firestore web kod laboratuvarına gidin. Veya Cloud Firestore'u tanımak için bu YouTube serisine göz atın!
Daha fazla bilgi edin
- Firebase sitesi: firebase.google.com
- Flutter sitesi: flutter.dev
- FlutterFire Firebase Çarpıntı widget'ları: firebase.flutter.dev
- Firebase YouTube kanalı
- Flutter YouTube kanalı
Nasıl gitti?
Geri bildiriminizi çok isteriz! Lütfen burada (çok) kısa bir form doldurun.
1. Başlamadan önce
Bu kod laboratuvarında, Android ve iOS için Flutter mobil uygulamaları oluşturmak için Firebase'in bazı temellerini öğreneceksiniz.
Önkoşullar
Bu codelab, Flutter'a aşina olduğunuzu ve Flutter SDK'yı ve bir düzenleyiciyi yüklediğinizi varsayar.
ne yaratacaksın
Bu kod laboratuvarında, Flutter kullanarak Android, iOS, Web ve macOS üzerinde bir etkinlik LCV ve ziyaretçi defteri sohbet uygulaması oluşturacaksınız. Firebase Authentication ile kullanıcıların kimliğini doğrulayacak ve Cloud Firestore'u kullanarak verileri senkronize edeceksiniz.
Neye ihtiyacın olacak
Bu codelab'i aşağıdaki cihazlardan herhangi birini kullanarak çalıştırabilirsiniz:
- Bilgisayarınıza bağlı ve geliştirici moduna ayarlanmış fiziksel bir cihaz (Android veya iOS).
- iOS simülatörü. ( Xcode araçlarının yüklenmesini gerektirir.)
- Android öykünücüsü. ( Android Studio'da kurulum gerektirir.)
Yukarıdakilere ek olarak, şunlara da ihtiyacınız olacak:
- Chrome gibi seçtiğiniz bir tarayıcı.
- Dart ve Flutter eklentileriyle yapılandırılmış Android Studio veya VS Code gibi seçtiğiniz bir IDE veya metin düzenleyicisi.
- Flutter'ın en son
stable
sürümü (ya da uçlarda yaşamayı seviyorsanızbeta
). - Firebase projenizi oluşturmak ve yönetmek için gmail hesabı gibi bir Google hesabı.
-
firebase
komut satırı aracı , gmail hesabınıza giriş yaptı. - Codelab'ın örnek kodu. Kodun nasıl alınacağını öğrenmek için sonraki adıma bakın.
2. Örnek kodu alın
Projemizin ilk sürümünü GitHub'dan indirerek başlayalım.
GitHub deposunu komut satırından klonlayın:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Alternatif olarak, GitHub'ın cli aracını yüklediyseniz:
gh repo clone flutter/codelabs flutter-codelabs
Örnek kod, bir codelabs koleksiyonunun kodunu içeren flutter-codelabs
dizinine klonlanmalıdır. Bu codelab'in kodu flutter-codelabs/firebase-get-to-know-flutter
.
flutter-codelabs/firebase-get-to-know-flutter
altındaki dizin yapısı, adlandırılmış her adımın sonunda nerede olmanız gerektiğine dair bir dizi anlık görüntüdür. Bu 2. Adımdır, bu nedenle eşleşen dosyaları bulmak şu kadar kolaydır:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
İleri atlamak veya bir adımdan sonra bir şeyin nasıl görünmesi gerektiğini görmek istiyorsanız, ilgilendiğiniz adımın adını taşıyan dizine bakın.
Başlangıç uygulamasını içe aktarın
flutter-codelabs/firebase-get-to-know-flutter/step_02
dizinini tercih ettiğiniz IDE'ye açın veya içe aktarın. Bu dizin, henüz işlevsel olmayan bir Flutter buluşma uygulamasından oluşan kod laboratuvarı için başlangıç kodunu içerir.
Üzerinde çalışılacak dosyaları bulun
Bu uygulamadaki kod, birden çok dizine yayılmıştır. Bu işlevsellik ayrımı, kodu işlevselliğe göre gruplayarak üzerinde çalışmayı kolaylaştırmak için tasarlanmıştır.
Projede aşağıdaki dosyaları bulun:
-
lib/main.dart
: Bu dosya, ana giriş noktasını ve uygulama pencere öğesini içerir. -
lib/src/widgets.dart
: Bu dosya, uygulamanın stilini standartlaştırmaya yardımcı olacak bir avuç widget içerir. Bunlar, başlangıç uygulamasının ekranını oluşturmak için kullanılır. -
lib/src/authentication.dart
: Bu dosya, Firebase e-posta tabanlı kimlik doğrulaması için bir oturum açma kullanıcı deneyimi oluşturmak için bir dizi widget ile kısmen FirebaseUI Auth uygulamasını içerir. Yetkilendirme akışına yönelik bu widget'lar henüz başlangıç uygulamasında kullanılmamaktadır, ancak bunları yakında bağlayacaksınız.
Uygulamanın geri kalanını oluşturmak için gerektiği kadar ek dosyalar ekleyeceksiniz.
lib/main.dart
dosyasını gözden geçirme
Bu uygulama, Roboto'yu tüm uygulama boyunca varsayılan yazı tipi yapmamızı sağlamak için google_fonts
paketinden yararlanır. Motive okuyucu için bir alıştırma, fonts.google.com'u keşfetmek ve orada keşfettiğiniz yazı tiplerini uygulamanın farklı bölümlerinde kullanmaktır.
lib/src/widgets.dart
adresindeki yardımcı pencere öğelerini Header
, Paragraph
ve IconAndDetail
. Bu widget'lar, yinelenen kodu ortadan kaldırarak HomePage
açıklanan sayfa düzenindeki dağınıklığı azaltır. Bu, tutarlı bir görünüm ve his sağlamanın ek avantajına sahiptir.
Uygulamanız Android, iOS, Web ve macOS'ta şöyle görünür:
Uygulama önizlemesi
3. Bir Firebase projesi oluşturun ve kurun
Etkinlik bilgilerini görüntülemek konuklarınız için harikadır, ancak yalnızca etkinlikleri göstermek hiç kimse için pek yararlı değildir. Bu uygulamaya bazı dinamik işlevler ekleyelim. Bunun için Firebase'i uygulamanıza bağlamanız gerekir. Firebase'i kullanmaya başlamak için bir Firebase projesi oluşturmanız ve ayarlamanız gerekir.
Bir Firebase projesi oluşturun
- Firebase'de oturum açın.
- Firebase konsolunda, Proje Ekle'yi (veya Bir proje oluşturun ) tıklayın ve Firebase projenizi Firebase-Flutter-Codelab olarak adlandırın .
- Proje oluşturma seçeneklerine tıklayın. Accept the Firebase terms if prompted. Skip setting up Google Analytics, because you won't be using Analytics for this app.
To learn more about Firebase projects, see Understand Firebase projects .
The app that you're building uses several Firebase products that are available for web apps:
- Firebase Authentication to allow your users to sign in to your app.
- Cloud Firestore to save structured data on the cloud and get instant notification when data changes.
- Firebase Security Rules to secure your database.
Some of these products need special configuration or need to be enabled using the Firebase console.
Enable email sign-in for Firebase Authentication
To allow users to sign in to the web app, you'll use the Email/Password sign-in method for this codelab:
- In the Firebase console, expand the Build menu in the left panel.
- Click Authentication , and then click the Get Started button, then the Sign-in method tab (or click here to go directly to the Sign-in method tab).
- Click Email/Password in the Sign-in providers list, set the Enable switch to the on position, and then click Save .
Enable Cloud Firestore
The web app uses Cloud Firestore to save chat messages and receive new chat messages.
Enable Cloud Firestore:
- In the Firebase console's Build section, click Cloud Firestore .
- Click Create database .
- Select the Start in test mode option. Read the disclaimer about the security rules. Test mode ensures that you can freely write to the database during development. Click Next .
- Select the location for your database (You can just use the default). Note that this location can't be changed later.
- Click Enable .
4. Firebase configuration
In order to use Firebase with Flutter, you need to follow a process to configure the Flutter project to utilise the FlutterFire libraries correctly:
- Add the FlutterFire dependencies to your project
- Register the desired platform on the Firebase project
- Download the platform-specific configuration file, and add it to the code.
In the top-level directory of your Flutter app, there are subdirectories called android
, ios
, macos
and web
. These directories hold the platform-specific configuration files for iOS and Android, respectively.
Configure dependencies
You need to add the FlutterFire libraries for the two Firebase products you are utilizing in this app - Firebase Auth and Cloud Firestore. Run the following three commands to add the depencies.
$ flutter pub add firebase_core Resolving dependencies... + firebase_core 1.10.5 + firebase_core_platform_interface 4.2.2 + firebase_core_web 1.5.2 + flutter_web_plugins 0.0.0 from sdk flutter + js 0.6.3 test_api 0.4.3 (0.4.8 available) Changed 5 dependencies!
The firebase_core
is the common code required for all Firebase Flutter plugins.
$ flutter pub add firebase_auth Resolving dependencies... + firebase_auth 3.3.3 + firebase_auth_platform_interface 6.1.8 + firebase_auth_web 3.3.4 + intl 0.17.0 test_api 0.4.3 (0.4.8 available) Changed 4 dependencies!
The firebase_auth
enables integration with Firebase's Authentication capability.
$ flutter pub add cloud_firestore Resolving dependencies... + cloud_firestore 3.1.4 + cloud_firestore_platform_interface 5.4.9 + cloud_firestore_web 2.6.4 test_api 0.4.3 (0.4.8 available) Changed 3 dependencies!
The cloud_firestore
enables access to Cloud Firestore data storage.
$ flutter pub add provider Resolving dependencies... + nested 1.0.0 + provider 6.0.1 test_api 0.4.3 (0.4.8 available) Changed 2 dependencies!
While you have added the required packages, you also need to configure the iOS, Android, macOS and Web runner projects to appropriately utilise Firebase. You are also using the provider
package that will enable separation of business logic from display logic.
Installing flutterfire
The FlutterFire CLI depends on the underlying Firebase CLI. If you haven't done so already, ensure the Firebase CLI is installed on your machine.
Next, install the FlutterFire CLI by running the following command:
$ dart pub global activate flutterfire_cli
Once installed, the flutterfire
command will be globally available.
Configuring your apps
The CLI extracts information from your Firebase project and selected project applications to generate all the configuration for a specific platform.
In the root of your application, run the configure command:
$ flutterfire configure
The configuration command will guide you through a number of processes:
- Selecting a Firebase project (based on the .firebaserc file or from the Firebase Console).
- Prompt what platforms (eg Android, iOS, macOS & web) you would like configuration for.
- Identify which Firebase applications for the chosen platforms should be used to extract configuration for. By default, the CLI will attempt to automatically match Firebase apps based on your current project configuration.
- Generate a firebase_options.dart file in your project.
Configure macOS
Flutter on macOS builds fully sandboxed applications. As this application is integrating using the network to communicate with the Firebase servers, you will need to configure your application with network client privileges.
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>
See Entitlements and the App Sandbox for more detail.
5. Add user sign-in (RSVP)
Now that you've added Firebase to the app, you can set up an RSVP button that registers people using Firebase Authentication . For Android native, iOS native, and Web there are pre-built FirebaseUI Auth packages, but for Flutter you will need to build this capability.
The project you retrieved in Step 2 included a set of widgets that implements the user interface for most of the authentication flow. You will implement the business logic to integrate Firebase Authentication into the application.
Business Logic with Provider
You are going to use the provider
package to make a centralized application state object available throughout the application's tree of Flutter widgets. To start with, modify the imports at the top of lib/main.dart
:
lib/main.dart
import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.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';
The import
lines introduce Firebase Core and Auth, pull in the provider
package which you are using to make the application state object available through the widget tree, and include the authentication widgets from lib/src
.
This application state object, ApplicationState
, has two main responsibilities for this step, but will gain additional responsibilities as you add more capabilities to the application in later steps. The first responsibility is to initialize the Firebase library with a call to Firebase.initializeApp()
, and then there is the handling of the authorization flow. Add the following class to the end of lib/main.dart
:
lib/main.dart
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
} else {
_loginState = ApplicationLoginState.loggedOut;
}
notifyListeners();
});
}
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
void startLoginFlow() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> verifyEmail(
String email,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
var methods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
if (methods.contains('password')) {
_loginState = ApplicationLoginState.password;
} else {
_loginState = ApplicationLoginState.register;
}
_email = email;
notifyListeners();
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
Future<void> signInWithEmailAndPassword(
String email,
String password,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void cancelRegistration() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> registerAccount(
String email,
String displayName,
String password,
void Function(FirebaseAuthException e) errorCallback) async {
try {
var credential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
await credential.user!.updateDisplayName(displayName);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void signOut() {
FirebaseAuth.instance.signOut();
}
}
It is worth noting a few key points in this class. The user starts off unauthenticated, the app shows a form requesting the user's email address, depending on whether that email address is on file, the app will either ask the user register, or request their password, and then assuming everything works out, the user is authenticated.
It must be noted that this isn't a complete implementation of the FirebaseUI Auth flow, as it does not handle the case of a user with an existing account who is having trouble logging in. Implementing this additional capability is left as an exercise to the motivated reader.
Integrating the Authentication flow
Now that you have the start of the application state it is time to wire the application state into the app initialization and add the authentication flow into HomePage
. Update the main entry point to integrate application state via the provider
package:
lib/main.dart
void main() {
// Modify from here
runApp(
ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: (context, _) => App(),
),
);
// to here.
}
The modification to the main
function makes the provider package responsible for instantiating the application state object using the ChangeNotifierProvider
widget. You are using this specific provider class because the application state object extends ChangeNotifier
and this enables the provider
package to know when to redisplay dependent widgets. Finally, integrate the application state with Authentication
by updating HomePage
's build
method:
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'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.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!',
),
],
),
);
}
}
You instantiate the Authentication
widget, and wrap it in a Consumer
widget. The Consumer widget the usual way that the provider
package can be used to rebuild part of the tree when the application state changes. The Authentication
widget is the authentication UI that you will now test.
Testing the Authentication flow
Here is the start of the authentication flow, where the user can tap on the RSVP button, to initiate the email form.
Upon entering the email, the system confirms if the user is already registered, in which case the user is prompted for a password, alternatively if the user isn't registered, then they go through the registration form.
Make sure to try out entering a short password (less than six characters) to check the error handling flow. If the user is registered, they will see the password for instead.
On this page make sure to enter incorrect passwords to check the error handling on this page. Finally, once the user is logged in, you will see the logged in experience which offers the user the ability to log out again.
And with that, you have implemented an authentication flow. Congrats!
6. Write messages to Cloud Firestore
Knowing that users are coming is great, but let's give the guests something else to do in the app. What if they could leave messages in a guestbook? They can share why they're excited to come or who they hope to meet.
To store the chat messages that users write in the app, you'll use Cloud Firestore .
Data model
Cloud Firestore is a NoSQL database, and data stored in the database is split into collections, documents, fields, and subcollections. You will store each message of the chat as a document in a top-level collection called guestbook
.
Add messages to Firestore
In this section, you'll add the functionality for users to write new messages to the database. First, you add the UI elements (form field and send button), and then you add the code that hooks these elements up to the database.
First, add imports for the cloud_firestore
package and 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';
To construct the UI elements of a message field and a send button, add a new stateful widget GuestBook
at the bottom of lib/main.dart
.
lib/main.dart
class GuestBook extends StatefulWidget {
const GuestBook({required this.addMessage});
final FutureOr<void> Function(String message) addMessage;
@override
_GuestBookState 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'),
],
),
),
],
),
),
);
}
}
There are a couple of points of interest here. First up, you are instantiating a Form so you can validate the message actually has some content, and show the user an error message if there isn't any. The way to validate a form involves accessing the form state behind the form, and for this you use a GlobalKey
. For more information on Keys, and how to use them, please see the Flutter Widgets 101 episode "When to Use Keys" .
Also note the way the widgets are laid out, you have a Row
, with a TextFormField
and a StyledButton
, which itself contains a Row
. Also note the TextFormField
is wrapped in an Expanded
widget, this forces the TextFormField
to take up any extra space in the row. To better understand why this is required, please read through Understanding constraints .
Now that you have a widget that enables the user to enter some text to add to the Guest Book, you need to get it on the screen. To do so, edit the body of HomePage
to add the following two lines at the bottom of the ListView
's children:
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)),
While this is enough to display the Widget, it isn't sufficient to do anything useful. You will update this code shortly to make it functional.
App preview
A user clicking the SEND button will trigger the code snippet below. It adds the contents of the message input field to the guestbook
collection of the database. Specifically, the addMessageToGuestBook
method adds the message content to a new document (with an automatically generated ID) to the guestbook
collection.
Note that FirebaseAuth.instance.currentUser.uid
is a reference to the auto-generated unique ID that Firebase Authentication gives for all logged-in users.
Make another change to the lib/main.dart
file. Add the addMessageToGuestBook
method. You will wire the user interface and this capability together in the next step.
lib/main.dart
class ApplicationState extends ChangeNotifier {
// Current content of ApplicationState elided ...
// Add from here
Future<DocumentReference> addMessageToGuestBook(String message) {
if (_loginState != ApplicationLoginState.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
}
Wiring the UI into the database
You have a UI where the user can enter the text they want to add to the Guest Book, and you have the code to add the entry to Cloud Firestore. Now all you need to do is wire the two together. In lib/main.dart
make the following change to the HomePage
widget.
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, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.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.loginState == ApplicationLoginState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// To here.
],
),
);
}
}
You have replaced the two lines you added back at the start of this step with the full implementation. You are again using Consumer<ApplicationState>
to make the application state available to the part of the tree you are rendering. This enables you to react to someone entering a message in the UI, and publish it into the database. In the next section you will test if the added messages are published into the database.
Test sending messages
- Make sure that you're signed in to the app.
- Enter a message such as "Hey there!", and then click SEND .
This action writes the message to your Cloud Firestore database. However, you won't yet see the message in your actual Flutter app because you still need to implement retrieving the data. You'll do that in the next step.
But you can see the newly added message in the Firebase console.
In the Firebase console, in the Database dashboard , you should see the guestbook
collection with your newly added message. If you keep sending messages, your guestbook collection will contain many documents, like this:
Firebase console
7. Read messages
It's lovely that guests can write messages to the database, but they can't see them in the app yet. Let's fix that!
Synchronize messages
To display messages, you'll need to add listeners that trigger when data changes and then create a UI element that shows new messages. You'll add code to the application state that listens for newly added messages from the app.
Just above the GuestBook
widget the following value class. This class exposes a structured view of the data you are storing in Cloud Firestore.
lib/main.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
In the section of ApplicationState
where you define state and getters, add the following new lines:
lib/main.dart
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
// Add from here
StreamSubscription<QuerySnapshot>? _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// to here.
And finally, in the initialization section of ApplicationState
, add the following to subscribe to a query over the document collection when a user logs in, and unsubscribe when they log out.
lib/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
// Add from here
_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();
});
// to here.
} else {
_loginState = ApplicationLoginState.loggedOut;
// Add from here
_guestBookMessages = [];
_guestBookSubscription?.cancel();
// to here.
}
notifyListeners();
});
}
This section is important, as here is where you construct a query over the guestbook
collection, and handle subscribing and unsubscribing to this collection. You listen to the stream, where you reconstruct a local cache of the messages in the guestbook
collection, and also store a reference to this subscription so you can unsubscribe from it later. There is a lot going on here, and it is worth spending some time in a debugger inspecting what happens when to get a clearer mental model.
For more information, see the Cloud Firestore documentation .
In the GuestBook
widget you need to connect this changing state to the user interface. You modify the widget by adding a list of messages as part of its configuration.
lib/main.dart
class GuestBook extends StatefulWidget {
// Modify the following line
const GuestBook({required this.addMessage, required this.messages});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
_GuestBookState createState() => _GuestBookState();
}
Next, we expose this new configuration in _GuestBookState
by modifying the build
method as follows.
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.
);
}
}
You wrap the previous content of the build method with a Column
widget, and then at the tail of the Column
's children you add a collection for to generate a new Paragraph
for each message in the list of messages.
Finally, you now need to update the body of HomePage
to correctly construct GuestBook
with the new messages
parameter.
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
),
],
],
),
),
Test synchronizing messages
Cloud Firestore automatically and instantly synchronizes data with clients subscribed to the database.
- The messages that you created earlier in the database should be displayed in the app. Feel free to write new messages; they should appear instantly.
- If you open your workspace in multiple windows or tabs, messages will sync in real time across tabs.
- (Optional) You can try manually deleting, modifying, or adding new messages directly in the Database section of the Firebase console; any changes should appear in the UI.
Congratulations! You are reading Cloud Firestore documents in your app!
App p review
8. Set up basic security rules
You initially set up Cloud Firestore to use test mode, meaning that your database is open for reads and writes. However, you should only use test mode during very early stages of development. As a best practice, you should set up security rules for your database as you develop your app. Security should be integral to your app's structure and behavior.
Security Rules allow you to control access to documents and collections in your database. The flexible rules syntax allows you to create rules that match anything from all writes to the entire database to operations on a specific document.
You can write security rules for Cloud Firestore in the Firebase console:
- In the Firebase console's Develop section, click Database , and then select the Rules tab (or click here to go directly to the Rules tab).
- You should see the following default security rules, along with a warning about the rules being public.
Identify collections
First, identify the collections to which the app writes data.
In match /databases/{database}/documents
, identify the collection that you want to secure:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
// You'll add rules here in the next step.
}
}
Add security rules
Because you used the Authentication UID as a field in each guestbook document, you can get the Authentication UID and verify that anyone attempting to write to the document has a matching Authentication UID.
Add the read and write rules to your rule set as shown below:
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;
}
}
}
Now, for the guestbook, only signed-in users can read messages (any message!), but only a message's author can edit a message.
Add validation rules
Add data validation to make sure that all of the expected fields are present in the document:
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. Bonus step: Practice what you've learned
Record an attendee's RSVP status
Right now, your app just allows people to start chatting if they're interested in the event. Also, the only way you know if someone's coming is if they post it in the chat. Let's get organized and let people know how many people are coming.
You are going to add a couple of new capabilities to the application state. The first is the ability for a logged in user to nominate if they are attending or not. The second capability is a counter of how many people are actually attending.
In lib/main.dart
, add the following to the accessors section to enable the UI code to interact with this state:
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});
}
}
Update ApplicationState
's init
method as follows:
lib/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// 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();
});
}
The above adds an always subscribed query to find out the number of attendees, and a second query that is only active while a user is logged in to find out if the user is attending. Next, add the following enumeration after the GuestBookMessage
declaration:
lib/main.dart
enum Attending { yes, no, unknown }
You are now going to define a new widget that acts like radio buttons of old. It starts off in an indeterminate state, with neither yes nor no selected, but once the user selects whether they are attending or not, then you show that option highlighted with a filled button, and the other option receding with a flat rendering.
lib/main.dart
class YesNoSelection extends StatelessWidget {
const YesNoSelection({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'),
),
],
),
);
}
}
}
Next, you need to update HomePage
's build method to take advantage of YesNoSelection
, enabling a logged in user to nominate if they are attending. You will also display the number of attendees for this event.
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.loginState == ApplicationLoginState.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,
),
],
],
),
),
Add rules
Because you already have some rules set up, the new data that you're adding with the buttons is going to be rejected. You'll need to update the rules to allow adding to the attendees
collection.
For the attendees
collection, since you used the Authentication UID as the document name, you can grab it and verify that the submitter's uid
is the same as the document they are writing. You'll allow everyone to read the attendees list (since there is no private data there), but only the creator should be able to update it.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId;
}
}
}
Add validation rules
Add data validation to make sure that all of the expected fields are present in the document:
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;
}
}
}
(Optional) You can now view the results of clicking the buttons. Go to your Cloud Firestore dashboard in the Firebase console.
App preview
10. Congratulations!
You've used Firebase to build an interactive, real-time web application!
What we've covered
- Firebase Authentication
- Cloud Firestore
- Firebase Security Rules
Sonraki adımlar
- Want to learn more about other Firebase products? Maybe you want to store image files that users upload? Or send notifications to your users? Check out the Firebase documentation . Want to learn more about Flutter plugins for Firebase? Check out FlutterFire for more information.
- Want to learn more about Cloud Firestore? Maybe you want to learn about subcollections and transactions? Head over to the Cloud Firestore web codelab for a codelab that goes into more depth on Cloud Firestore. Or check out this YouTube series to get to know Cloud Firestore !
Learn more
- Firebase site: firebase.google.com
- Flutter site: flutter.dev
- FlutterFire Firebase Flutter widgets: firebase.flutter.dev
- Firebase YouTube channel
- Flutter YouTube channel
How did it go?
We would love your feedback! Please fill out a (very) short form here .