Poznaj Firebase dla Flutter

1. Zanim zaczniesz

Podczas tych zajęć z programowania poznasz podstawy Firebase do tworzenia aplikacji mobilnych Flutter na Androida i iOS.

Warunki wstępne

Czego się dowiesz

  • Jak zbudować aplikację do odpowiadania na wydarzenia i czatowania w księdze gości na Androidzie, iOS, w Internecie i macOS za pomocą Flutter.
  • Jak uwierzytelniać użytkowników za pomocą uwierzytelniania Firebase i synchronizować dane z Firestore.

Ekran główny aplikacji na Androida

Ekran główny aplikacji na iOS

Co będziesz potrzebował

Którekolwiek z poniższych urządzeń:

  • Fizyczne urządzenie z Androidem lub iOS podłączone do Twojego komputera i ustawione w trybie programisty.
  • Symulator iOS (wymaga narzędzi Xcode ).
  • Emulator Androida (wymaga konfiguracji w Android Studio ).

Potrzebujesz także:

  • Wybrana przeglądarka, taka jak Google Chrome.
  • Wybrane IDE lub edytor tekstu skonfigurowany z wtyczkami Dart i Flutter, takimi jak Android Studio lub Visual Studio Code .
  • Najnowsza stable wersja Fluttera lub beta , jeśli lubisz życie na krawędzi.
  • Konto Google do tworzenia projektu Firebase i zarządzania nim.
  • Interfejs Firebase CLI zalogował się na Twoje konto Google.

2. Pobierz przykładowy kod

Pobierz początkową wersję swojego projektu z GitHub:

  1. Z wiersza poleceń sklonuj repozytorium GitHub do katalogu flutter-codelabs :
git clone https://github.com/flutter/codelabs.git flutter-codelabs

Katalog flutter-codelabs zawiera kod kolekcji codelabs. Kod tego ćwiczenia z programowania znajduje się w katalogu flutter-codelabs/firebase-get-to-know-flutter . Katalog zawiera serię migawek, które pokazują, jak powinien wyglądać Twój projekt na końcu każdego kroku. Na przykład jesteś na drugim etapie.

  1. Znajdź pasujące pliki do drugiego kroku:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02

Jeżeli chcesz przeskoczyć do przodu lub zobaczyć jak coś powinno wyglądać po danym kroku, zajrzyj do katalogu nazwanego od kroku, 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 laboratorium kodowania, które składa się z jeszcze niedziałającej aplikacji spotkań Flutter.

Zlokalizuj pliki wymagające pracy

Kod 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 pomagających ujednolicić styl aplikacji. Stanowią one ekran aplikacji startowej.
    • lib/src/authentication.dart : ten plik zawiera częściową implementację uwierzytelniania z zestawem widżetów tworzących środowisko logowania użytkownika na potrzeby uwierzytelniania opartego na e-mailach Firebase. Te widżety służące do uwierzytelniania nie są jeszcze używane w aplikacji startowej, ale wkrótce je dodasz.

W razie potrzeby dodajesz dodatkowe pliki, aby zbudować pozostałą część aplikacji.

Przejrzyj plik lib/main.dart

Ta aplikacja korzysta z pakietu google_fonts , aby ustawić Roboto jako domyślną czcionkę w całej aplikacji. Możesz odwiedzić stronę Fonts.google.com i używać czcionek, które tam znajdziesz, 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ą powielony kod, aby zmniejszyć bałagan w układzie strony opisanym w HomePage . Umożliwia to również zachowanie spójnego wyglądu i stylu.

Oto jak wygląda Twoja aplikacja na Androidzie, iOS, w Internecie i na macOS:

Ekran główny aplikacji na Androida

Ekran główny aplikacji na iOS

Ekran główny aplikacji w Internecie

Ekran główny aplikacji na macOS

3. Utwórz i skonfiguruj projekt Firebase

Wyświetlanie informacji o wydarzeniu jest świetne dla gości, ale samo w sobie nie jest zbyt przydatne. 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

  1. Zaloguj się do Firebase .
  2. W konsoli kliknij Dodaj projekt lub Utwórz projekt .
  3. W polu Nazwa projektu wpisz Firebase-Flutter-Codelab i kliknij Kontynuuj .

4395e4e67c08043a.png

  1. 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.

b7138cde5f2c7b61.png

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 aplikacji.
  • Firestore: zapisuje uporządkowane dane w chmurze i otrzymuje natychmiastowe powiadomienia w przypadku zmiany danych.
  • Reguły bezpieczeństwa Firebase: zabezpieczają Twoją bazę danych.

Niektóre z tych produktów wymagają specjalnej konfiguracji lub trzeba je włączyć w konsoli Firebase.

Włącz uwierzytelnianie logowania e-mailem

  1. W panelu Przegląd projektu w konsoli Firebase rozwiń menu Kompilacja .
  2. Kliknij Uwierzytelnianie > Rozpocznij > Metoda logowania > E-mail/hasło > Włącz > Zapisz .

58e3e3e23c2f16a4.png

Włącz Firestore

Aplikacja internetowa używa Firestore do zapisywania wiadomości czatu i odbierania nowych wiadomości czatu.

Włącz Firestore:

  • W menu Kompilacja kliknij Baza danych Firestore > Utwórz bazę danych .

99e8429832d23fa3.png

  1. Wybierz opcję Uruchom w trybie testowym , a następnie przeczytaj zastrzeżenie dotyczące zasad bezpieczeństwa. Tryb testowy zapewnia możliwość swobodnego zapisu do bazy danych podczas programowania.

6be00e26c72ea032.png

  1. Kliknij Dalej , a następnie wybierz lokalizację swojej bazy danych. Możesz użyć domyślnego. Nie można później zmienić lokalizacji.

278656eefcfb0216.png

  1. Kliknij opcję Włącz .

4. Skonfiguruj Firebase

Aby używać Firebase z Flutter, musisz wykonać następujące zadania, aby skonfigurować projekt Flutter tak, aby poprawnie korzystał z bibliotek FlutterFire :

  1. Dodaj zależności FlutterFire do swojego projektu.
  2. Zarejestruj żądaną platformę w projekcie Firebase.
  3. Pobierz plik konfiguracyjny specyficzny dla platformy, a następnie dodaj go do kodu.

W katalogu najwyższego poziomu aplikacji Flutter znajdują się podkatalogi android , ios , macos i web , w których znajdują się 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 zapewnia zestaw widżetów i narzędzi zwiększających szybkość działania programistów 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 korzystać z Firebase. Używany jest również pakiet provider , który umożliwia oddzielenie logiki biznesowej od logiki wyświetlania.

Zainstaluj interfejs wiersza polecenia FlutterFire

Interfejs CLI FlutterFire zależy od podstawowego interfejsu wiersza polecenia Firebase.

  1. Jeśli jeszcze tego nie zrobiłeś, zainstaluj Firebase CLI na swoim komputerze.
  2. 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

Interfejs wiersza polecenia wyodrębnia informacje z projektu Firebase i wybranych aplikacji projektowych w celu wygenerowania całej konfiguracji dla określonej platformy.

W katalogu głównym aplikacji uruchom polecenie configure :

$ flutterfire configure

Polecenie konfiguracji prowadzi Cię przez następujące procesy:

  1. Wybierz projekt Firebase w oparciu o plik .firebaserc lub z konsoli Firebase.
  2. Określ platformy do konfiguracji, takie jak Android, iOS, macOS i Internet.
  3. 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.
  4. Wygeneruj plik firebase_options.dart w swoim projekcie.

Skonfiguruj system macOS

Flutter na macOS tworzy aplikacje w pełni piaskownicowe. Ponieważ ta aplikacja integruje się z siecią w celu komunikacji z serwerami Firebase, musisz skonfigurować 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 komputerów stacjonarnych dla Flutter .

5. Dodaj funkcję RSVP

Teraz, gdy dodałeś Firebase do aplikacji, możesz utworzyć przycisk RSVP , który rejestruje osoby za pomocą uwierzytelniania . W przypadku natywnych rozwiązań dla systemu Android, natywnych dla systemu iOS i sieci Web dostępne są wstępnie skompilowane pakiety FirebaseUI Auth , ale należy zbudować tę funkcję dla Flutter.

Pobrany wcześniej projekt zawierał zestaw widżetów implementujących interfejs użytkownika dla większości procesów 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 całym drzewie widżetów Flutter aplikacji:

  1. Utwórz nowy plik o nazwie app_state.dart z następującą 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 obejmują widżety uwierzytelniające z pakietu firebase_ui_auth .

Ten obiekt stanu aplikacji ApplicationState ma jedną główną odpowiedzialność za ten krok, a mianowicie powiadomienie drzewa widżetów o aktualizacji stanu uwierzytelnionego.

Korzystasz z usług dostawcy wyłącznie w celu komunikowania stanu statusu logowania użytkownika do aplikacji. Aby umożliwić użytkownikowi zalogowanie się, korzystasz z interfejsów użytkownika dostarczonych przez pakiet firebase_ui_auth , co jest doskonałym sposobem na szybkie ładowanie ekranów logowania w aplikacjach.

Zintegruj przepływ uwierzytelniania

  1. Zmodyfikuj import na górze 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';
  1. 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 pozwala pakietowi provider wiedzieć, kiedy ponownie wyświetlić zależne widżety.

  1. 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.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 akcji w zależności od 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.

  1. W metodzie kompilacji klasy HomePage zintegruj stan aplikacji z widżetem AuthFunc :

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 otaczasz ją widżetem Consumer . Widget Konsument to typowy sposób wykorzystania pakietu provider do odbudowania części drzewa w przypadku zmiany stanu aplikacji. Widżet AuthFunc to dodatkowe widżety, które testujesz.

Przetestuj przepływ uwierzytelniania

cdf2d25e436bd48d.png

  1. W aplikacji dotknij przycisku RSVP , aby zainicjować SignInScreen .

2a2cd6d69d172369.png

  1. Wprowadź adres email. Jeśli jesteś już zarejestrowany, system poprosi Cię o podanie hasła. W przeciwnym razie system poprosi Cię o wypełnienie formularza rejestracyjnego.

e5e65065dba36b54.png

  1. Wprowadź hasło składające się z mniej niż sześciu znaków, aby sprawdzić przepływ obsługi błędów. Jeśli jesteś zarejestrowany, zamiast tego zobaczysz hasło.
  2. Wprowadź nieprawidłowe hasła, aby sprawdzić przepływ obsługi błędów.
  3. Wprowadź prawidłowe hasło. Widzisz doświadczenie zalogowania, które oferuje użytkownikowi możliwość wylogowania się.

4ed811a25b0cf816.png

6. Napisz wiadomości do Firestore

Wspaniale jest wiedzieć, że użytkownicy przychodzą, ale musisz dać gościom coś innego do zrobienia w aplikacji. A gdyby mogli zostawiać wiadomości w księdze gości? Mogą opowiedzieć, dlaczego tak bardzo chcą przyjechać lub kogo chcą 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ść na czacie przechowujesz jako dokument w kolekcji guestbook , która jest kolekcją najwyższego poziomu.

7c20dc8424bb1d84.png

Dodaj 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 następnie dodajesz kod łączący te elementy z bazą danych.

  1. Utwórz nowy plik o nazwie guest_book.dart , dodaj stanowy widżet GuestBook , 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 interesujących miejsc. Najpierw tworzysz instancję formularza, dzięki czemu możesz sprawdzić, czy wiadomość faktycznie zawiera treść, i wyświetlić użytkownikowi komunikat o błędzie, jeśli go nie ma. Aby sprawdzić poprawność formularza, uzyskujesz dostęp do stanu formularza znajdującego się za formularzem za pomocą GlobalKey . Aby uzyskać więcej informacji na temat kluczy i sposobu ich używania, zobacz Kiedy używać klawiszy .

Zwróć także uwagę na sposób rozmieszczenia widżetów, masz Row z TextFormField i StyledButton , który zawiera Row . Należy również pamiętać, że TextFormField jest opakowane w widżet Expanded , co wymusza na polu TextFormField wypełnienie dodatkowego miejsca w wierszu. Aby lepiej zrozumieć, dlaczego jest to wymagane, zobacz temat Omówienie ograniczeń .

Teraz, gdy masz już widżet umożliwiający użytkownikowi wprowadzenie tekstu i dodanie go do Księgi Gości, musisz wyświetlić go na ekranie.

  1. Edytuj treść HomePage , aby dodać następujące dwie linie na końcu elementów podrzędnych ListView :
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)),

Chociaż to wystarczy, aby wyświetlić widżet, nie wystarczy, aby zrobić coś przydatnego. Wkrótce zaktualizujesz ten kod, aby był funkcjonalny.

Podgląd aplikacji

Ekran główny aplikacji na Androida z integracją czatu

Ekran główny aplikacji na iOS z integracją czatu

Ekran główny aplikacji internetowej z integracją z czatem

Ekran główny aplikacji na macOS z integracją czatu

Kliknięcie przez użytkownika WYŚLIJ powoduje uruchomienie poniższego fragmentu kodu. Dodaje zawartość pola wejściowego wiadomości do zbioru guestbook w bazie danych. W szczególności metoda addMessageToGuestBook dodaje treść wiadomości do nowego dokumentu z automatycznie wygenerowanym identyfikatorem w kolekcji guestbook .

Należy pamiętać, że FirebaseAuth.instance.currentUser.uid jest odniesieniem do automatycznie wygenerowanego unikalnego identyfikatora, który zapewnia uwierzytelnianie wszystkim zalogowanym użytkownikom.

  • W pliku lib/app_state.dart dodaj metodę addMessageToGuestBook . W następnym kroku łączysz tę możliwość z interfejsem użytkownika.

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.
}

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 widżecie HomePage :

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żyj Consumer<ApplicationState> , aby udostępnić stan aplikacji renderowanej części drzewa. Dzięki temu możesz zareagować na osobę, która wpisze wiadomość w interfejsie użytkownika i opublikować ją w bazie danych. W kolejnej sekcji sprawdzisz czy dodane wiadomości zostaną opublikowane w bazie danych.

Testuj wysyłanie wiadomości

  1. W razie potrzeby zaloguj się do aplikacji.
  2. Wpisz wiadomość, na przykład Hey there! , a następnie kliknij WYŚLIJ .

Ta akcja zapisuje wiadomość w bazie danych Firestore. Jednak nie widzisz tego komunikatu w swojej rzeczywistej aplikacji Flutter, ponieważ nadal musisz zaimplementować pobieranie danych, co zrobisz 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 księgi guestbook . Na przykład spójrz na następujący fragment kodu:

713870af0b3b63c.png

7. Czytaj wiadomości

Fajnie, że goście mogą zapisywać wiadomości do bazy danych, ale nie widzą ich jeszcze w aplikacji. Czas to naprawić!

Synchronizuj wiadomości

Aby wyświetlić wiadomości, musisz dodać detektory uruchamiające się w przypadku zmiany danych, a następnie utworzyć element interfejsu użytkownika, który będzie wyświetlał nowe wiadomości. Dodajesz kod do stanu aplikacji, który nasłuchuje nowo dodanych wiadomości z aplikacji.

  1. Utwórz nowy plik guest_book_message.dart , dodaj następującą 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;
}
  1. W pliku lib/app_state.dart dodaj następujące importy:

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
  1. W sekcji ApplicationState , w której definiujesz stan i moduły pobierające, dodaj następujące linie:

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.
  1. 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ę po wylogowaniu:

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ż tworzysz w niej zapytanie dotyczące kolekcji guestbook oraz obsługujesz subskrybowanie i anulowanie subskrypcji tej kolekcji. Słuchasz strumienia, w którym rekonstruujesz lokalną pamięć podręczną wiadomości w zbiorze guestbook , a także przechowujesz odniesienie do tej subskrypcji, abyś mógł 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 Otrzymuj aktualizacje w czasie rzeczywistym za pomocą Firestore .

  1. W pliku lib/guest_book.dart dodaj następujący import:
import 'guest_book_message.dart';
  1. W widżecie GuestBook dodaj listę wiadomości w ramach 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();
}
  1. 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.
    );
  }
}

Owijasz poprzednią treść metody build() widżetem Column , a następnie dodajesz kolekcję for na końcu elementów podrzędnych Column , aby wygenerować nowy Paragraph dla każdej wiadomości na liście wiadomości.

  1. Zaktualizuj treść HomePage , aby poprawnie skonstruować GuestBook z nowym parametrem messages :

lib/strona_domowa.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
        ),
      ],
    ],
  ),
),

Testuj synchronizację wiadomości

Firestore automatycznie i błyskawicznie synchronizuje dane z klientami zapisanymi do bazy danych.

Testowa synchronizacja wiadomości:

  1. W aplikacji odszukaj w bazie danych utworzone wcześniej wiadomości.
  2. Napisz nowe wiadomości. Pojawiają się natychmiast.
  3. Otwórz obszar roboczy w wielu oknach lub kartach. Wiadomości synchronizują się w czasie rzeczywistym pomiędzy oknami i kartami.
  4. Opcjonalnie: w menu Baza 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

Ekran główny aplikacji na Androida z integracją czatu

Ekran główny aplikacji na iOS z integracją czatu

Ekran główny aplikacji internetowej z integracją z czatem

Ekran główny aplikacji na macOS z integracją czatu

8. Skonfiguruj podstawowe reguły bezpieczeństwa

Początkowo skonfigurowałeś Firestore do korzystania z trybu testowego, co oznacza, że ​​Twoja baza danych jest otwarta do odczytu i zapisu. Jednakże trybu testowego należy używać wyłącznie na wczesnych etapach programowania. Najlepszym rozwiązaniem jest skonfigurowanie reguł bezpieczeństwa dla bazy danych podczas tworzenia aplikacji. Bezpieczeństwo jest integralną częścią struktury i zachowania aplikacji.

Reguły bezpieczeństwa Firebase pozwalają kontrolować dostęp do dokumentów i kolekcji w bazie danych. Elastyczna składnia reguł umożliwia tworzenie reguł pasujących do wszystkiego, od wszystkich zapisów do całej bazy danych po operacje na określonym dokumencie.

Skonfiguruj podstawowe reguły bezpieczeństwa:

  1. 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:

7767a2d2e64e7275.png

  1. Zidentyfikuj kolekcje, do których aplikacja zapisuje dane:

W match /databases/{database}/documents zidentyfikuj 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ż użyłeś UID uwierzytelnienia jako pola w każdym dokumencie księgi gości, możesz uzyskać UID uwierzytelnienia i sprawdzić, czy każda osoba próbująca pisać do dokumentu ma pasujący UID uwierzytelnienia.

  1. Dodaj reguły odczytu i zapisu do 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ść.

  1. Dodaj weryfikację 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. Krok dodatkowy: przećwicz to, czego się nauczyłeś

Zapisz status odpowiedzi uczestnika

Obecnie Twoja aplikacja umożliwia czatowanie tylko wtedy, gdy są zainteresowani wydarzeniem. Poza tym jedynym sposobem, aby dowiedzieć się, czy ktoś przyjdzie, jest informacja o tym na czacie.

Na tym etapie organizujesz się i informujesz ludzi, ile osób przyjdzie. Do stanu aplikacji dodajesz kilka możliwości. Pierwszą z nich jest możliwość zalogowania się użytkownika, który może wskazać, czy chce wziąć udział. Drugi to licznik pokazujący, ile osób uczestniczy.

  1. W pliku lib/app_state.dart dodaj następujące wiersze do sekcji akcesorów ApplicationState , aby kod interfejsu użytkownika mógł wchodzić w interakcję 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});
  }
}
  1. Zaktualizuj metodę init() ApplicationState w następujący 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 zawsze subskrybowane w celu określenia liczby uczestników oraz drugie zapytanie, które jest aktywne tylko wtedy, gdy użytkownik jest zalogowany, w celu ustalenia, czy użytkownik uczestniczy.

  1. Dodaj następujące wyliczenie na górze pliku lib/app_state.dart .

lib/app_state.dart

enum Attending { yes, no, unknown }
  1. Utwórz nowy plik yes_no_selection.dart , zdefiniuj nowy widget, który będzie 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 nieokreślonym stanie, bez zaznaczenia opcji Tak lub Nie . Gdy użytkownik wybierze, czy chce wziąć udział, opcja ta zostanie podświetlona za pomocą wypełnionego przycisku, a druga opcja zostanie cofnięta w postaci płaskiego renderowania.

  1. Zaktualizuj metodę build() HomePage , aby skorzystać z YesNoSelection , umożliwić zalogowanemu użytkownikowi wybranie swojego udziału i wyświetlić liczbę uczestników wydarzenia:

lib/strona_domowa.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 dodawane za pomocą przycisków zostaną odrzucone. Musisz zaktualizować reguły, aby umożliwić dodanie dodatków do kolekcji attendees .

  1. W kolekcji attendees pobierz identyfikator UID uwierzytelnienia, którego użyłeś jako nazwy dokumentu i sprawdź, czy uid 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 przeczytać listę uczestników, ponieważ nie ma tam żadnych prywatnych danych, ale tylko twórca może ją zaktualizować.

  1. Dodaj weryfikację 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;

    }
  }
}
  1. Opcjonalnie: w aplikacji kliknij przyciski, aby zobaczyć wyniki w panelu Firestore w konsoli Firebase.

Podgląd aplikacji

Ekran główny aplikacji na Androida

Ekran główny aplikacji na iOS

Ekran główny aplikacji w Internecie

Ekran główny aplikacji na macOS

10. Gratulacje!

Użyłeś Firebase do zbudowania interaktywnej aplikacji internetowej działającej w czasie rzeczywistym!

Ucz się więcej