Thêm luồng xác thực người dùng vào ứng dụng Flutter bằng FirebaseUI

1. Trước khi bạn bắt đầu

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách thêm Xác thực Firebase vào ứng dụng Flutter của mình bằng gói giao diện người dùng FlutterFire. Với gói này, bạn sẽ thêm cả xác thực email/mật khẩu và xác thực Đăng nhập bằng Google vào ứng dụng Flutter. Bạn cũng sẽ tìm hiểu cách thiết lập dự án Firebase và sử dụng FlutterFire CLI để khởi tạo Firebase trong ứng dụng Flutter của mình.

Điều kiện tiên quyết

Lớp học lập trình này giả định rằng bạn đã có một số kinh nghiệm về Flutter. Nếu không, trước tiên bạn có thể muốn tìm hiểu những điều cơ bản. Các liên kết sau đây rất hữu ích:

Bạn cũng nên có một số kinh nghiệm về Firebase, nhưng sẽ không sao nếu bạn chưa bao giờ thêm Firebase vào dự án Flutter. Nếu bạn chưa quen với bảng điều khiển Firebase hoặc bạn hoàn toàn mới làm quen với Firebase, trước tiên hãy xem các liên kết sau:

Những gì bạn sẽ tạo ra

Lớp học lập trình này hướng dẫn bạn cách xây dựng quy trình xác thực cho ứng dụng Flutter bằng cách sử dụng Firebase để xác thực. Ứng dụng sẽ có màn hình đăng nhập, màn hình 'Đăng ký', màn hình khôi phục mật khẩu và màn hình hồ sơ người dùng.

6604fc9157f2c6ae.pngeab9509a41074930.pngda49189a5838e0bb.pngb2ccfb3632b77878.png

Bạn sẽ học được gì

Lớp học lập trình này bao gồm:

  • Thêm Firebase vào ứng dụng Flutter
  • Thiết lập bảng điều khiển Firebase
  • Sử dụng Firebase CLI để thêm Firebase vào ứng dụng của bạn
  • Sử dụng FlutterFire CLI để tạo cấu hình Firebase trong Dart
  • Thêm xác thực Firebase vào ứng dụng Flutter của bạn
  • Thiết lập xác thực Firebase trong bảng điều khiển
  • Thêm Email và Mật khẩu đăng nhập bằng gói firebase_ui_auth
  • Thêm đăng ký người dùng với gói firebase_ui_auth
  • Thêm 'Quên mật khẩu?' trang
  • Thêm Đăng nhập bằng Google bằng firebase_ui_auth
  • Định cấu hình ứng dụng của bạn để hoạt động với nhiều nhà cung cấp dịch vụ đăng nhập.
  • Thêm màn hình hồ sơ người dùng vào ứng dụng của bạn bằng gói firebase_ui_auth

Lớp học lập trình này đặc biệt quan tâm đến việc thêm hệ thống Xác thực mạnh mẽ bằng cách sử dụng gói firebase_ui_auth . Như bạn sẽ thấy, toàn bộ ứng dụng này, với tất cả các tính năng trên, có thể được triển khai với khoảng 100 dòng mã.

Những gì bạn cần

  • Kiến thức làm việc về Flutter và cài đặt SDK
  • Trình soạn thảo văn bản (JetBrains IDE, Android Studio và VS Code được Flutter hỗ trợ)
  • Trình duyệt Google Chrome hoặc mục tiêu phát triển ưa thích khác của bạn cho Flutter. (Một số lệnh đầu cuối trong lớp học lập trình này sẽ cho rằng bạn đang chạy ứng dụng của mình trên Chrome)

2. Tạo và thiết lập dự án Firebase

Nhiệm vụ đầu tiên bạn cần hoàn thành là tạo dự án Firebase trong bảng điều khiển web của Firebase.

Tạo dự án Firebase

  1. Đăng nhập vào Firebase .
  2. Trong bảng điều khiển Firebase, hãy nhấp vào Thêm dự án (hoặc Tạo dự án ) và nhập tên cho dự án Firebase của bạn (ví dụ: " FlutterFire-UI-Codelab ").

df42a5e3d9584b48.png

  1. Nhấp qua các tùy chọn tạo dự án. Chấp nhận các điều khoản của Firebase nếu được nhắc. Bỏ qua việc thiết lập Google Analytics vì bạn sẽ không sử dụng Analytics cho ứng dụng này.

d1fcec48bf251eaa.png

Để tìm hiểu thêm về các dự án Firebase, hãy xem Tìm hiểu các dự án Firebase .

Ứng dụng bạn đang xây dựng sử dụng Xác thực Firebase để cho phép người dùng đăng nhập vào ứng dụng của bạn. Nó cũng cho phép người dùng mới đăng ký từ ứng dụng Flutter.

Xác thực Firebase cần được bật bằng Bảng điều khiển Firebase và cần cấu hình đặc biệt sau khi được bật.

Cho phép đăng nhập email để xác thực Firebase

Để cho phép người dùng đăng nhập vào ứng dụng web, trước tiên bạn sẽ sử dụng phương thức đăng nhập Email/Mật khẩu . Sau đó, bạn sẽ thêm phương thức Đăng nhập bằng Google .

  1. Trong bảng điều khiển Firebase, hãy mở rộng menu Build ở bảng điều khiển bên trái.
  2. Nhấp vào Xác thực , sau đó nhấp vào nút Bắt đầu , sau đó nhấp vào tab Phương thức đăng nhập (hoặc nhấp vào đây để chuyển trực tiếp đến tab Phương thức đăng nhập ).
  3. Nhấp vào Email/Mật khẩu trong danh sách Nhà cung cấp dịch vụ đăng nhập , đặt nút Bật sang vị trí bật rồi nhấp vào Lưu . 58e3e3e23c2f16a4.png

3. Thiết lập ứng dụng Flutter

Bạn sẽ cần tải xuống mã khởi động và cài đặt Firebase CLI trước khi chúng ta bắt đầu.

Lấy mã khởi đầu

Sao chép kho GitHub từ dòng lệnh:

git clone https://github.com/flutter/codelabs.git flutter-codelabs

Ngoài ra, nếu bạn đã cài đặt công cụ CLI của GitHub :

gh repo clone flutter/codelabs flutter-codelabs

Mã mẫu phải được sao chép vào thư mục flutter-codelabs trên máy của bạn, nơi chứa mã cho một tập hợp các lớp học lập trình. Mã cho lớp học lập trình này nằm trong thư mục con flutter-codelabs/firebase-auth-flutterfire-ui .

Thư mục flutter-codelabs/firebase-auth-flutterfire-ui chứa hai dự án Flutter. Một cái được gọi là complete và cái còn lại được gọi là start . Thư mục start chứa một dự án chưa hoàn thành và đó là nơi bạn sẽ dành nhiều thời gian nhất.

cd flutter-codelabs/firebase-auth-flutterfire-ui/start

Nếu bạn muốn bỏ qua phần tiếp theo hoặc xem nội dung nào đó sẽ trông như thế nào khi hoàn tất, hãy tìm trong thư mục có tên Complete để tham khảo chéo.

Nếu muốn theo dõi lớp học lập trình và tự thêm mã, bạn nên bắt đầu với ứng dụng Flutter tại flutter-codelabs/firebase-auth-flutterfire-ui/start và thêm mã vào dự án đó trong suốt lớp học lập trình. Mở hoặc nhập thư mục đó vào IDE ưa thích của bạn.

Cài đặt Firebase CLI

Firebase CLI cung cấp các công cụ để quản lý các dự án Firebase của bạn. CLI là cần thiết cho FlutterFire CLI, bạn sẽ cài đặt sau.

Có nhiều cách khác nhau để cài đặt CLI. Cách đơn giản nhất, nếu bạn đang sử dụng MacOS hoặc Linux, là chạy lệnh này từ thiết bị đầu cuối của mình:

curl -sL https://firebase.tools | bash

Sau khi cài đặt CLI, bạn phải xác thực bằng Firebase.

  1. Đăng nhập vào Firebase bằng tài khoản Google của bạn bằng cách chạy lệnh sau:
firebase login
  1. Lệnh này kết nối máy cục bộ của bạn với Firebase và cấp cho bạn quyền truy cập vào các dự án Firebase của bạn.
  1. Kiểm tra xem CLI đã được cài đặt đúng cách và có quyền truy cập vào tài khoản của bạn hay chưa bằng cách liệt kê các dự án Firebase của bạn. Chạy lệnh sau:
firebase projects:list
  1. Danh sách được hiển thị phải giống với danh sách các dự án Firebase được liệt kê trong bảng điều khiển Firebase . Bạn nên xem ít nhất flutterfire-ui-codelab.

Cài đặt FlutterFire CLI

FlutterFire CLI là một công cụ giúp đơn giản hóa quá trình cài đặt Firebase trên tất cả các nền tảng được hỗ trợ trong ứng dụng Flutter của bạn. Nó được xây dựng dựa trên Firebase CLI.

Đầu tiên, cài đặt CLI:

dart pub global activate flutterfire_cli

Đảm bảo CLI đã được cài đặt. Chạy lệnh sau và đảm bảo rằng CLI xuất ra menu trợ giúp.

flutterfire -—help

Thêm dự án Firebase vào ứng dụng Flutter của bạn

Định cấu hình FlutterFire

Bạn có thể sử dụng FlutterFire để tạo mã Dart cần thiết nhằm sử dụng Firebase trong ứng dụng Flutter của mình.

flutterfire configure

Khi lệnh này chạy, bạn sẽ được nhắc chọn dự án Firebase nào bạn muốn sử dụng và nền tảng nào bạn muốn thiết lập.

Các ảnh chụp màn hình sau đây hiển thị lời nhắc bạn cần trả lời.

  1. Chọn dự án bạn muốn sử dụng. Trong trường hợp này, hãy sử dụng flutterfire-ui-codelab 1359cdeb83204baa.png
  2. Chọn nền tảng bạn muốn sử dụng. Trong lớp học lập trình này, có các bước để định cấu hình Xác thực Firebase cho Flutter cho web, iOS và Android, nhưng bạn có thể thiết lập dự án Firebase của mình để sử dụng tất cả các tùy chọn. 301c9534f594f472.png
  3. Ảnh chụp màn hình này hiển thị đầu ra ở cuối quá trình. Nếu đã quen thuộc với Firebase, bạn sẽ nhận thấy rằng bạn không phải tạo các ứng dụng nền tảng (ví dụ: ứng dụng Android) trong bảng điều khiển và FlutterFire CLI đã làm điều đó cho bạn. 12199a85ade30459.png

Khi quá trình này hoàn tất, hãy xem ứng dụng Flutter trong trình soạn thảo văn bản của bạn. FlutterFire CLI đã tạo một tệp mới có tên firebase_options.dart . Tệp này chứa một lớp có tên là FirebaseOptions, có các biến tĩnh chứa cấu hình Firebase cần thiết cho mỗi nền tảng. Nếu bạn đã chọn tất cả các nền tảng khi chạy flutterfire configure , bạn sẽ thấy các giá trị tĩnh có tên web , android , iosmacos .

firebase_options.dart

import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
   show defaultTargetPlatform, kIsWeb, TargetPlatform;

/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
///   options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
 static FirebaseOptions get currentPlatform {
   if (kIsWeb) {
     return web;
   }
   // ignore: missing_enum_constant_in_switch
   switch (defaultTargetPlatform) {
     case TargetPlatform.android:
       return android;
     case TargetPlatform.iOS:
       return ios;
     case TargetPlatform.macOS:
       return macos;
   }

   throw UnsupportedError(
     'DefaultFirebaseOptions are not supported for this platform.',
   );
 }

 static const FirebaseOptions web = FirebaseOptions(
   apiKey: 'AIzaSyCqFjCV_9CZmYeIvcK9FVy4drmKUlSaIWY',
   appId: '1:963656261848:web:7219f7fca5fc70afb237ad',
   messagingSenderId: '963656261848',
   projectId: 'flutterfire-ui-codelab',
   authDomain: 'flutterfire-ui-codelab.firebaseapp.com',
   storageBucket: 'flutterfire-ui-codelab.appspot.com',
   measurementId: 'G-DGF0CP099H',
 );

 static const FirebaseOptions android = FirebaseOptions(
   apiKey: 'AIzaSyDconZaCQpkxIJ5KQBF-3tEU0rxYsLkIe8',
   appId: '1:963656261848:android:c939ccc86ab2dcdbb237ad',
   messagingSenderId: '963656261848',
   projectId: 'flutterfire-ui-codelab',
   storageBucket: 'flutterfire-ui-codelab.appspot.com',
 );

 static const FirebaseOptions ios = FirebaseOptions(
   apiKey: 'AIzaSyBqLWsqFjYAdGyihKTahMRDQMo0N6NVjAs',
   appId: '1:963656261848:ios:d9e01cfe8b675dfcb237ad',
   messagingSenderId: '963656261848',
   projectId: 'flutterfire-ui-codelab',
   storageBucket: 'flutterfire-ui-codelab.appspot.com',
   iosClientId: '963656261848-v7r3vq1v6haupv0l1mdrmsf56ktnua60.apps.googleusercontent.com',
   iosBundleId: 'com.example.complete',
 );

 static const FirebaseOptions macos = FirebaseOptions(
   apiKey: 'AIzaSyBqLWsqFjYAdGyihKTahMRDQMo0N6NVjAs',
   appId: '1:963656261848:ios:d9e01cfe8b675dfcb237ad',
   messagingSenderId: '963656261848',
   projectId: 'flutterfire-ui-codelab',
   storageBucket: 'flutterfire-ui-codelab.appspot.com',
   iosClientId: '963656261848-v7r3vq1v6haupv0l1mdrmsf56ktnua60.apps.googleusercontent.com',
   iosBundleId: 'com.example.complete',
 );
}

Firebase sử dụng từ ứng dụng để chỉ bản dựng cụ thể cho một nền tảng cụ thể trong dự án Firebase. Ví dụ: dự án Firebase có tên FlutterFire-ui-codelab có nhiều ứng dụng: một dành cho Android, một dành cho iOS, một dành cho MacOS và một dành cho Web.

Phương thức DefaultFirebaseOptions.currentPlatform sử dụng enum TargetPlatform do Flutter cung cấp để phát hiện nền tảng mà ứng dụng của bạn đang chạy, sau đó trả về các giá trị cấu hình Firebase cần thiết cho ứng dụng Firebase chính xác.

Thêm gói Firebase vào ứng dụng Flutter

Bước thiết lập cuối cùng là thêm các gói Firebase có liên quan vào dự án Flutter của bạn. Tệp firebase_options.dart chắc chắn có lỗi vì nó dựa trên các gói Firebase chưa được thêm vào. Trong thiết bị đầu cuối, hãy đảm bảo bạn đang ở trong thư mục gốc của dự án Flutter tại flutter-codelabs/firebase-emulator-suite/start . Sau đó, chạy ba lệnh sau:

flutter pub add firebase_core
flutter pub add firebase_auth
flutter pub add firebase_ui_auth

Đây là những gói duy nhất bạn cần vào thời điểm này.

Khởi tạo Firebase

Để sử dụng các gói đã thêm và DefaultFirebaseOptions.currentPlatform, hãy cập nhật mã trong hàm main trong tệp main.dart .

chính.dart

void main() async {
 WidgetsFlutterBinding.ensureInitialized();
 await Firebase.initializeApp(
   options: DefaultFirebaseOptions.currentPlatform,
 );


 runApp(const MyApp());
}

Mã này thực hiện hai việc.

  1. WidgetsFlutterBinding.ensureInitialized() yêu cầu Flutter không bắt đầu chạy mã widget ứng dụng cho đến khi Flutter framework được khởi động hoàn toàn. Firebase sử dụng các kênh nền tảng gốc, yêu cầu phải chạy khung.
  2. Firebase.initializeApp thiết lập kết nối giữa ứng dụng Flutter và dự án Firebase của bạn. DefaultFirebaseOptions.currentPlatform được nhập từ tệp firebase_options.dart được tạo của chúng tôi. Giá trị tĩnh này phát hiện bạn đang chạy trên nền tảng nào và chuyển vào các khóa Firebase tương ứng.

4. Thêm trang xác thực giao diện người dùng Firebase ban đầu

Giao diện người dùng Firebase cho Auth cung cấp các tiện ích đại diện cho toàn bộ màn hình trong ứng dụng của bạn. Các màn hình này xử lý các luồng xác thực khác nhau trong toàn bộ ứng dụng của bạn, chẳng hạn như Đăng nhập, Đăng ký, Quên mật khẩu, Hồ sơ người dùng, v.v. Để bắt đầu, hãy thêm trang đích vào ứng dụng của bạn, trang này hoạt động như một trình bảo vệ xác thực cho ứng dụng chính.

Ứng dụng Material hoặc Cupertino

Giao diện người dùng FlutterFire yêu cầu ứng dụng của bạn được gói trong MaterialApp hoặc CupertinoApp. Tùy thuộc vào lựa chọn của bạn, giao diện người dùng sẽ tự động phản ánh sự khác biệt của các tiện ích Material hoặc Cupertino. Đối với lớp học lập trình này, hãy sử dụng MaterialApp đã được thêm vào ứng dụng trong app.dart .

ứng dụng.dart

import 'package:flutter/material.dart';
import 'auth_gate.dart';

class MyApp extends StatelessWidget {
 const MyApp({super.key});
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     theme: ThemeData(
       primarySwatch: Colors.blue,
     ),
     home: const AuthGate(),
   );
 }
}

Kiểm tra trạng thái xác thực

Trước khi có thể hiển thị màn hình đăng nhập, bạn cần xác định xem người dùng hiện có được xác thực hay không. Cách phổ biến nhất để kiểm tra điều này là nghe authStateChanges của FirebaseAuth bằng cách sử dụng plugin Firebase Auth .

Trong mẫu mã ở trên, MaterialApp đang xây dựng tiện ích AuthGate theo phương thức xây dựng của nó. (Đây là một tiện ích tùy chỉnh, không được FlutterFire UI cung cấp.)

Tiện ích đó cần được cập nhật để bao gồm luồng authStateChanges .

API authStateChanges trả về Stream với người dùng hiện tại (nếu họ đã đăng nhập) hoặc null nếu không. Để đăng ký trạng thái này trong ứng dụng của chúng tôi, bạn có thể sử dụng tiện ích StreamBuilder của Flutter và truyền luồng tới nó.

StreamBuilder là một tiện ích tự xây dựng dựa trên ảnh chụp nhanh dữ liệu mới nhất từ ​​Luồng mà bạn chuyển nó. Nó tự động xây dựng lại khi Luồng phát ra ảnh chụp nhanh mới.

Cập nhật mã trong auth_gate.dart .

auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return SignInScreen(
            providers: [],
          );
        }

        return const HomeScreen();
      },
    );
  }
}
  • StreamBuilder.stream đang được thông qua FirebaseAuth.instance.authStateChanged , luồng nói trên sẽ trả về đối tượng User Firebase nếu người dùng đã xác thực. (Nếu không nó sẽ trả về null .)
  • Tiếp theo, mã đang sử dụng snapshot.hasData để kiểm tra xem giá trị từ luồng có chứa đối tượng User hay không.
  • Nếu không có, nó sẽ trả về tiện ích SignInScreen . Hiện tại, màn hình đó sẽ không làm gì cả. Điều này sẽ được cập nhật trong bước tiếp theo.
  • Nếu không, nó sẽ trả về HomeScreen , đây là phần chính của ứng dụng mà chỉ những người dùng được xác thực mới có thể truy cập.

SignInScreen là một tiện ích đến từ gói giao diện người dùng FlutterFire. Đây sẽ là trọng tâm của bước tiếp theo của lớp học lập trình này. Khi chạy ứng dụng vào thời điểm này, bạn sẽ thấy màn hình đăng nhập trống.

5. Màn hình đăng nhập

Tiện ích SignInScreen do FlutterFire UI cung cấp sẽ bổ sung thêm chức năng sau:

  • Cho phép người dùng đăng nhập
  • Nếu người dùng quên mật khẩu, họ có thể nhấn vào "Quên mật khẩu?" và được đưa đến một biểu mẫu để đặt lại mật khẩu của họ
  • Nếu người dùng chưa đăng ký, họ có thể nhấn "Đăng ký" và được đưa đến một biểu mẫu khác cho phép họ đăng ký.

Một lần nữa, điều này chỉ cần một vài dòng mã. Nhớ lại mã trong tiện ích AuthGate:

auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return SignInScreen(
            providers: [
              EmailAuthProvider(), // new
            ],
          );
        }

        return const HomeScreen();
      },
    );
  }
}

Tiện ích SignInScreen và đối số providers của nó là mã duy nhất cần có để có được tất cả chức năng nói trên. Bây giờ bạn sẽ thấy màn hình đăng nhập có đầu vào văn bản 'email' và 'mật khẩu' cũng như nút 'Đăng nhập'.

Mặc dù có chức năng nhưng nó thiếu kiểu dáng. Tiện ích hiển thị các thông số để tùy chỉnh giao diện của màn hình đăng nhập. Ví dụ: bạn có thể muốn thêm logo của công ty mình.

Tùy chỉnh màn hình đăng nhập

tiêu đềBuilder

Bằng cách sử dụng đối số SignInScreen.headerBuilder , bạn có thể thêm bất kỳ tiện ích nào bạn muốn phía trên biểu mẫu đăng nhập. Cập nhật tệp auth_gate.dart bằng mã này:

auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
 const AuthGate({super.key});

 @override
 Widget build(BuildContext context) {
   return StreamBuilder<User?>(
     stream: FirebaseAuth.instance.authStateChanges(),
     builder: (context, snapshot) {
       if (!snapshot.hasData) {
         return SignInScreen(
           providers: [
             EmailAuthProvider(),
           ],
           headerBuilder: (context, constraints, shrinkOffset) {
             return Padding(
               padding: const EdgeInsets.all(20),
               child: AspectRatio(
                 aspectRatio: 1,
                 child: Image.asset('assets/flutterfire_300x.png'),
               ),
             );
           },
         );
       }

       return const HomeScreen();
     },
   );
 }
}

Đối số headerBuilder yêu cầu một hàm thuộc loại HeaderBuilder, được xác định trong gói UI FlutterFire.

typedef HeaderBuilder = Widget Function(
 BuildContext context,
 BoxConstraints constraints,
 double shrinkOffset,
);

Vì là lệnh gọi lại nên nó hiển thị các giá trị bạn có thể sử dụng, chẳng hạn như BuildContextBoxConstraints , đồng thời yêu cầu bạn trả về một tiện ích. Bất kỳ tiện ích nào bạn trả lại sẽ được hiển thị ở đầu màn hình. Trong ví dụ này, mã mới sẽ thêm một hình ảnh vào đầu màn hình. Ứng dụng của bạn bây giờ sẽ trông như thế này.

73d7548d91bbd2ab.png

Trình tạo phụ đề

Màn hình đăng nhập hiển thị ba tham số bổ sung cho phép bạn tùy chỉnh màn hình: subtitleBuilder , footerBuildersideBuilder .

subtitleBuilder hơi khác một chút ở chỗ các đối số gọi lại bao gồm một hành động thuộc loại AuthAction . AuthAction là một enum mà bạn có thể sử dụng để phát hiện xem màn hình người dùng đang bật là màn hình "đăng nhập" hay màn hình "đăng ký".

Cập nhật mã trong auth_gate.dart để sử dụng subtitleBuilder.

auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
 const AuthGate({super.key});

 @override
 Widget build(BuildContext context) {
   return StreamBuilder<User?>(
     stream: FirebaseAuth.instance.authStateChanges(),
     builder: (context, snapshot) {
       if (!snapshot.hasData) {
         return SignInScreen(
           providers: [
              EmailAuthProvider()
           ],
           headerBuilder: (context, constraints, shrinkOffset) {
             return Padding(
               padding: const EdgeInsets.all(20),
               child: AspectRatio(
                 aspectRatio: 1,
                 child: Image.asset('flutterfire_300x.png'),
               ),
             );
           },
           subtitleBuilder: (context, action) {
             return Padding(
               padding: const EdgeInsets.symmetric(vertical: 8.0),
               child: action == AuthAction.signIn
                   ? const Text('Welcome to FlutterFire, please sign in!')
                   : const Text('Welcome to Flutterfire, please sign up!'),
             );
           },
         );
       }

       return const HomeScreen();
     },
   );
 }
}

Tải lại ứng dụng thì nó sẽ như thế này

Đối số footerBuilder giống với subtitleBuilder. Nó không hiển thị BoxConstraints hoặc shrinkOffset , vì nó dành cho văn bản chứ không phải hình ảnh. (Mặc dù bạn có thể thêm bất kỳ tiện ích nào bạn muốn.)

Thêm chân trang vào màn hình đăng nhập của bạn bằng mã này.

auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
 const AuthGate({super.key});

 @override
 Widget build(BuildContext context) {
   return StreamBuilder<User?>(
     stream: FirebaseAuth.instance.authStateChanges(),
     builder: (context, snapshot) {
       if (!snapshot.hasData) {
         return SignInScreen(
           providers: [
             EmailAuthProvider()
           ],
           headerBuilder: (context, constraints, shrinkOffset) {
             return Padding(
               padding: const EdgeInsets.all(20),
               child: AspectRatio(
                 aspectRatio: 1,
                 child: Image.asset('flutterfire_300x.png'),
               ),
             );
           },
           subtitleBuilder: (context, action) {
             return Padding(
               padding: const EdgeInsets.symmetric(vertical: 8.0),
               child: action == AuthAction.signIn
                   ? const Text('Welcome to FlutterFire, please sign in!')
                   : const Text('Welcome to Flutterfire, please sign up!'),
             );
           },
           footerBuilder: (context, action) {
             return const Padding(
               padding: EdgeInsets.only(top: 16),
               child: Text(
                 'By signing in, you agree to our terms and conditions.',
                 style: TextStyle(color: Colors.grey),
               ),
             );
           },
         );
       }

       return const HomeScreen();
     },
   );
 }}

Người xây dựng bên

Đối số SignInScreen.sidebuilder chấp nhận một lệnh gọi lại và lần này các đối số cho lệnh gọi lại đó là BuildContextdouble shrinkOffset . Tiện ích mà sideBuilder trả về sẽ được hiển thị ở bên trái biểu mẫu đăng nhập và chỉ trên màn hình rộng. Thực tế, điều đó có nghĩa là tiện ích sẽ chỉ được hiển thị trên máy tính để bàn và ứng dụng web.

Trong nội bộ, FlutterFire UI sử dụng điểm dừng để xác định xem nội dung tiêu đề sẽ được hiển thị (trên màn hình cao, như thiết bị di động) hay nội dung bên cạnh (trên màn hình rộng, máy tính để bàn hoặc web). Cụ thể, nếu màn hình rộng hơn 800 pixel thì nội dung trình tạo bên được hiển thị còn nội dung tiêu đề thì không. Nếu màn hình rộng dưới 800 pixel thì điều ngược lại là đúng.

Cập nhật mã trong auth_gate.dart để thêm tiện ích sideBuilder.

auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
 const AuthGate({super.key});

 @override
 Widget build(BuildContext context) {
   return StreamBuilder<User?>(
     stream: FirebaseAuth.instance.authStateChanges(),
     builder: (context, snapshot) {
       if (!snapshot.hasData) {
         return SignInScreen(
           providers: [
             EmailAuthProvider(),
           ],
           headerBuilder: (context, constraints, shrinkOffset) {
             return Padding(
               padding: const EdgeInsets.all(20),
               child: AspectRatio(
                 aspectRatio: 1,
                 child: Image.asset('flutterfire_300x.png'),
               ),
             );
           },
           subtitleBuilder: (context, action) {
             return Padding(
               padding: const EdgeInsets.symmetric(vertical: 8.0),
               child: action == AuthAction.signIn
                   ? const Text('Welcome to FlutterFire, please sign in!')
                   : const Text('Welcome to Flutterfire, please sign up!'),
             );
           },
           footerBuilder: (context, action) {
             return const Padding(
               padding: EdgeInsets.only(top: 16),
               child: Text(
                 'By signing in, you agree to our terms and conditions.',
                 style: TextStyle(color: Colors.grey),
               ),
             );
           },
           sideBuilder: (context, shrinkOffset) {
             return Padding(
               padding: const EdgeInsets.all(20),
               child: AspectRatio(
                 aspectRatio: 1,
                 child: Image.asset('flutterfire_300x.png'),
               ),
             );
           },
         );
       }
       return const HomeScreen();
     },
   );
 }
}

Ứng dụng của bạn bây giờ sẽ trông như thế này khi bạn mở rộng chiều rộng của cửa sổ (nếu bạn đang sử dụng web Flutter hoặc MacOS).

8dc60b4e5d7dd2d0.png

Tạo người dùng

Tại thời điểm này, tất cả mã cho màn hình này đã được thực hiện. Tuy nhiên, trước khi có thể đăng nhập, bạn cần tạo Người dùng. Bạn có thể thực hiện việc này bằng màn hình "Đăng ký" hoặc bạn có thể tạo người dùng trong bảng điều khiển Firebase.

Để sử dụng bảng điều khiển:

  1. Chuyển đến bảng "Người dùng" trong bảng điều khiển Firebase.
  2. Bấm vào đây
  3. Chọn 'flutterfire-ui-codelab' (hoặc dự án khác nếu bạn sử dụng tên khác). Bạn sẽ thấy bảng này:

f038fd9a58ed60d9.png

  1. Nhấp vào nút "Thêm người dùng".

2d78390d4c5dbbfa.png

  1. Nhập địa chỉ email và mật khẩu cho người dùng mới. Đây có thể là email và mật khẩu giả, như tôi đã nhập vào hình ảnh bên dưới. Điều đó sẽ có tác dụng nhưng chức năng "Quên mật khẩu" sẽ không hoạt động nếu bạn sử dụng địa chỉ email giả.

62ba0feb33d54add.png

  1. Nhấp vào "Thêm người dùng"

32b236b3ef94d4c7.png

Bây giờ, bạn có thể quay lại ứng dụng Flutter của mình và đăng nhập người dùng thông qua trang đăng nhập. Ứng dụng của bạn sẽ trông như thế này:

dd43d260537f3b1a.png

6. Màn hình hồ sơ

FlutterFire UI cũng cung cấp tiện ích ProfileScreen , một lần nữa, tiện ích này cung cấp cho bạn rất nhiều chức năng chỉ trong một vài dòng mã.

Thêm tiện ích ProfileScreen

Điều hướng đến tệp home.dart trong trình soạn thảo văn bản của bạn. Cập nhật nó với mã này:

nhà.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: const Icon(Icons.person),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute<ProfileScreen>(
                  builder: (context) => const ProfileScreen(),
                ),
              );
            },
          )
        ],
        automaticallyImplyLeading: false,
      ),
      body: Center(
        child: Column(
          children: [
            Image.asset('dash.png'),
            Text(
              'Welcome!',
              style: Theme.of(context).textTheme.displaySmall,
            ),
            const SignOutButton(),
          ],
        ),
      ),
    );
  }
}

Mã mới cần lưu ý là lệnh gọi lại được chuyển đến IconButton.isPressed method. Khi nhấn IconButton đó, ứng dụng của bạn sẽ tạo một tuyến ẩn danh mới và điều hướng đến tuyến đó. Tuyến đường đó sẽ hiển thị tiện ích ProfileScreen , được trả về từ lệnh gọi lại MaterialPageRoute.builder .

Tải lại ứng dụng của bạn và nhấn biểu tượng ở trên cùng bên phải (trong thanh ứng dụng) và nó sẽ hiển thị một trang như thế này:

36487fc4ab4f26a7.png

Đây là giao diện người dùng tiêu chuẩn được cung cấp bởi trang giao diện người dùng FlutterFire. Tất cả các nút và trường văn bản đều được kết nối với Firebase Auth và hoạt động ngay lập tức. Ví dụ: bạn có thể nhập tên vào trường văn bản "Tên" và giao diện người dùng FlutterFire sẽ gọi phương thức FirebaseAuth.instance.currentUser?.updateDisplayName , phương thức này sẽ lưu tên đó trong Firebase.

Đăng xuất

Hiện tại, nếu bạn nhấn nút "Đăng xuất", ứng dụng sẽ không thay đổi. Thao tác này sẽ đăng xuất bạn nhưng bạn sẽ không được điều hướng trở lại tiện ích AuthGate. Để thực hiện điều này, hãy sử dụng tham số ProfileScreen.actions.

Đầu tiên, hãy cập nhật mã trong home.dart.

nhà.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: const Icon(Icons.person),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute<ProfileScreen>(
                  builder: (context) => ProfileScreen(
                    actions: [
                      SignedOutAction((context) {
                        Navigator.of(context).pop();
                      })
                    ],
                  ),
                ),
              );
            },
          )
        ],
        automaticallyImplyLeading: false,
      ),
      body: Center(
        child: Column(
          children: [
            Image.asset('dash.png'),
            Text(
              'Welcome!',
              style: Theme.of(context).textTheme.displaySmall,
            ),
            const SignOutButton(),
          ],
        ),
      ),
    );
  }
}

Bây giờ, khi bạn tạo một phiên bản ProfileScreen , bạn cũng chuyển cho nó một danh sách các hành động cho đối số ProfileScreen.actions . Những hành động này thuộc loại FlutterFireUiAction . Có nhiều lớp khác nhau là kiểu con của FlutterFireUiAction và nói chung, bạn sử dụng chúng để yêu cầu ứng dụng của mình phản ứng với các thay đổi trạng thái xác thực khác nhau. SignedOutAction gọi hàm gọi lại mà bạn cung cấp khi trạng thái xác thực Firebase thay đổi thành CurrentUser là null.

Bằng cách thêm một lệnh gọi lại gọi Navigator.of(context).pop() khi SignedOutAction kích hoạt, ứng dụng sẽ điều hướng đến trang trước đó. Trong ứng dụng ví dụ này, chỉ có một tuyến cố định hiển thị trang đăng nhập nếu không có người dùng đăng nhập và trang chủ nếu có người dùng. Vì điều này xảy ra khi người dùng đăng xuất nên ứng dụng sẽ hiển thị trang Đăng nhập.

Tùy chỉnh trang hồ sơ

Tương tự như trang Đăng nhập, trang hồ sơ có thể tùy chỉnh. Đầu tiên, trang hiện tại của chúng tôi không có cách nào điều hướng trở lại trang chủ khi người dùng đã ở trang hồ sơ. Hãy khắc phục điều này bằng cách cung cấp cho tiện ích ProfileScreen một AppBar.

nhà.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
 const HomeScreen({super.key});

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       actions: [
         IconButton(
           icon: const Icon(Icons.person),
           onPressed: () {
             Navigator.push(
               context,
               MaterialPageRoute<ProfileScreen>(
                 builder: (context) => ProfileScreen(
                   appBar: AppBar(
                     title: const Text('User Profile'),
                   ),
                   actions: [
                     SignedOutAction((context) {
                       Navigator.of(context).pop();
                     })
                   ],
                 ),
               ),
             );
           },
         )
       ],
       automaticallyImplyLeading: false,
     ),
     body: Center(
       child: Column(
         children: [
           Image.asset('dash.png'),
           Text(
             'Welcome!',
             style: Theme.of(context).textTheme.displaySmall,
           ),
           const SignOutButton(),
         ],
       ),
     ),
   );
 }
}

Đối số ProfileScreen.appBar chấp nhận tiện ích AppBar từ gói Flutter Material, do đó, nó có thể được xử lý giống như bất kỳ AppBar nào khác mà bạn đã tạo và chuyển tới Scaffold . Trong ví dụ này, chức năng mặc định là tự động thêm nút "quay lại" được giữ nguyên và màn hình hiện có tiêu đề.

Thêm trẻ em vào màn hình hồ sơ

Tiện ích ProfileScreen cũng có một đối số tùy chọn có tên là trẻ em. Đối số này chấp nhận danh sách các tiện ích và các tiện ích đó sẽ được đặt theo chiều dọc bên trong tiện ích Cột đã được sử dụng nội bộ để xây dựng ProfileScreen. Tiện ích Cột này trong phương thức xây dựng ProfileScreen sẽ đặt các phần tử con mà bạn chuyển nó phía trên nút "Đăng xuất".

Cập nhật mã trong home.dart để hiển thị logo công ty tại đây, tương tự như màn hình đăng nhập.

nhà.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: const Icon(Icons.person),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute<ProfileScreen>(
                  builder: (context) => ProfileScreen(
                    appBar: AppBar(
                      title: const Text('User Profile'),
                    ),
                    actions: [
                      SignedOutAction((context) {
                        Navigator.of(context).pop();
                      })
                    ],
                    children: [
                      const Divider(),
                      Padding(
                        padding: const EdgeInsets.all(2),
                        child: AspectRatio(
                          aspectRatio: 1,
                          child: Image.asset('flutterfire_300x.png'),
                        ),
                      ),
                    ],
                  ),
                ),
              );
            },
          )
        ],
        automaticallyImplyLeading: false,
      ),
      body: Center(
        child: Column(
          children: [
            Image.asset('dash.png'),
            Text(
              'Welcome!',
              style: Theme.of(context).textTheme.displaySmall,
            ),
            const SignOutButton(),
          ],
        ),
      ),
    );
  }
}

Tải lại ứng dụng của bạn và bạn sẽ thấy điều này trên màn hình:

ebe5792b765dbf87.png

7. Đăng nhập Google Auth đa nền tảng

FlutterFire UI cũng cung cấp các tiện ích và chức năng để xác thực với các nhà cung cấp bên thứ 3, chẳng hạn như Google, Twitter, Facebook, Apple và Github.

Để tích hợp với xác thực của Google, hãy cài đặt plugin firebase_ui_oauth_google chính thức và các phần phụ thuộc của nó sẽ xử lý luồng xác thực gốc. Trong thiết bị đầu cuối, điều hướng đến thư mục gốc của dự án rung của bạn và nhập lệnh sau:

flutter pub add google_sign_in
flutter pub add firebase_ui_oauth_google

Bật nhà cung cấp đăng nhập Google

Tiếp theo, kích hoạt nhà cung cấp Google trong Bảng điều khiển Firebase :

  1. Điều hướng đến màn hình Nhà cung cấp dịch vụ đăng nhập xác thực trong bảng điều khiển.
  2. Nhấp vào "Thêm nhà cung cấp mới". 8286fb28be94bf30.png
  3. Chọn "Google". c4e28e6f4974be7f.png
  4. Chuyển đổi công tắc có nhãn "Bật" và nhấn "Lưu". e74ff86990763826.png
  5. Nếu một phương thức xuất hiện với thông tin về việc tải xuống các tệp cấu hình, hãy nhấp vào "Xong".
  6. Xác nhận rằng nhà cung cấp dịch vụ đăng nhập Google đã được thêm. 5329ce0543c90d95.png

Thêm nút đăng nhập Google

Khi đăng nhập bằng Google được bật, hãy thêm tiện ích cần thiết để hiển thị nút đăng nhập Google được cách điệu vào trang đăng nhập. Điều hướng đến tệp auth_gate.dart và cập nhật mã như sau:

auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:firebase_ui_oauth_google/firebase_ui_oauth_google.dart'; // new
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
 const AuthGate({super.key});

 @override
 Widget build(BuildContext context) {
   return StreamBuilder<User?>(
     stream: FirebaseAuth.instance.authStateChanges(),
     builder: (context, snapshot) {
       if (!snapshot.hasData) {
         return SignInScreen(
           providers: [
             EmailAuthProvider(),
             GoogleProvider(clientId: "YOUR_WEBCLIENT_ID"),  // new
           ],
           headerBuilder: (context, constraints, shrinkOffset) {
             return Padding(
               padding: const EdgeInsets.all(20),
               child: AspectRatio(
                 aspectRatio: 1,
                 child: Image.asset('flutterfire_300x.png'),
               ),
             );
           },
           subtitleBuilder: (context, action) {
             return Padding(
               padding: const EdgeInsets.symmetric(vertical: 8.0),
               child: action == AuthAction.signIn
                   ? const Text('Welcome to FlutterFire, please sign in!')
                   : const Text('Welcome to Flutterfire, please sign up!'),
             );
           },
           footerBuilder: (context, action) {
             return const Padding(
               padding: EdgeInsets.only(top: 16),
               child: Text(
                 'By signing in, you agree to our terms and conditions.',
                 style: TextStyle(color: Colors.grey),
               ),
             );
           },
           sideBuilder: (context, shrinkOffset) {
             return Padding(
               padding: const EdgeInsets.all(20),
               child: AspectRatio(
                 aspectRatio: 1,
                 child: Image.asset('flutterfire_300x.png'),
               ),
             );
           },
         );
       }

       return const HomeScreen();
     },
   );
 }
}

Mã mới duy nhất ở đây là việc bổ sung GoogleProvider(clientId: "YOUR_WEBCLIENT_ID") vào cấu hình tiện ích SignInScreen.

Sau khi thêm vào, hãy tải lại ứng dụng của bạn và bạn sẽ thấy nút đăng nhập Google.

aca71a46a011bfb5.png

Định cấu hình nút đăng nhập

Nút không hoạt động nếu không có cấu hình bổ sung. Nếu bạn đang phát triển với Flutter Web, đây là bước duy nhất bạn phải thêm để nó hoạt động. Các nền tảng khác yêu cầu các bước bổ sung, sẽ được thảo luận sau.

  1. Điều hướng đến trang Nhà cung cấp xác thực trong Bảng điều khiển Firebase .
  2. Nhấp vào nhà cung cấp Google. 9b3a325c5eca6e49.png
  3. Nhấp vào bảng mở rộng "Cấu hình SDK Web".
  4. Sao chép giá trị từ 'ID khách hàng web" 711a79f0d931c60f.png
  5. Quay lại trình soạn thảo văn bản của bạn và cập nhật phiên bản GoogleProvider trong tệp auth_gate.dart bằng cách chuyển ID này tới tham số có tên clientId .
GoogleProvider(
   clientId: "YOUR_WEBCLIENT_ID"
)

Sau khi nhập ID ứng dụng khách web, hãy tải lại ứng dụng của bạn. Khi bạn nhấn nút "Đăng nhập bằng Google", một cửa sổ mới sẽ xuất hiện (nếu bạn đang sử dụng web) hướng dẫn bạn qua quy trình đăng nhập Google. Ban đầu, nó trông như thế này:

14e73e3c9de704bb.png

Định cấu hình iOS

Để tính năng này hoạt động trên iOS, cần có một quy trình cấu hình bổ sung.

  1. Điều hướng đến màn hình Cài đặt dự án trong bảng điều khiển Firebase . Sẽ có một thẻ liệt kê các ứng dụng Firebase của bạn trông như thế này: fefa674acbf213cc.png
  2. Nhấp vào iOS. Lưu ý rằng tên ứng dụng của bạn sẽ khác với tên của tôi. Trường hợp của tôi nói "hoàn thành" thì bạn sẽ nói "bắt đầu", nếu bạn đã sử dụng dự án flutter-codelabs/firebase-auth-flutterfire-ui/start để làm theo lớp học lập trình này.
  3. Nhấp vào nút có nội dung "GoogleServices-Info.plist" để tải xuống tệp cấu hình cần thiết. f89b3192871dfbe3.png
  4. Kéo và thả tệp đã tải xuống vào thư mục có tên . /ios/Runner trong dự án Flutter của bạn.
  5. Mở Xcode bằng cách chạy lệnh đầu cuối sau từ thư mục gốc của dự án của bạn:

mở ios/Runner.xcworkspace

  1. Nhấp chuột phải vào thư mục Runner và chọn Add Files to "Runner". 858986063a4c5201.png
  2. Chọn GoogleService-Info.plist từ trình quản lý tệp.
  3. Quay lại trình soạn thảo văn bản của bạn (không phải Xcode), hãy thêm các thuộc tính CFBundleURLTypes bên dưới vào tệp [my_project]/ios/Runner/Info.plist.
<!-- Put me in the [my_project]/ios/Runner/Info.plist file -->
<!-- Google Sign-in Section -->
<key>CFBundleURLTypes</key>
<array>
        <dict>
                <key>CFBundleTypeRole</key>
                <string>Editor</string>
                <key>CFBundleURLSchemes</key>
                <array>
                        <!-- TODO Replace this value: -->
                        <!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
                        <string>com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn</string>
                </array>
        </dict>
</array>
<!-- End of the Google Sign-in Section -->

Nếu ứng dụng Flutter của bạn đã chạy trên iOS, bạn phải tắt hoàn toàn ứng dụng đó rồi chạy lại ứng dụng. Nếu không, hãy chạy ứng dụng trong iOS.

8. Xin chúc mừng!

Bạn đã hoàn thành giao diện người dùng xác thực Firebase cho lớp học lập trình Flutter. Bạn có thể tìm thấy mã hoàn chỉnh cho Codelab này trong thư mục "hoàn chỉnh" trên github: Flutter Codelabs

Những gì chúng tôi đã đề cập

  • Thiết lập ứng dụng Flutter để sử dụng Firebase
  • Thiết lập dự án Firebase trong bảng điều khiển Firebase
  • FlutterFire CLI
  • Firebase CLI
  • Sử dụng xác thực Firebase
  • Sử dụng FlutterFire UI để xử lý xác thực Firebase trong ứng dụng Flutter của bạn một cách dễ dàng

Bước tiếp theo

Tìm hiểu thêm

Sparky ở đây để ăn mừng cùng bạn!

2a0ad195769368b1.gif