Flutter và Firebase năm 2026: Xác thực, Firestore và Câu hỏi Phỏng vấn

Tìm hiểu chuyên sâu Flutter Firebase: xác thực với firebase_auth, CRUD Firestore và stream thời gian thực, quy tắc bảo mật, cùng các câu hỏi phỏng vấn thường gặp kèm ví dụ mã.

Tích hợp Flutter và Firebase minh họa kiến trúc xác thực và cơ sở dữ liệu Firestore

Tích hợp Flutter Firebase vẫn là lựa chọn backend phổ biến nhất cho các ứng dụng Flutter vào năm 2026, hỗ trợ xác thực, cơ sở dữ liệu thời gian thực và cloud functions trên hàng triệu ứng dụng. Với FlutterFire SDK v4.15, bộ công cụ đã trưởng thành đáng kể — từ thiết lập gọn gàng qua flutterfire configure đến hỗ trợ hạng nhất cho Firestore Pipelines và Firebase Admin Dart SDK.

FlutterFire SDK v4.15 (2026)

Bản phát hành FlutterFire mới nhất đi kèm firebase_auth v6.1.2 và cloud_firestore v6.4.1, hỗ trợ Firestore Pipelines, xác thực TOTP trên macOS, và API validatePassword để áp dụng chính sách mật khẩu.

Thiết Lập Firebase Trong Dự Án Flutter

Trước khi viết bất kỳ mã Firebase nào, dự án cần được khởi tạo đúng cách. FlutterFire CLI tự động đăng ký nền tảng và tạo các tệp cấu hình cho Android, iOS, web và macOS.

main.dartdart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Initialize Firebase with platform-specific config
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

Tệp firebase_options.dart được tạo tự động khi chạy flutterfire configure. Nó chứa khóa API, ID dự án và các định danh riêng cho từng nền tảng — không cần sao chép thủ công từ Firebase console.

Firebase Authentication: Email, Google và Đa Yếu Tố

Firebase Authentication xử lý danh tính người dùng với mã soạn sẵn tối thiểu. Gói firebase_auth hỗ trợ email/mật khẩu, các nhà cung cấp OAuth (Google, Apple, GitHub), xác minh SMS qua điện thoại và xác thực đa yếu tố.

Đăng Ký Bằng Email và Mật Khẩu

Luồng phổ biến nhất bắt đầu với email/mật khẩu. Phương thức createUserWithEmailAndPassword trả về một UserCredential chứa đối tượng User đã được xác thực.

auth_service.dartdart
import 'package:firebase_auth/firebase_auth.dart';

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  // Register with email and password
  Future<User?> register(String email, String password) async {
    try {
      final credential = await _auth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
      return credential.user;
    } on FirebaseAuthException catch (e) {
      // Handle specific error codes
      switch (e.code) {
        case 'email-already-in-use':
          throw Exception('This email is already registered');
        case 'weak-password':
          throw Exception('Password must be at least 6 characters');
        default:
          throw Exception('Registration failed: ${e.message}');
      }
    }
  }

  // Sign in with existing credentials
  Future<User?> signIn(String email, String password) async {
    final credential = await _auth.signInWithEmailAndPassword(
      email: email,
      password: password,
    );
    return credential.user;
  }

  // Reactive auth state stream
  Stream<User?> get authStateChanges => _auth.authStateChanges();
}

Stream authStateChanges() là cách được khuyến nghị để theo dõi trạng thái đăng nhập trên toàn ứng dụng. Nó phát ra User hiện tại khi đăng ký lắng nghe và kích hoạt lại trên mỗi sự kiện đăng nhập hoặc đăng xuất.

Tích Hợp Google Sign-In

Các nhà cung cấp OAuth cần thêm một gói riêng. Google Sign-In kích hoạt bộ chọn tài khoản native, sau đó trao đổi token với Firebase.

google_auth.dartdart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

Future<UserCredential> signInWithGoogle() async {
  // Trigger the native Google Sign-In flow
  final googleUser = await GoogleSignIn().signIn();
  if (googleUser == null) throw Exception('Sign-in cancelled');

  // Obtain auth details from the Google account
  final googleAuth = await googleUser.authentication;

  // Create a Firebase credential from the Google tokens
  final credential = GoogleAuthProvider.credential(
    accessToken: googleAuth.accessToken,
    idToken: googleAuth.idToken,
  );

  // Sign in to Firebase with the Google credential
  return FirebaseAuth.instance.signInWithCredential(credential);
}

Mẫu này áp dụng cho tất cả nhà cung cấp OAuth: lấy token của nhà cung cấp, bọc nó trong một credential Firebase, rồi gọi signInWithCredential.

Xác Thực Chính Sách Mật Khẩu

Firebase Auth v6.1+ bao gồm API validatePassword, cho phép áp dụng chính sách mật khẩu phía máy chủ. Chính sách có thể yêu cầu độ dài tối thiểu, chữ hoa, chữ thường, chữ số và ký tự đặc biệt — tất cả đều có thể cấu hình từ Firebase console.

Các Thao Tác CRUD Firestore và Mô Hình Hóa Dữ Liệu

Cloud Firestore tổ chức dữ liệu thành các collection và document. Không giống cơ sở dữ liệu SQL, Firestore không có schema — mỗi document có thể chứa các trường khác nhau. Sự linh hoạt này phù hợp với ứng dụng Flutter, nhưng đòi hỏi mô hình hóa dữ liệu kỷ luật để tránh các vấn đề về hiệu năng.

Ghi và Đọc Document

Các thao tác Firestore rất đơn giản với gói cloud_firestore. Mỗi lần đọc và ghi đều nhắm đến một đường dẫn document cụ thể.

firestore_service.dartdart
import 'package:cloud_firestore/cloud_firestore.dart';

class TaskService {
  final _db = FirebaseFirestore.instance;
  final String _collection = 'tasks';

  // Create a new document with auto-generated ID
  Future<String> createTask(String userId, String title) async {
    final doc = await _db.collection(_collection).add({
      'userId': userId,
      'title': title,
      'completed': false,
      'createdAt': FieldValue.serverTimestamp(),
    });
    return doc.id;
  }

  // Read a single document by ID
  Future<Map<String, dynamic>?> getTask(String taskId) async {
    final snapshot = await _db.collection(_collection).doc(taskId).get();
    return snapshot.data();
  }

  // Update specific fields without overwriting the entire document
  Future<void> toggleComplete(String taskId, bool completed) async {
    await _db.collection(_collection).doc(taskId).update({
      'completed': completed,
      'updatedAt': FieldValue.serverTimestamp(),
    });
  }

  // Delete a document
  Future<void> deleteTask(String taskId) async {
    await _db.collection(_collection).doc(taskId).delete();
  }
}

FieldValue.serverTimestamp() đảm bảo timestamp nhất quán trên mọi thiết bị bằng cách dùng đồng hồ máy chủ Firestore thay vì đồng hồ máy khách.

Stream Thời Gian Thực Với snapshots()

Firestore tỏa sáng với đồng bộ hóa thời gian thực. Phương thức snapshots() trả về một Stream phát ra dữ liệu mới mỗi khi các document bên dưới thay đổi — không cần polling.

real_time_tasks.dartdart
import 'package:cloud_firestore/cloud_firestore.dart';

class TaskStream {
  final _db = FirebaseFirestore.instance;

  // Stream all tasks for a specific user, ordered by creation date
  Stream<List<Map<String, dynamic>>> userTasks(String userId) {
    return _db
        .collection('tasks')
        .where('userId', isEqualTo: userId)
        .orderBy('createdAt', descending: true)
        .snapshots()
        .map((snapshot) => snapshot.docs.map((doc) {
              final data = doc.data();
              data['id'] = doc.id; // Include document ID
              return data;
            }).toList());
  }
}

Trong một widget Flutter, stream này tích hợp trực tiếp với StreamBuilder để dựng lại UI trên mỗi thay đổi cơ sở dữ liệu. Firestore tự động xử lý quản lý kết nối, caching và lưu trữ ngoại tuyến.

Sẵn sàng chinh phục phỏng vấn Flutter?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Quy Tắc Bảo Mật Firestore Cho Ứng Dụng Flutter

Quy tắc bảo mật chạy trên máy chủ Firestore và kiểm soát quyền đọc/ghi ở cấp độ document. Nếu không có quy tắc phù hợp, bất kỳ người dùng nào đã xác thực đều có thể đọc hoặc sửa đổi bất kỳ document nào.

firestore.rulesjavascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Users can only access their own profile
    match /users/{userId} {
      allow read, update: if request.auth != null
                          && request.auth.uid == userId;
      allow create: if request.auth != null;
      allow delete: if false; // Prevent self-deletion
    }

    // Tasks belong to the user who created them
    match /tasks/{taskId} {
      allow read, write: if request.auth != null
                         && resource.data.userId == request.auth.uid;
      allow create: if request.auth != null
                    && request.resource.data.userId == request.auth.uid;
    }
  }
}

Biến request.auth.uid chứa ID của người dùng đã xác thực từ Firebase Auth. Các quy tắc tham chiếu resource.data kiểm tra các trường document hiện có, trong khi request.resource.data xác thực dữ liệu ghi đang đến.

Quy Tắc Bảo Mật Không Phải Tùy Chọn

Firestore mặc định từ chối mọi quyền truy cập ở chế độ production. Mỗi collection cần quy tắc rõ ràng. Một lỗi phổ biến trong các dự án Flutter: phát triển với quy tắc chế độ thử nghiệm (allow read, write: if true) và quên khóa lại trước khi triển khai production.

Lưu Trữ Ngoại Tuyến và Chiến Lược Caching

Firestore bật lưu trữ ngoại tuyến theo mặc định trên các nền tảng di động. Khi thiết bị mất kết nối, các thao tác đọc trả về dữ liệu đã được cache và các thao tác ghi xếp hàng cục bộ cho đến khi kết nối được khôi phục.

Hành vi này trong suốt đối với mã Flutter — vẫn cùng các lệnh gọi get()snapshots() hoạt động khi ngoại tuyến. Tuy nhiên, hai điểm cần lưu ý cho ứng dụng production:

  • Kích thước cache: Firestore cache tất cả document mà client đã đọc. Với các ứng dụng nhiều dữ liệu, hãy cấu hình Settings(cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED) hoặc đặt một giới hạn cụ thể. Bản phát hành v6.4.1 đã sửa một lỗi khi cache không giới hạn không được áp dụng đúng trên iOS.
  • Chỉ báo ghi đang chờ: Dùng SnapshotMetadata.hasPendingWrites để cho người dùng biết khi nào các thay đổi cục bộ chưa được đồng bộ lên máy chủ.
offline_aware_widget.dartdart
StreamBuilder<DocumentSnapshot>(
  stream: FirebaseFirestore.instance
      .collection('tasks')
      .doc(taskId)
      .snapshots(),
  builder: (context, snapshot) {
    if (!snapshot.hasData) return const CircularProgressIndicator();

    final data = snapshot.data!;
    final isPending = data.metadata.hasPendingWrites;

    return Row(
      children: [
        Text(data['title']),
        if (isPending) const Icon(Icons.cloud_upload, size: 16),
      ],
    );
  },
)

Cấu Trúc Một Ứng Dụng Flutter Firebase Cho Production

Một kiến trúc có khả năng mở rộng tách biệt logic Firebase khỏi mã UI. Mẫu repository cung cấp một lớp trừu tượng rõ ràng giúp đơn giản hóa việc kiểm thử và di chuyển backend trong tương lai.

task_repository.dartdart
abstract class TaskRepository {
  Future<String> create(String userId, String title);
  Stream<List<Task>> watchAll(String userId);
  Future<void> update(String id, Map<String, dynamic> fields);
  Future<void> delete(String id);
}

// firebase_task_repository.dart
class FirebaseTaskRepository implements TaskRepository {
  final _db = FirebaseFirestore.instance;

  
  Future<String> create(String userId, String title) async {
    final doc = await _db.collection('tasks').add({
      'userId': userId,
      'title': title,
      'completed': false,
      'createdAt': FieldValue.serverTimestamp(),
    });
    return doc.id;
  }

  
  Stream<List<Task>> watchAll(String userId) {
    return _db
        .collection('tasks')
        .where('userId', isEqualTo: userId)
        .orderBy('createdAt', descending: true)
        .snapshots()
        .map((s) => s.docs.map(Task.fromFirestore).toList());
  }

  // ... update and delete implementations
}

Với mẫu này, các giải pháp quản lý state như Riverpod hoặc BLoC sử dụng giao diện repository mà không cần biết về Firestore. Các bài kiểm thử đơn vị có thể thay thế bằng một mock repository trả về dữ liệu định trước.

Các Câu Hỏi Phỏng Vấn Flutter Firebase Thường Gặp

Các buổi phỏng vấn thường kiểm tra cả hiểu biết khái niệm lẫn triển khai thực tế các dịch vụ Firebase. Những câu hỏi sau xuất hiện thường xuyên trong các buổi phỏng vấn lập trình viên Flutter năm 2026.

authStateChanges() khác với idTokenChanges()userChanges() như thế nào?

authStateChanges() chỉ kích hoạt trên các sự kiện đăng nhập và đăng xuất. idTokenChanges() còn kích hoạt khi ID token được làm mới (khoảng mỗi 60 phút). userChanges() ghi nhận tất cả những điều trên cộng với cập nhật hồ sơ như updateDisplayName(). Với hầu hết navigation guard, authStateChanges() là đủ.

Điều gì xảy ra khi một thao tác ghi Firestore diễn ra lúc thiết bị ngoại tuyến?

Thao tác ghi được lưu trong cache cục bộ và một hàng đợi ghi đang chờ. Các listener snapshots() ngay lập tức nhận dữ liệu cập nhật với metadata.hasPendingWrites == true. Khi kết nối được khôi phục, Firestore tự động đồng bộ các thao tác ghi đang chờ bằng cách giải quyết xung đột last-writer-wins ở cấp độ trường.

Nên mô hình hóa dữ liệu Firestore như thế nào cho một ứng dụng chat?

Một cách tiếp cận phổ biến: một collection chats trong đó mỗi document chứa metadata (người tham gia, timestamp tin nhắn cuối), và một subcollection messages dưới mỗi document chat. Cấu trúc này cho phép truy vấn danh sách chat của người dùng một cách hiệu quả đồng thời phân trang tin nhắn trong từng cuộc trò chuyện. Hãy phi chuẩn hóa tin nhắn cuối vào document cha để tránh đọc subcollection cho khung nhìn danh sách.

Tại sao nên ưu tiên FieldValue.serverTimestamp() hơn DateTime.now()?

DateTime.now() dùng đồng hồ thiết bị, vốn có thể không chính xác hoặc bị thao túng. FieldValue.serverTimestamp() dùng đồng hồ máy chủ Firestore, đảm bảo thứ tự nhất quán trên mọi client. Điều này quan trọng với các tính năng như sắp xếp tin nhắn, bảng tin hoạt động và nhật ký kiểm toán.

Các truy vấn phức hợp được xử lý như thế nào trong Firestore, và yêu cầu về chỉ mục là gì?

Firestore yêu cầu một chỉ mục phức hợp cho các truy vấn kết hợp nhiều trường với mệnh đề whereorderBy. Chỉ mục một trường được tạo tự động, nhưng chỉ mục phức hợp phải được định nghĩa thủ công trong firestore.indexes.json hoặc qua Firebase console. Firestore ghi lại một liên kết trực tiếp để tạo chỉ mục còn thiếu khi một truy vấn thất bại.

Để chuẩn bị phỏng vấn Flutter sâu hơn, các module luyện tập chuyên biệt bao quát các chủ đề này với câu hỏi tương tác và thử thách tính giờ.

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Kết Luận

  • FlutterFire SDK v4.15 đi kèm các gói ổn định cho xác thực (v6.1.2) và Firestore (v6.4.1), với các tính năng mới như Firestore Pipelines và xác thực chính sách mật khẩu
  • authStateChanges() cung cấp một stream phản ứng để quản lý trạng thái đăng nhập trên toàn ứng dụng mà không cần kiểm tra thủ công
  • Các thao tác CRUD Firestore tuân theo một mẫu đơn giản: add, get, update, delete trên các tham chiếu collection và document
  • Đồng bộ hóa thời gian thực qua snapshots() loại bỏ polling và tích hợp trực tiếp với StreamBuilder để cập nhật UI tự động
  • Quy tắc bảo mật là bắt buộc cho production — luôn xác thực request.auth.uid đối chiếu với các trường quyền sở hữu document
  • Lưu trữ ngoại tuyến hoạt động trong suốt trên di động; dùng hasPendingWrites để truyền đạt trạng thái đồng bộ cho người dùng
  • Mẫu repository tách Firebase khỏi logic nghiệp vụ, giúp kiểm thử và di chuyển trong tương lai trở nên đơn giản
  • Việc chuẩn bị phỏng vấn nên bao quát sự khác biệt giữa các stream auth, hành vi ngoại tuyến, các mẫu mô hình hóa dữ liệu và yêu cầu chỉ mục phức hợp

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thẻ

#flutter
#firebase
#authentication
#firestore
#dart
#mobile development

Chia sẻ

Bài viết liên quan