1. Zanim zaczniesz
W tym laboratorium kodowania nauczysz się podstaw Firebase , aby tworzyć aplikacje mobilne Flutter na Androida i iOS.
Wymagania wstępne
- Znajomość Fluttera
- SDK Fluttera
- Wybrany edytor tekstu
Czego się nauczysz
- Jak zbudować aplikację do czatu RSVP i księgi gości na Androida, iOS, Internet i macOS za pomocą Flutter.
- Jak uwierzytelniać użytkowników za pomocą uwierzytelniania Firebase i synchronizować dane z Firestore.
Co będziesz potrzebował
Dowolne z następujących urządzeń:
- Fizyczne urządzenie z systemem Android lub iOS podłączone do komputera i ustawione w trybie programisty.
- Symulator systemu iOS (wymaga narzędzi Xcode ).
- Emulator Androida (wymaga konfiguracji w Android Studio ).
Potrzebujesz również:
- Wybrana przeglądarka, na przykład Google Chrome.
- Wybrane środowisko IDE lub edytor tekstu skonfigurowany z wtyczkami Dart i Flutter, na przykład Android Studio lub Visual Studio Code .
- Najnowsza
stable
wersja Fluttera lubbeta
, jeśli lubisz życie na krawędzi. - Konto Google do tworzenia projektu Firebase i zarządzania nim.
-
Firebase
CLI zalogował się na Twoje konto Google.
2. Pobierz przykładowy kod
Pobierz wstępną wersję swojego projektu z GitHub:
- Z wiersza poleceń sklonuj repozytorium GitHub w katalogu
flutter-codelabs
:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Katalog flutter-codelabs
zawiera kod kolekcji codelabs. Kod tego laboratorium kodów znajduje się w katalogu flutter-codelabs/firebase-get-to-know-flutter
. Katalog zawiera serię migawek, które pokazują, jak projekt powinien wyglądać na końcu każdego kroku. Na przykład jesteś na drugim etapie.
- Znajdź pliki pasujące do drugiego kroku:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
Jeśli chcesz przeskoczyć do przodu lub zobaczyć jak coś powinno wyglądać po kroku, zajrzyj do katalogu o nazwie odpowiadającej krokowi, który Cię interesuje.
Zaimportuj aplikację startową
- Otwórz lub zaimportuj
flutter-codelabs/firebase-get-to-know-flutter/step_02
w preferowanym środowisku IDE. Ten katalog zawiera kod startowy dla codelab, który składa się z niedziałającej jeszcze aplikacji Flutter meetup.
Znajdź pliki, które wymagają pracy
Kod w tej aplikacji jest rozłożony na wiele katalogów. Ten podział funkcjonalności ułatwia pracę, ponieważ grupuje kod według funkcjonalności.
- Zlokalizuj następujące pliki:
-
lib/main.dart
: Ten plik zawiera główny punkt wejścia i widżet aplikacji. -
lib/src/widgets.dart
: Ten plik zawiera kilka widżetów, które pomagają ujednolicić styl aplikacji. Tworzą ekran aplikacji startowej. -
lib/src/authentication.dart
: Ten plik zawiera częściową implementację uwierzytelniania z zestawem widżetów służących do tworzenia środowiska logowania użytkownika do uwierzytelniania opartego na e-mailach Firebase. Te widżety przepływu uwierzytelniania nie są jeszcze używane w aplikacji startowej, ale wkrótce je dodasz.
-
W razie potrzeby dodajesz dodatkowe pliki, aby skompilować resztę aplikacji.
Przejrzyj plik lib/main.dart
Ta aplikacja korzysta z pakietu google_fonts
, aby Roboto była 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.
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
. Umożliwia to również spójny wygląd i styl.
Oto jak wygląda Twoja aplikacja na Androidzie, iOS, w Internecie i macOS:
3. Utwórz i skonfiguruj projekt Firebase
Wyświetlanie informacji o wydarzeniu jest świetne dla Twoich gości, ale samo w sobie nie jest zbyt przydatne dla nikogo. Musisz dodać dynamiczną funkcjonalność do aplikacji. Aby to zrobić, musisz połączyć Firebase ze swoją aplikacją. Aby rozpocząć korzystanie z Firebase, musisz utworzyć i skonfigurować projekt Firebase.
Utwórz projekt Firebase
- Zaloguj się do Firebase .
- W konsoli kliknij Dodaj projekt lub Utwórz projekt .
- W polu Nazwa projektu wpisz Firebase-Flutter-Codelab , a następnie kliknij Kontynuuj .
- Kliknij opcje tworzenia projektu. Jeśli pojawi się monit, zaakceptuj warunki Firebase, ale pomiń konfigurację Google Analytics, ponieważ nie będziesz go używać w tej aplikacji.
Aby dowiedzieć się więcej o projektach Firebase, zobacz Omówienie projektów Firebase .
Aplikacja korzysta z następujących produktów Firebase, które są dostępne dla aplikacji internetowych:
- Uwierzytelnianie: umożliwia użytkownikom logowanie się do Twojej aplikacji.
- Firestore: zapisuje uporządkowane dane w chmurze i otrzymuje natychmiastowe powiadomienia o zmianie danych.
- Reguły bezpieczeństwa Firebase: Zabezpiecza Twoją bazę danych.
Niektóre z tych produktów wymagają specjalnej konfiguracji lub musisz je włączyć w konsoli Firebase.
Włącz uwierzytelnianie logowania przez e-mail
- W okienku Przegląd projektu w konsoli Firebase rozwiń menu Kompilacja .
- Kliknij opcję Uwierzytelnianie > Rozpocznij > Metoda logowania > Adres e-mail/hasło > Włącz > Zapisz .
Włącz Firestore
Aplikacja internetowa używa Firestore do zapisywania wiadomości na czacie i odbierania nowych wiadomości na czacie.
Włącz Firestore:
- W menu Build kliknij Cloud Firestore > Utwórz bazę danych .
- Wybierz Uruchom w trybie testowym, a następnie przeczytaj zastrzeżenie dotyczące zasad bezpieczeństwa. Tryb testowy zapewnia, że możesz swobodnie pisać do bazy danych podczas programowania.
- Kliknij Dalej , a następnie wybierz lokalizację dla swojej bazy danych. Możesz użyć domyślnego. Nie możesz później zmienić lokalizacji.
- Kliknij Włącz .
4. Skonfiguruj Firebase
Aby używać Firebase z Flutter, musisz wykonać następujące zadania, aby skonfigurować projekt Flutter do prawidłowego korzystania z bibliotek FlutterFire
:
- Dodaj zależności
FlutterFire
do swojego projektu. - Zarejestruj wybraną platformę w projekcie Firebase.
- Pobierz plik konfiguracyjny specyficzny dla platformy, a następnie dodaj go do kodu.
W katalogu najwyższego poziomu Twojej aplikacji Flutter znajdują się podkatalogi android
, ios
, macos
i web
, które przechowują pliki konfiguracyjne specyficzne dla platformy odpowiednio dla iOS i Androida.
Skonfiguruj zależności
Musisz dodać biblioteki FlutterFire
dla dwóch produktów Firebase, których używasz w tej aplikacji: Authentication i Firestore.
- Z wiersza poleceń dodaj następujące zależności:
$ flutter pub add firebase_core
Pakiet firebase_core
to wspólny kod wymagany dla wszystkich wtyczek Firebase Flutter.
$ flutter pub add firebase_auth
Pakiet firebase_auth
umożliwia integrację z uwierzytelnianiem.
$ flutter pub add cloud_firestore
Pakiet cloud_firestore
umożliwia dostęp do magazynu danych Firestore.
$ flutter pub add provider
Pakiet firebase_ui_auth
zawiera zestaw widżetów i narzędzi zwiększających prędkość programisty dzięki przepływom uwierzytelniania.
$ flutter pub add firebase_ui_auth
Dodałeś wymagane pakiety, ale musisz także skonfigurować projekty iOS, Android, macOS i Web runner, aby odpowiednio używać Firebase. Korzystasz również z pakietu provider
, który umożliwia oddzielenie logiki biznesowej od logiki wyświetlania.
Zainstaluj interfejs wiersza polecenia FlutterFire
Interfejs wiersza polecenia FlutterFire zależy od bazowego interfejsu wiersza polecenia Firebase.
- Jeśli jeszcze tego nie zrobiłeś, zainstaluj Firebase CLI na swoim komputerze.
- Zainstaluj interfejs wiersza polecenia FlutterFire:
$ dart pub global activate flutterfire_cli
Po zainstalowaniu polecenie flutterfire
jest dostępne na całym świecie.
Skonfiguruj swoje aplikacje
CLI wyodrębnia informacje z Twojego projektu Firebase i wybranych aplikacji projektu, aby wygenerować całą konfigurację dla określonej platformy.
W katalogu głównym swojej aplikacji uruchom komendę configure
:
$ flutterfire configure
Polecenie konfiguracji prowadzi użytkownika przez następujące procesy:
- Wybierz projekt Firebase na podstawie pliku
.firebaserc
lub z konsoli Firebase. - Określ platformy do konfiguracji, takie jak Android, iOS, macOS i Web.
- Zidentyfikuj 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 swoim projekcie.
Skonfiguruj macOS
Flutter na macOS buduje aplikacje w pełni piaskownicy. Ponieważ ta aplikacja integruje się z siecią, aby komunikować się z serwerami Firebase, musisz skonfigurować swoją aplikację z uprawnieniami klienta sieciowego.
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>
Aby uzyskać więcej informacji, zobacz Obsługa aplikacji Flutter dla komputerów stacjonarnych .
5. Dodaj funkcję RSVP
Teraz, po dodaniu Firebase do aplikacji, możesz utworzyć przycisk RSVP , który rejestruje osoby z uwierzytelnianiem . W przypadku natywnego systemu Android, natywnego systemu iOS i sieci Web dostępne są gotowe pakiety FirebaseUI Auth
, ale musisz zbudować tę możliwość dla Flutter.
Pobrany wcześniej projekt zawierał zestaw widżetów implementujących interfejs użytkownika dla większości przepływu uwierzytelniania. Implementujesz logikę biznesową, aby zintegrować uwierzytelnianie z aplikacją.
Dodaj logikę biznesową za pomocą pakietu Provider
Użyj pakietu provider
, aby udostępnić scentralizowany obiekt stanu aplikacji w drzewie widżetów Flutter:
- Zmodyfikuj importy u góry pliku
lib/main.dart
:
lib/main.dart
import 'dart:async'; // new
import 'package:firebase_auth/firebase_auth.dart' // new
hide EmailAuthProvider, PhoneAuthProvider; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:firebase_ui_auth/firebase_ui_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart'; // new
import 'firebase_options.dart'; // new
import 'src/authentication.dart'; // new
import 'src/widgets.dart';
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 jedną główną odpowiedzialność za ten krok, czyli powiadomienie drzewa widżetów, że nastąpiła aktualizacja stanu uwierzytelnionego.
- Dodaj następującą klasę na końcu pliku
lib/main.dart
:
lib/main.dart
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
bool _loggedIn = false;
bool get loggedIn => _loggedIn;
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
]);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loggedIn = true;
} else {
_loggedIn = false;
}
notifyListeners();
});
}
}
Używasz tylko dostawcy do przekazywania stanu statusu logowania użytkownika do aplikacji. Aby umożliwić użytkownikowi zalogowanie się, korzystasz z interfejsów użytkownika dostarczanych przez pakiet firebase_ui_auth
, który jest świetnym sposobem na szybkie ładowanie ekranów logowania w aplikacjach.
Zintegruj przepływ uwierzytelniania
- Połącz stan aplikacji z inicjalizacją aplikacji, a następnie dodaj przepływ uwierzytelniania do strony
HomePage
:
lib/main.dart
void main() {
// Modify from here...
WidgetsFlutterBinding.ensureInitialized();
runApp(ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: ((context, child) => const App()),
));
// ...to here.
}
Modyfikacja funkcji main()
powoduje, ż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
, która informuje pakiet provider
, kiedy ponownie wyświetlić zależne widżety.
- Zaktualizuj swoją aplikację, aby obsługiwała nawigację do różnych ekranów udostępnianych przez FirebaseUI:
lib/main.dart
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
//Start adding here
initialRoute: '/home',
routes: {
'/home': (context) {
return const HomePage();
},
'/sign-in': ((context) {
return SignInScreen(
actions: [
ForgotPasswordAction(((context, email) {
Navigator.of(context)
.pushNamed('/forgot-password', arguments: {'email': email});
})),
AuthStateChangeAction(((context, state) {
if (state is SignedIn || state is UserCreated) {
var user = (state is SignedIn)
? state.user
: (state as UserCreated).credential.user;
if (user == null) {
return;
}
if (state is UserCreated) {
user.updateDisplayName(user.email!.split('@')[0]);
}
if (!user.emailVerified) {
user.sendEmailVerification();
const snackBar = SnackBar(
content: Text(
'Please check your email to verify your email address'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
Navigator.of(context).pushReplacementNamed('/home');
}
})),
],
);
}),
'/forgot-password': ((context) {
final arguments = ModalRoute.of(context)?.settings.arguments
as Map<String, dynamic>?;
return ForgotPasswordScreen(
email: arguments?['email'] as String,
headerMaxExtent: 200,
);
}),
'/profile': ((context) {
return ProfileScreen(
providers: [],
actions: [
SignedOutAction(
((context) {
Navigator.of(context).pushReplacementNamed('/home');
}),
),
],
);
})
},
// end adding here
title: 'Firebase Meetup',
theme: ThemeData(
buttonTheme: Theme.of(context).buttonTheme.copyWith(
highlightColor: Colors.deepPurple,
),
primarySwatch: Colors.deepPurple,
textTheme: GoogleFonts.robotoTextTheme(
Theme.of(context).textTheme,
),
visualDensity: VisualDensity.adaptivePlatformDensity,
),
);
}
}
Każdy ekran ma skojarzony z nim inny typ akcji na podstawie nowego stanu przepływu uwierzytelniania. Po większości zmian stanu uwierzytelniania możesz przekierować z powrotem do preferowanego ekranu, niezależnie od tego, czy jest to ekran główny, czy inny ekran, na przykład profil.
- W metodzie budowania klasy
HomePage
zintegruj stan aplikacji z widżetemAuthFunc
:
lib/main.dart
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Consumer<ApplicationState>(
builder: (context, appState, child) => Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
loggedIn: appState.loggedIn,
signOut: () {
FirebaseAuth.instance.signOut();
}),
),
// to here
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
],
),
));
}
}
Tworzysz instancję widżetu AuthFunc
i opakowujesz go w widżet Consumer
. Widżet Konsument to zwykły sposób użycia pakietu provider
do odbudowania części drzewa po zmianie stanu aplikacji. Widżet AuthFunc
to dodatkowe widżety, które testujesz.
Przetestuj przepływ uwierzytelniania
- W aplikacji dotknij przycisku RSVP , aby zainicjować
SignInScreen
.
- Wprowadź adres email. Jeśli jesteś już zarejestrowany, system poprosi o podanie hasła. W innym przypadku system poprosi o wypełnienie formularza rejestracyjnego.
- Wprowadź hasło krótsze niż sześć znaków, aby sprawdzić przepływ obsługi błędów. Jeśli jesteś zarejestrowany, zamiast tego zobaczysz hasło.
- Wprowadź nieprawidłowe hasła, aby sprawdzić przepływ obsługi błędów.
- Wprowadź poprawne hasło. Zobaczysz środowisko zalogowania, które oferuje użytkownikowi możliwość wylogowania.
6. Pisz wiadomości do Firestore
Wspaniale jest wiedzieć, że użytkownicy przychodzą, ale musisz dać gościom coś innego do zrobienia w aplikacji. Co by było, gdyby mogli zostawiać wiadomości w księdze gości? Mogą powiedzieć, dlaczego nie mogą się doczekać przyjazdu lub kogo mają nadzieję spotkać.
Do przechowywania wiadomości czatu, które użytkownicy piszą w aplikacji, używasz Firestore .
Model danych
Firestore to baza danych NoSQL, a dane przechowywane w bazie danych są podzielone na kolekcje, dokumenty, pola i podkolekcje. Każdą wiadomość czatu przechowujesz jako dokument w kolekcji gustbook
, która jest kolekcją najwyższego poziomu.
Dodaj wiadomości do Firestore
W tej sekcji dodasz funkcję umożliwiającą użytkownikom zapisywanie komunikatów w bazie danych. Najpierw dodajesz pole formularza i przycisk wyślij, a następnie kod łączący te elementy z bazą danych.
- W pliku
lib/main.dart
dodaj importy dlacloud_firestore
idart: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';
- Na końcu pliku
lib/main.dart
dodaj stanowy widżet KsięgiGuestBook
, aby utworzyć elementy interfejsu użytkownika pola wiadomości i przycisku wysyłania:
lib/main.dart
class GuestBook extends StatefulWidget {
const GuestBook({required this.addMessage, super.key});
final FutureOr<void> Function(String message) addMessage;
@override
State<GuestBook> createState() => _GuestBookState();
}
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Leave a message',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: const [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
);
}
}
Jest tu kilka ciekawostek. Najpierw tworzysz instancję formularza, aby sprawdzić, czy wiadomość rzeczywiście zawiera treść, i pokazać użytkownikowi komunikat o błędzie, jeśli go nie ma. Aby zweryfikować formularz, uzyskujesz dostęp do stanu formularza za formularzem za pomocą GlobalKey
. Aby uzyskać więcej informacji o kluczach i sposobach ich używania, zobacz Kiedy używać kluczy .
Zwróć również uwagę na sposób rozmieszczenia widżetów, masz Row
z TextFormField
i StyledButton
, który zawiera Row
. Należy również zauważyć, że TextFormField
jest opakowany w widżet Expanded
, co zmusza TextFormField
do wypełnienia dodatkowego miejsca w wierszu. Aby lepiej zrozumieć, dlaczego jest to wymagane, zobacz Zrozumienie ograniczeń .
Teraz, gdy masz widżet, który umożliwia użytkownikowi wprowadzenie tekstu do Księgi Gości, musisz go wyświetlić na ekranie.
- Edytuj
HomePage
, aby dodać następujące dwa wiersze na końcu elementówListView
:
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)),
To wystarczy do wyświetlenia widżetu, ale nie wystarczy do zrobienia czegokolwiek pożytecznego. Wkrótce zaktualizujesz ten kod, aby działał.
Podgląd aplikacji
Gdy użytkownik kliknie WYŚLIJ , uruchamia następujący fragment kodu. Dodaje zawartość 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 generowanym identyfikatorem w kolekcji guestbook
.
Pamiętaj, że FirebaseAuth.instance.currentUser.uid
jest odniesieniem do automatycznie generowanego unikalnego identyfikatora, który uwierzytelnianie nadaje wszystkim zalogowanym użytkownikom.
- W pliku
lib/main.dart
dodaj metodęaddMessageToGuestBook
. W następnym kroku połączysz tę możliwość z interfejsem użytkownika.
lib/main.dart
class ApplicationState extends ChangeNotifier {
// Current content of ApplicationState elided ...
// Add from here...
Future<DocumentReference> addMessageToGuestBook(String message) {
if (!_loggedIn) {
throw Exception('Must be logged in');
}
return FirebaseFirestore.instance
.collection('guestbook')
.add(<String, dynamic>{
'text': message,
'timestamp': DateTime.now().millisecondsSinceEpoch,
'name': FirebaseAuth.instance.currentUser!.displayName,
'userId': FirebaseAuth.instance.currentUser!.uid,
});
}
// ...to here.
}
Połącz interfejs użytkownika i bazę danych
Masz interfejs użytkownika, w którym użytkownik może wprowadzić tekst, który chce dodać do Księgi Gości, i masz kod, aby dodać wpis do Firestore. Teraz wszystko, co musisz zrobić, to połączyć oba.
- W pliku
lib/main.dart
wprowadź następującą zmianę w widgecieHomePage
:
lib/main.dart
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
loggedIn: appState.loggedIn,
signOut: () {
FirebaseAuth.instance.signOut();
}),
),
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Modify from here...
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// ...to here.
],
),
);
}
}
Zastąpiłeś dwie linie dodane na początku tego kroku pełną implementacją. Ponownie używasz Consumer<ApplicationState>
, aby udostępnić stan aplikacji dla renderowanej części drzewa. Dzięki temu możesz zareagować na kogoś, kto wpisze wiadomość w interfejsie użytkownika i opublikuje ją w bazie danych. W następnej sekcji sprawdzisz, czy dodane wiadomości są publikowane w bazie danych.
Testuj wysyłanie wiadomości
- W razie potrzeby zaloguj się do aplikacji.
- Wprowadź wiadomość, na przykład
Hey there!
, a następnie kliknij WYŚLIJ .
Ta czynność powoduje zapisanie wiadomości w bazie danych Firestore. Jednak nie widzisz komunikatu w rzeczywistej aplikacji Flutter, ponieważ nadal musisz zaimplementować pobieranie danych, co robisz w następnym kroku. Jednak w panelu bazy danych konsoli Firebase możesz 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 następujący fragment kodu:
7. Czytaj wiadomości
To cudowne, że goście mogą pisać wiadomości do bazy danych, ale nie widzą ich jeszcze w aplikacji. Czas to naprawić!
Synchronizuj wiadomości
Aby wyświetlać komunikaty, musisz dodać odbiorniki uruchamiające się w przypadku zmiany danych, a następnie utworzyć element interfejsu użytkownika, który wyświetla nowe komunikaty. Do stanu aplikacji dodajesz kod, który nasłuchuje nowo dodanych komunikatów z aplikacji.
- W pliku
lib/main.dart
przed widżetemGuestBook
gości dodaj następującą klasę, aby udostępnić uporządkowany widok danych przechowywanych w Firestore.
lib/main.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
- W sekcji
ApplicationState
, w której definiujesz stan i gettery, dodaj następujące linie:
lib/main.dart
bool _loggedIn = false;
bool get loggedIn => _loggedIn;
// Add from here...
StreamSubscription<QuerySnapshot>? _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// ...to here.
- W sekcji inicjalizacji
ApplicationState
dodaj następujące wiersze, aby subskrybować zapytanie dotyczące kolekcji dokumentów, gdy użytkownik się loguje i anulować subskrypcję, gdy się wyloguje:
lib/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
]);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loggedIn = true;
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
for (final document in snapshot.docs) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
}
notifyListeners();
});
} else {
_loggedIn = false;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
}
notifyListeners();
});
}
Ta sekcja jest ważna, ponieważ tworzysz zapytanie dotyczące kolekcji guestbook
i obsługujesz subskrypcję i anulowanie subskrypcji tej kolekcji. Słuchasz strumienia, w którym rekonstruujesz lokalną pamięć podręczną wiadomości w kolekcji guestbook
, a także przechowujesz odniesienie do tej subskrypcji, aby móc później z niej zrezygnować. Dużo się tu dzieje, więc powinieneś zbadać to w debugerze, aby sprawdzić, co się dzieje, aby uzyskać wyraźniejszy model mentalny. Aby uzyskać więcej informacji, zobacz Pobieranie aktualizacji w czasie rzeczywistym za pomocą Firestore .
- W widżecie
GuestBook
dodaj listę wiadomości jako część konfiguracji, aby połączyć ten zmieniający się stan z interfejsem użytkownika:
lib/main.dart
class GuestBook extends StatefulWidget {
// Modify the following line:
const GuestBook({super.key, required this.addMessage, required this.messages,});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
_GuestBookState createState() => _GuestBookState();
}
- W
_GuestBookState
zmodyfikuj metodębuild
w następujący sposób, aby udostępnić tę konfigurację:
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.
);
}
}
Zawijasz poprzednią zawartość metody build()
widżetem Column
, a następnie dodajesz kolekcję dla na końcu elementów Column
, aby wygenerować nowy Paragraph
dla każdej wiadomości na liście wiadomości.
- Zaktualizuj
HomePage
, aby poprawnie skonstruowaćGuestBook
za pomocą nowego parametrumessages
:
lib/main.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
),
],
],
),
),
Przetestuj synchronizację wiadomości
Firestore automatycznie i błyskawicznie synchronizuje dane z klientami zapisanymi do bazy danych.
Synchronizacja wiadomości testowych:
- W aplikacji znajdź wiadomości utworzone wcześniej w bazie danych.
- Napisz nowe wiadomości. Pojawiają się natychmiast.
- Otwórz swój obszar roboczy w wielu oknach lub kartach. Wiadomości synchronizują się w czasie rzeczywistym w oknach i na kartach.
- Opcjonalnie: w menu bazy danych konsoli Firebase ręcznie usuń, zmodyfikuj lub dodaj nowe wiadomości. Wszystkie zmiany pojawiają się w interfejsie użytkownika.
Gratulacje! Czytasz dokumenty Firestore w swojej aplikacji!
Podgląd aplikacji
8. Skonfiguruj podstawowe zasady bezpieczeństwa
Początkowo skonfigurowałeś Firestore do używania trybu testowego, co oznacza, że Twoja baza danych jest otwarta do odczytu i zapisu. Należy jednak używać trybu testowego tylko na wczesnych etapach programowania. Najlepszym rozwiązaniem jest skonfigurowanie reguł bezpieczeństwa dla bazy danych podczas opracowywania aplikacji. Bezpieczeństwo jest integralną częścią struktury i zachowania Twojej aplikacji.
Reguły bezpieczeństwa Firebase pozwalają kontrolować dostęp do dokumentów i kolekcji w bazie danych. Elastyczna składnia reguł pozwala tworzyć reguły pasujące do wszystkiego, od wszystkich zapisów w całej bazie danych po operacje na określonym dokumencie.
Skonfiguruj podstawowe zasady bezpieczeństwa:
- W menu Programowanie konsoli Firebase kliknij Baza danych > Reguły . Powinieneś zobaczyć następujące domyślne reguły bezpieczeństwa i ostrzeżenie o tym, że reguły są publiczne:
- Zidentyfikuj kolekcje, do których aplikacja zapisuje dane:
W match /databases/{database}/documents
określ 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ż identyfikator UID uwierzytelnienia został użyty jako pole w każdym dokumencie księgi gości, można uzyskać identyfikator UID uwierzytelnienia i sprawdzić, czy każdy, kto próbuje pisać w dokumencie, ma pasujący identyfikator UID uwierzytelnienia.
- Dodaj reguły odczytu i zapisu do swojego zestawu reguł:
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 edytować wiadomość.
- Dodaj walidację danych, aby upewnić się, ż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. Dodatkowy krok: przećwicz to, czego się nauczyłeś
Zapisz status RSVP uczestnika
W tej chwili Twoja aplikacja umożliwia czatowanie tylko osobom zainteresowanym wydarzeniem. Ponadto jedynym sposobem, aby dowiedzieć się, czy ktoś nadchodzi, jest powiadomienie o tym na czacie.
Na tym etapie organizujesz się i informujesz ludzi, ile osób przyjdzie. Do stanu aplikacji dodajesz kilka możliwości. Pierwszym z nich jest możliwość nominowania przez zalogowanego użytkownika, czy bierze udział. Drugi to licznik, ile osób uczestniczy.
- W pliku
lib/main.dart
dodaj następujące wiersze do sekcji narzędzi dostępu, aby kod interfejsu użytkownika mógł wchodzić w interakcję z tym stanem:
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});
}
}
- Zaktualizuj metodę
init()
ApplicationState
w następujący sposób:
lib/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
]);
// Add from here...
FirebaseFirestore.instance
.collection('attendees')
.where('attending', isEqualTo: true)
.snapshots()
.listen((snapshot) {
_attendees = snapshot.docs.length;
notifyListeners();
});
// ...to here.
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
for (final document in snapshot.docs) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
}
notifyListeners();
});
// Add from here...
_attendingSubscription = FirebaseFirestore.instance
.collection('attendees')
.doc(user.uid)
.snapshots()
.listen((snapshot) {
if (snapshot.data() != null) {
if (snapshot.data()!['attending'] as bool) {
_attending = Attending.yes;
} else {
_attending = Attending.no;
}
} else {
_attending = Attending.unknown;
}
notifyListeners();
});
// ...to here.
} else {
_loginState = ApplicationLoginState.loggedOut;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
_attendingSubscription?.cancel(); // new
}
notifyListeners();
});
}
Ten kod dodaje zawsze subskrybowane zapytanie, aby określić liczbę uczestników, oraz drugie zapytanie, które jest aktywne tylko wtedy, gdy użytkownik jest zalogowany, aby określić, czy użytkownik uczestniczy.
- Dodaj następujące wyliczenie po deklaracji
GuestBookMessage
:
lib/main.dart
enum Attending { yes, no, unknown }
- Zdefiniuj nowy widżet, który działa jak stare przyciski opcji:
lib/main.dart
class YesNoSelection extends StatelessWidget {
const YesNoSelection(
{super.key, required this.state, required this.onSelection});
final Attending state;
final void Function(Attending selection) onSelection;
@override
Widget build(BuildContext context) {
switch (state) {
case Attending.yes:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
TextButton(
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
case Attending.no:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
TextButton(
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
default:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
StyledButton(
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
}
}
}
Rozpoczyna się w stanie nieokreślonym bez wybrania opcji Tak lub Nie . Gdy użytkownik wybierze, czy bierze udział, pokazujesz tę opcję podświetloną wypełnionym przyciskiem, a drugą opcję cofa się z płaskim renderowaniem.
- Zaktualizuj metodę
build()
HomePage
, aby skorzystać zYesNoSelection
, umożliwić zalogowanemu użytkownikowi nominowanie, czy bierze udział, i wyświetlić liczbę uczestników wydarzenia:
lib/main.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Add from here...
if (appState.attendees >= 2)
Paragraph('${appState.attendees} people going')
else if (appState.attendees == 1)
const Paragraph('1 person going')
else
const Paragraph('No one going'),
// ...to here.
if (appState.loggedIn) ...[
// Add from here...
YesNoSelection(
state: appState.attending,
onSelection: (attending) => appState.attending = attending,
),
// ...to here.
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages,
),
],
],
),
),
Dodaj reguły
Skonfigurowałeś już pewne reguły, więc dane, które dodasz 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 uwierzytelnienia użyty jako nazwa dokumentu i sprawdź, czyuid
użytkownika przesyłającego 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 przeczytać listę uczestników, ponieważ nie ma tam żadnych prywatnych danych, ale tylko twórca może ją aktualizować.
- Dodaj walidację danych, aby upewnić się, ż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 zobaczyć wyniki w panelu Firestore w konsoli Firebase.
Podgląd aplikacji
10. Gratulacje!
Użyłeś Firebase do zbudowania interaktywnej aplikacji internetowej działającej w czasie rzeczywistym!