1. Zanim zaczniesz
W tym module dowiesz się, jak korzystać z Firebase do tworzenia aplikacji mobilnych Flutter na Androida i iOS.
Wymagania wstępne
- znajomość Fluttera,
- Pakiet SDK Flutter
- dowolny edytor tekstu,
Czego się nauczysz
- Jak utworzyć aplikację do obsługi czatu z potwierdzeniem uczestnictwa w wydarzeniu i księgą gości na Androida, iOS, w internecie i na macOS za pomocą Fluttera.
- Jak uwierzytelniać użytkowników za pomocą Uwierzytelniania Firebase i synchronizować dane z Firestore.
Czego potrzebujesz
dowolne z tych urządzeń:
- fizyczne urządzenie z Androidem lub iOS podłączone do komputera i ustawione w trybie deweloperskim;
- Symulator iOS (wymaga narzędzi Xcode).
- Emulator Androida (wymaga konfiguracji w Android Studio).
Potrzebujesz też:
- wybraną przeglądarkę, np. Google Chrome;
- Wybrane IDE lub edytor tekstu skonfigurowany za pomocą wtyczek Dart i Flutter, np. Android Studio lub Visual Studio Code.
- Najnowsza wersja
stable
Fluttera lubbeta
, jeśli lubisz nowości. - Konto Google do tworzenia projektu Firebase i zarządzania nim.
Firebase
CLI zalogował się na Twoje konto Google.
2. Pobieranie przykładowego kodu
Pobierz początkową wersję projektu z GitHuba:
- W wierszu poleceń sklonuj repozytorium GitHub w katalogu
flutter-codelabs
:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Katalog flutter-codelabs
zawiera kod kolekcji codelabów. Kod do tego laboratorium znajduje się w katalogu flutter-codelabs/firebase-get-to-know-flutter
. Katalog zawiera serię zrzutów ekranu pokazujących, jak projekt powinien wyglądać na końcu każdego kroku. Na przykład jesteś na drugim etapie.
- Znajdź pasujące pliki w drugim kroku:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
Jeśli chcesz przejść do przodu lub zobaczyć, jak coś powinno wyglądać po wykonaniu danego kroku, zajrzyj do katalogu o nazwie odpowiadającej temu krokowi.
Importowanie aplikacji startowej
- Otwórz lub zaimportuj katalog
flutter-codelabs/firebase-get-to-know-flutter/step_02
w wybranym środowisku IDE. Ten katalog zawiera kod początkowy do laboratorium, który składa się z nie w pełni funkcjonalnej aplikacji Flutter do spotkań.
Znajdowanie plików, które wymagają pracy
Kod w tej aplikacji jest rozproszony w wielu katalogach. Podział funkcji ułatwia pracę, ponieważ grupuje kod według funkcjonalności.
- Znajdź te pliki:
lib/main.dart
: ten plik zawiera główny punkt wejścia i widżet aplikacji.lib/home_page.dart
: Ten plik zawiera widżet strony głównej.lib/src/widgets.dart
: ten plik zawiera kilka widżetów, które pomagają ujednolicić styl aplikacji. Tworzą one ekran aplikacji startowej.lib/src/authentication.dart
: ten plik zawiera częściową implementację uwierzytelniania z zestawem widżetów do tworzenia środowiska logowania użytkowników na potrzeby uwierzytelniania Firebase opartego na adresie e-mail. Te widżety do procesu uwierzytelniania nie są jeszcze używane w aplikacji startowej, ale wkrótce je dodasz.
W razie potrzeby dodaj dodatkowe pliki, aby utworzyć pozostałą część aplikacji.
Sprawdź plik lib/main.dart
Ta aplikacja korzysta z pakietu google_fonts
, aby ustawić Roboto jako domyślną czcionkę w całej aplikacji. Możesz przeglądać fonts.google.com i używać znalezionych tam czcionek w różnych częściach aplikacji.
W formularzu używasz widżetów pomocniczych z pliku lib/src/widgets.dart
w postaci Header
, Paragraph
i IconAndDetail
. Te widżety eliminują zduplikowany kod, aby zmniejszyć bałagan w układzie strony opisanym w HomePage
. Zapewnia to również spójny wygląd i działanie.
Oto jak wygląda Twoja aplikacja na Androidzie, iOS, w internecie i na macOS:
3. Tworzenie i konfigurowanie projektu Firebase
Wyświetlanie informacji o wydarzeniu jest przydatne dla gości, ale samo w sobie nie jest zbyt użyteczne. Musisz dodać do aplikacji funkcje dynamiczne. Aby to zrobić, połącz Firebase z aplikacją. Aby zacząć korzystać z Firebase, musisz utworzyć i skonfigurować projekt Firebase.
Tworzenie projektu Firebase
- Zaloguj się w konsoli Firebase, korzystając ze swojego konta Google.
- Kliknij przycisk, aby utworzyć nowy projekt, a potem wpisz jego nazwę (np.
Firebase-Flutter-Codelab
).
- Kliknij Dalej.
- Po wyświetleniu monitu przeczytaj i zaakceptuj warunki usługi Firebase, a potem kliknij Dalej.
- (Opcjonalnie) Włącz w konsoli Firebase pomoc AI (nazywaną „Gemini w Firebase”).
- W tym samouczku nie potrzebujesz Google Analytics, więc wyłącz opcję Google Analytics.
- Kliknij Utwórz projekt, poczekaj, aż projekt zostanie udostępniony, a następnie kliknij Dalej.
Więcej informacji o projektach Firebase znajdziesz w artykule Projekty w Firebase.
Konfigurowanie usług Firebase
Aplikacja korzysta z tych usług Firebase, które są dostępne w przypadku aplikacji internetowych:
- Uwierzytelnianie: umożliwia użytkownikom logowanie się w aplikacji.
- Firestore: zapisuje dane strukturalne w chmurze i wysyła natychmiastowe powiadomienia o zmianach danych.
- Reguły zabezpieczeń Firebase: zabezpieczają bazę danych.
Niektóre z tych usług wymagają specjalnej konfiguracji lub włączenia w konsoli Firebase.
Włącz uwierzytelnianie logowania za pomocą adresu e-mail
- W panelu Omówienie projektu w konsoli Firebase rozwiń menu Kompilacja.
- Kliknij Uwierzytelnianie > Rozpocznij > Metoda logowania > E-mail/hasło > Włącz > Zapisz.
Konfigurowanie Firestore
Aplikacja internetowa używa Firestore do zapisywania i odbierania nowych wiadomości na czacie.
Aby skonfigurować Firestore w projekcie Firebase:
- W panelu po lewej stronie konsoli Firebase rozwiń Kompilacja, a następnie wybierz Baza danych Firestore.
- Kliknij Utwórz bazę danych.
- W polu Identyfikator bazy danych pozostaw wartość
(default)
. - Wybierz lokalizację bazy danych i kliknij Dalej.
W przypadku prawdziwej aplikacji wybierz lokalizację, która jest blisko użytkowników. - Kliknij Uruchom w trybie testowym. Przeczytaj wyłączenie odpowiedzialności dotyczące reguł bezpieczeństwa.
W dalszej części tego laboratorium dodasz reguły bezpieczeństwa, aby zabezpieczyć swoje dane. Nierozpowszechniajani nie udostępniaj publicznie aplikacji bez dodania reguł bezpieczeństwa do bazy danych. - Kliknij Utwórz.
4. Konfigurowanie Firebase
Aby używać Firebase z Flutterem, musisz wykonać te czynności, aby skonfigurować projekt Fluttera do prawidłowego korzystania z bibliotek FlutterFire
:
- Dodaj do projektu zależności
FlutterFire
. - Zarejestruj wybraną platformę w projekcie Firebase.
- Pobierz plik konfiguracyjny dla danej platformy, a następnie dodaj go do kodu.
W katalogu najwyższego poziomu aplikacji Flutter znajdują się podkatalogi android
, ios
, macos
i web
, które zawierają pliki konfiguracyjne specyficzne dla platformy iOS i Android.
Konfigurowanie zależności
Musisz dodać biblioteki FlutterFire
dla 2 usług Firebase, których używasz w tej aplikacji: Uwierzytelnianie i Firestore.
- W wierszu poleceń dodaj te zależności:
$ flutter pub add firebase_core
firebase_core
package to wspólny kod wymagany w przypadku wszystkich wtyczek Firebase Flutter.
$ flutter pub add firebase_auth
firebase_auth
Pakiet umożliwia integrację z usługą Authentication.
$ flutter pub add cloud_firestore
cloud_firestore
Pakiet umożliwia dostęp do pamięci danych Firestore.
$ flutter pub add provider
firebase_ui_auth
Pakiet zawiera zestaw widżetów i narzędzi, które przyspieszają pracę programistów dzięki przepływom uwierzytelniania.
$ flutter pub add firebase_ui_auth
Wymagane pakiety zostały dodane, ale musisz też skonfigurować projekty wykonawcze na iOS, Androida, macOS i w internecie, aby prawidłowo korzystać z Firebase. Używasz też provider
pakietu, który umożliwia oddzielenie logiki biznesowej od logiki wyświetlania.
Instalowanie interfejsu wiersza poleceń FlutterFire
Wiersz poleceń FlutterFire zależy od wiersza poleceń Firebase.
- Jeśli jeszcze tego nie zrobiono, zainstaluj na komputerze interfejs wiersza poleceń Firebase.
- Zainstaluj interfejs wiersza poleceń FlutterFire:
$ dart pub global activate flutterfire_cli
Po zainstalowaniu polecenie flutterfire
jest dostępne globalnie.
Konfigurowanie aplikacji
Interfejs CLI wyodrębnia informacje z projektu Firebase i wybranych aplikacji w projekcie, aby wygenerować całą konfigurację dla konkretnej platformy.
W głównym katalogu aplikacji uruchom polecenie configure
:
$ flutterfire configure
Polecenie konfiguracji przeprowadzi Cię przez te procesy:
- Wybierz projekt Firebase na podstawie pliku
.firebaserc
lub w konsoli Firebase. - Określ platformy do konfiguracji, np. Android, iOS, macOS i internet.
- Określ aplikacje Firebase, z których chcesz wyodrębnić konfigurację. Domyślnie interfejs CLI próbuje automatycznie dopasować aplikacje Firebase na podstawie bieżącej konfiguracji projektu.
- Wygeneruj plik
firebase_options.dart
w projekcie.
Konfigurowanie systemu macOS
Flutter w systemie macOS tworzy aplikacje w pełni odizolowane od innych procesów. Ponieważ ta aplikacja jest zintegrowana z siecią, aby komunikować się z serwerami Firebase, musisz skonfigurować ją z uprawnieniami klienta sieci.
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>
Więcej informacji znajdziesz w artykule Obsługa Fluttera na komputerach.
5. Dodawanie funkcji potwierdzania udziału
Po dodaniu Firebase do aplikacji możesz utworzyć przycisk RSVP, który rejestruje osoby za pomocą uwierzytelniania. W przypadku Androida, iOS i internetu dostępne są gotowe pakiety FirebaseUI Auth
, ale w przypadku Fluttera musisz samodzielnie utworzyć tę funkcję.
Pobrany wcześniej projekt zawierał zestaw widżetów, które implementują interfejs użytkownika dla większości procesu uwierzytelniania. Implementujesz logikę biznesową, aby zintegrować uwierzytelnianie z aplikacją.
Dodawanie logiki biznesowej za pomocą pakietu Provider
Użyj provider
pakietu, aby udostępnić scentralizowany obiekt stanu aplikacji w całym drzewie widżetów Flattera:
- Utwórz nowy plik o nazwie
app_state.dart
z tą zawartością:
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();
});
}
}
Instrukcje import
wprowadzają Firebase Core i Auth, pobierają pakiet provider
, który udostępnia obiekt stanu aplikacji w całym drzewie widżetów, i zawierają widżety uwierzytelniania z pakietu firebase_ui_auth
.
Ten obiekt stanu aplikacji ApplicationState
ma na tym etapie jedno główne zadanie: powiadomić drzewo widżetów o aktualizacji stanu uwierzytelnienia.
Dostawca służy tylko do przekazywania aplikacji stanu logowania użytkownika. Aby umożliwić użytkownikowi zalogowanie się, użyj interfejsów dostarczonych przez pakiet firebase_ui_auth
. To świetny sposób na szybkie uruchomienie ekranów logowania w aplikacjach.
Integrowanie procesu uwierzytelniania
- Zmodyfikuj instrukcje importu u góry pliku
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';
- Połącz stan aplikacji z jej inicjalizacją, a następnie dodaj proces uwierzytelniania do funkcji
HomePage
:
lib/main.dart
void main() {
// Modify from here...
WidgetsFlutterBinding.ensureInitialized();
runApp(ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: ((context, child) => const App()),
));
// ...to here.
}
Zmiana funkcji main()
sprawia, że pakiet dostawcy jest odpowiedzialny za utworzenie instancji obiektu stanu aplikacji za pomocą widżetu ChangeNotifierProvider
. Używasz tej konkretnej klasy provider
, ponieważ obiekt stanu aplikacji rozszerza klasę ChangeNotifier
, co pozwala pakietowi provider
wiedzieć, kiedy ponownie wyświetlić zależne widżety.
- Zaktualizuj aplikację, aby obsługiwała nawigację do różnych ekranów udostępnianych przez FirebaseUI, tworząc
GoRouter
konfigurację:
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
);
}
}
Z każdym ekranem jest powiązany inny typ działania, który zależy od nowego stanu procesu uwierzytelniania. Po większości zmian stanu w procesie uwierzytelniania możesz wrócić do preferowanego ekranu, np. ekranu głównego lub profilu.
- W metodzie kompilacji klasy
HomePage
zintegruj stan aplikacji z widżetemAuthFunc
:
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!',
),
],
),
);
}
}
Tworzysz instancję widżetu AuthFunc
i zawijasz ją w widżet Consumer
. Widżet Consumer to zwykły sposób używania pakietu provider
do przebudowywania części drzewa, gdy zmienia się stan aplikacji. AuthFunc
to dodatkowe widżety, które testujesz.
Testowanie procesu uwierzytelniania
- W aplikacji kliknij przycisk Odpowiedz, aby rozpocząć
SignInScreen
.
- Wpisz adres e-mail. Jeśli jesteś już zarejestrowanym użytkownikiem, system poprosi Cię o podanie hasła. W przeciwnym razie system poprosi Cię o wypełnienie formularza rejestracyjnego.
- Wpisz hasło krótsze niż 6 znaków, aby sprawdzić proces obsługi błędów. Jeśli jesteś zarejestrowany(-a), zobaczysz hasło do konta.
- Wpisz nieprawidłowe hasła, aby sprawdzić proces obsługi błędów.
- Wpisz prawidłowe hasło. Widzisz interfejs po zalogowaniu, który umożliwia użytkownikowi wylogowanie się.
6. Zapisywanie wiadomości w Firestore
Dobrze wiedzieć, że użytkownicy przychodzą, ale musisz dać im coś jeszcze do zrobienia w aplikacji. Co by było, gdyby mogli zostawiać wiadomości w księdze gości? Mogą napisać, dlaczego chcą wziąć udział w wydarzeniu lub kogo chcą spotkać.
Do przechowywania wiadomości na czacie, które użytkownicy piszą w aplikacji, używasz Firestore.
Model danych
Firestore to baza danych NoSQL, a dane w niej przechowywane są podzielone na kolekcje, dokumenty, pola i podkolekcje. Każda wiadomość z czatu jest przechowywana jako dokument w kolekcji guestbook
, która jest kolekcją najwyższego poziomu.
Dodawanie wiadomości do Firestore
W tej sekcji dodasz funkcję umożliwiającą użytkownikom zapisywanie wiadomości w bazie danych. Najpierw dodajesz pole formularza i przycisk wysyłania, a potem kod, który łączy te elementy z bazą danych.
- Utwórz nowy plik o nazwie
guest_book.dart
i dodaj do niego widżet stanuGuestBook
, aby utworzyć elementy interfejsu pola wiadomości i przycisku wysyłania:
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'),
],
),
),
],
),
),
);
}
}
Warto zwrócić uwagę na kilka kwestii. Najpierw tworzysz instancję formularza, aby sprawdzić, czy wiadomość zawiera treść, i w razie potrzeby wyświetlić użytkownikowi komunikat o błędzie. Aby sprawdzić poprawność formularza, uzyskaj dostęp do stanu formularza za pomocą GlobalKey
. Więcej informacji o kluczach i sposobie ich używania znajdziesz w artykule Kiedy używać kluczy.
Zwróć też uwagę na układ widżetów. Masz element Row
z elementami TextFormField
i StyledButton
, który zawiera element Row
. Zwróć też uwagę, że element TextFormField
jest zawarty w widżecie Expanded
, który wymusza wypełnienie przez element TextFormField
dodatkowego miejsca w wierszu. Aby lepiej zrozumieć, dlaczego jest to wymagane, zapoznaj się z artykułem Omówienie ograniczeń.
Teraz, gdy masz widżet, który umożliwia użytkownikowi wpisanie tekstu do dodania do księgi gości, musisz wyświetlić go na ekranie.
- Edytuj treść elementu
HomePage
, aby dodać na końcu elementów podrzędnych elementuListView
te 2 wiersze:
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)),
Wystarczy to do wyświetlenia widżetu, ale nie do niczego przydatnego. Wkrótce zaktualizujesz ten kod, aby działał.
Podgląd aplikacji
Gdy użytkownik kliknie WYŚLIJ, zostanie uruchomiony ten fragment kodu. Spowoduje to dodanie zawartości pola wprowadzania wiadomości do kolekcji guestbook
w bazie danych. W szczególności metoda addMessageToGuestBook
dodaje treść wiadomości do nowego dokumentu z automatycznie wygenerowanym identyfikatorem w kolekcji guestbook
.
Pamiętaj, że FirebaseAuth.instance.currentUser.uid
to odniesienie do automatycznie generowanego unikalnego identyfikatora, który uwierzytelnianie nadaje wszystkim zalogowanym użytkownikom.
- W pliku
lib/app_state.dart
dodaj metodęaddMessageToGuestBook
. W następnym kroku połączysz tę funkcję z interfejsem.
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.
}
Łączenie interfejsu i bazy danych
Masz interfejs, w którym użytkownik może wpisać tekst, który chce dodać do księgi gości, oraz kod umożliwiający dodanie wpisu do Firestore. Teraz wystarczy je połączyć.
- W pliku
lib/home_page.dart
wprowadź w widżecieHomePage
tę zmianę:
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.
],
),
);
}
}
Zastąpiliśmy 2 wiersze dodane na początku tego kroku pełną implementacją. Ponownie używasz Consumer<ApplicationState>
, aby udostępnić stan aplikacji części drzewa, którą renderujesz. Dzięki temu możesz reagować na osoby, które wpisują wiadomości w interfejsie, i publikować je w bazie danych. W następnej sekcji sprawdzisz, czy dodane wiadomości są publikowane w bazie danych.
Testowanie wysyłania wiadomości
- W razie potrzeby zaloguj się w aplikacji.
- Wpisz wiadomość, np.
Hey there!
, a następnie kliknij WYŚLIJ.
Ta czynność zapisuje wiadomość w bazie danych Firestore. Nie widzisz jednak tego komunikatu w rzeczywistej aplikacji Flutter, ponieważ musisz jeszcze wdrożyć pobieranie danych, co zrobisz w następnym kroku. W panelu Baza danych w konsoli Firebase możesz jednak zobaczyć dodaną wiadomość w kolekcji guestbook
. Jeśli wyślesz więcej wiadomości, dodasz więcej dokumentów do swojej kolekcji guestbook
. Na przykład zobacz ten fragment kodu:
7. Czytanie wiadomości
To wspaniale, że goście mogą pisać wiadomości do bazy danych, ale nie mogą ich jeszcze zobaczyć w aplikacji. Czas to naprawić.
Synchronizowanie wiadomości
Aby wyświetlać wiadomości, musisz dodać odbiorniki, które będą się uruchamiać, gdy dane się zmienią, a potem utworzyć element interfejsu, który będzie wyświetlać nowe wiadomości. Dodaj do stanu aplikacji kod, który będzie nasłuchiwać nowo dodanych wiadomości z aplikacji.
- Utwórz nowy plik
guest_book_message.dart
i dodaj do niego tę klasę, aby udostępnić uporządkowany widok danych przechowywanych w Firestore.
lib/guest_book_message.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
- W pliku
lib/app_state.dart
dodaj te instrukcje importu:
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
- W sekcji
ApplicationState
, w której definiujesz stan i gettery, dodaj te wiersze:
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.
- W sekcji inicjowania
ApplicationState
dodaj te wiersze, aby zasubskrybować zapytanie dotyczące kolekcji dokumentów, gdy użytkownik się zaloguje, i anulować subskrypcję, gdy się wyloguje:
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();
});
}
Ta sekcja jest ważna, ponieważ to w niej tworzysz zapytanie dotyczące kolekcji guestbook
oraz obsługujesz subskrypcję i anulowanie subskrypcji tej kolekcji. Odsłuchujesz strumień, w którym odtwarzasz lokalną pamięć podręczną wiadomości w kolekcji guestbook
, a także przechowujesz odniesienie do tej subskrypcji, aby później móc z niej zrezygnować. Dzieje się tu dużo, więc warto to sprawdzić w debuggerze, aby lepiej zrozumieć, co się dzieje. Więcej informacji znajdziesz w artykule Otrzymywanie aktualizacji w czasie rzeczywistym za pomocą Firestore.
- W pliku
lib/guest_book.dart
dodaj ten import:
import 'guest_book_message.dart';
- W widżecie
GuestBook
dodaj listę wiadomości w ramach konfiguracji, aby połączyć ten zmieniający się stan z interfejsem użytkownika:
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();
}
- W
_GuestBookState
zmodyfikuj metodębuild
w ten sposób, aby udostępnić tę konfigurację:
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.
);
}
}
Poprzednią zawartość metody build()
umieszczasz w widżecie Column
, a następnie dodajesz kolekcję dla na końcu elementów podrzędnych widżetu Column
, aby wygenerować nowy widżet Paragraph
dla każdej wiadomości na liście wiadomości.
- Zaktualizuj treść funkcji
HomePage
, aby prawidłowo utworzyć funkcjęGuestBook
z nowym parametremmessages
:
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
),
],
],
),
),
Testowanie synchronizacji wiadomości
Firestore automatycznie i natychmiast synchronizuje dane z klientami, którzy subskrybują bazę danych.
Synchronizacja wiadomości testowej:
- W aplikacji znajdź wiadomości utworzone wcześniej w bazie danych.
- pisać nowe wiadomości, Pojawiają się natychmiast.
- Otwórz obszar roboczy w kilku oknach lub kartach. Wiadomości są synchronizowane w czasie rzeczywistym między oknami i kartami.
- Opcjonalnie: w menu Baza danych w konsoli Firebase ręcznie usuń, zmodyfikuj lub dodaj nowe wiadomości. Wszystkie zmiany pojawią się w interfejsie.
Gratulacje! Odczytujesz dokumenty Firestore w aplikacji.
Podgląd aplikacji
8. Konfigurowanie podstawowych reguł zabezpieczeń
Początkowo skonfigurowano Firestore do korzystania z trybu testowego, co oznacza, że baza danych jest otwarta na odczyty i zapisy. Tryb testowy należy jednak stosować tylko na wczesnych etapach tworzenia. Najlepiej jest skonfigurować reguły zabezpieczeń bazy danych podczas tworzenia aplikacji. Bezpieczeństwo jest integralną częścią struktury i działania aplikacji.
Reguły zabezpieczeń Firebase umożliwiają kontrolowanie dostępu do dokumentów i kolekcji w bazie danych. Elastyczna składnia reguł umożliwia tworzenie reguł, które pasują do dowolnych operacji, od wszystkich zapisów w całej bazie danych po operacje na konkretnym dokumencie.
Skonfiguruj podstawowe reguły zabezpieczeń:
- W menu Programowanie w konsoli Firebase kliknij Baza danych > Reguły. Powinny się wyświetlić te domyślne reguły zabezpieczeń i ostrzeżenie o tym, że są one publiczne:
- Określ kolekcje, w których aplikacja zapisuje dane:
W sekcji match /databases/{database}/documents
znajdź kolekcję, którą chcesz zabezpieczyć:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
// You'll add rules here in the next step.
}
}
Ponieważ w każdym dokumencie księgi gości używasz identyfikatora UID uwierzytelniania jako pola, możesz pobrać ten identyfikator i sprawdzić, czy każda osoba, która próbuje zapisać dane w dokumencie, ma pasujący identyfikator UID uwierzytelniania.
- Dodaj do zestawu reguł reguły odczytu i zapisu:
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;
}
}
}
Teraz tylko zalogowani użytkownicy mogą czytać wiadomości w księdze gości, ale tylko autor wiadomości może ją edytować.
- Dodaj weryfikację danych, aby mieć pewność, że w dokumencie znajdują się wszystkie oczekiwane pola:
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. Krok dodatkowy: przećwicz zdobytą wiedzę
Zapisywanie stanu odpowiedzi uczestnika
Obecnie Twoja aplikacja umożliwia czatowanie tylko wtedy, gdy użytkownicy są zainteresowani wydarzeniem. Jedynym sposobem, aby dowiedzieć się, czy ktoś przyjdzie, jest informacja w czacie.
Na tym etapie organizujesz spotkanie i informujesz innych, ile osób weźmie w nim udział. Dodajesz do stanu aplikacji kilka funkcji. Pierwsza to możliwość wskazania przez zalogowanego użytkownika, czy weźmie udział w wydarzeniu. Drugi to licznik osób, które wezmą udział w wydarzeniu.
- W pliku
lib/app_state.dart
dodaj te wiersze do sekcji akcesorów wApplicationState
, aby kod interfejsu mógł wchodzić w interakcje z tym stanem:
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});
}
}
- Zaktualizuj metodę
init()
ApplicationState
w ten sposób:
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();
});
}
Ten kod dodaje zapytanie, które jest zawsze subskrybowane, aby określić liczbę uczestników, oraz drugie zapytanie, które jest aktywne tylko wtedy, gdy użytkownik jest zalogowany, aby określić, czy uczestniczy w wydarzeniu.
- U góry pliku
lib/app_state.dart
dodaj to wyliczenie.
lib/app_state.dart
enum Attending { yes, no, unknown }
- Utwórz nowy plik
yes_no_selection.dart
i zdefiniuj nowy widżet, który działa jak przyciski opcji:
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'),
),
],
),
);
}
}
}
Zaczyna się w stanie nieokreślonym, w którym nie jest zaznaczona ani opcja Tak, ani Nie. Gdy użytkownik wybierze, czy weźmie udział w wydarzeniu, wyświetl tę opcję z wypełnionym przyciskiem, a drugą opcję z płaskim renderowaniem.
- Zaktualizuj metodę
HomePage
build()
, aby korzystać zYesNoSelection
, umożliwić zalogowanemu użytkownikowi określenie, czy weźmie udział w wydarzeniu, i wyświetlić liczbę uczestników wydarzenia:
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,
),
],
],
),
),
Dodaj reguły
Masz już skonfigurowane reguły, więc dane dodane za pomocą przycisków zostaną odrzucone. Musisz zaktualizować reguły, aby zezwolić na dodawanie elementów do kolekcji attendees
.
- W kolekcji
attendees
pobierz identyfikator UID uwierzytelniania, którego używasz jako nazwy dokumentu, i sprawdź, czyuid
osoby przesyłającej jest taki sam jak dokument, który pisze:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId;
}
}
}
Dzięki temu każdy może odczytać listę uczestników, ponieważ nie zawiera ona danych prywatnych, ale tylko twórca może ją aktualizować.
- Dodaj weryfikację danych, aby mieć pewność, że w dokumencie znajdują się wszystkie oczekiwane pola:
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;
}
}
}
- Opcjonalnie: w aplikacji kliknij przyciski, aby wyświetlić wyniki w panelu Firestore w konsoli Firebase.
Podgląd aplikacji
10. Gratulacje!
Udało Ci się utworzyć interaktywną aplikację internetową działającą w czasie rzeczywistym za pomocą Firebase.