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 stworzyć aplikację do potwierdzania zgłoszeń i czatu w księdze 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/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ą 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:
- Utwórz nowy plik o nazwie
app_state.dart
o następującej treści:
lib/stan_aplikacji.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 jedną główną odpowiedzialność za ten krok, czyli powiadomienie drzewa widżetów, że nastąpiła aktualizacja stanu uwierzytelnionego.
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
- Zmodyfikuj importy 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 inicjalizacją aplikacji, a następnie dodaj przepływ uwierzytelniania do
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, tworząc konfigurację
GoRouter
:
lib/main.dart
// Add GoRouter configuration outside the App class
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: 'sign-in',
builder: (context, state) {
return SignInScreen(
actions: [
ForgotPasswordAction(((context, email) {
final uri = Uri(
path: '/sign-in/forgot-password',
queryParameters: <String, String?>{
'email': email,
},
);
context.push(uri.toString());
})),
AuthStateChangeAction(((context, state) {
final user = switch (state) {
SignedIn state => state.user,
UserCreated state => state.credential.user,
_ => null
};
if (user == null) {
return;
}
if (state is UserCreated) {
user.updateDisplayName(user.email!.split('@')[0]);
}
if (!user.emailVerified) {
user.sendEmailVerification();
const snackBar = SnackBar(
content: Text(
'Please check your email to verify your email address'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
context.pushReplacement('/');
})),
],
);
},
routes: [
GoRoute(
path: 'forgot-password',
builder: (context, state) {
final arguments = state.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
);
}
}
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/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 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 guestbook
, 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.
- Utwórz nowy plik o nazwie
guest_book.dart
, dodaj stanowy widżetGuestBook
, aby utworzyć elementy interfejsu użytkownika pola wiadomości i przycisku wysyłania:
lib/księga_gości.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'),
],
),
),
],
),
),
);
}
}
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 treść
HomePage
, aby dodać następujące dwa wiersze na końcu elementów podrzędnychListView
:
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/app_state.dart
dodaj metodęaddMessageToGuestBook
. W następnym kroku połączysz tę możliwość z interfejsem użytkownika.
lib/stan_aplikacji.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.
}
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/home_page.dart
wprowadź następującą zmianę w widgecieHomePage
:
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ą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.
- Utwórz nowy plik
guest_book_message.dart
, dodaj poniższą klasę, aby udostępnić uporządkowany widok danych przechowywanych w Firestore.
lib/księga_gości_wiadomości.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
- W pliku
lib/app_state.dart
dodaj następujące importy:
lib/stan_aplikacji.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 następujące linie:
lib/stan_aplikacji.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/stan_aplikacji.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 pliku
lib/guest_book.dart
dodaj następujący import:
import 'guest_book_message.dart';
- W widżecie
GuestBook
dodaj listę wiadomości jako część konfiguracji, aby połączyć ten zmieniający się stan z interfejsem użytkownika:
lib/księga_gości.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/księga_gości.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 podrzędnych Column
, aby wygenerować nowy Paragraph
dla każdej wiadomości na liście wiadomości.
- Zaktualizuj treść
HomePage
, aby poprawnie skonstruowaćGuestBook
za pomocą nowego parametrumessages
:
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
),
],
],
),
),
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/app_state.dart
dodaj następujące wiersze do sekcji akcesorów wApplicationState
, aby kod interfejsu użytkownika mógł wchodzić w interakcję z tym stanem:
lib/stan_aplikacji.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()
klasyApplicationState
w następujący sposób:
lib/stan_aplikacji.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 na początku pliku
lib/app_state.dart
.
lib/stan_aplikacji.dart
enum Attending { yes, no, unknown }
- Utwórz nowy plik
yes_no_selection.dart
, 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'),
),
],
),
);
}
}
}
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/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
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
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!