Flutter та Firebase у 2026: Автентифікація, Firestore та питання на співбесідах

Вичерпний посібник з інтеграції Flutter та Firebase у 2026 році — автентифікація, CRUD-операції у Firestore, синхронізація в реальному часі та найпоширеніші питання на технічних співбесідах.

Flutter та Firebase у 2026: Автентифікація, Firestore та питання на співбесідах

Інтеграція Flutter з Firebase залишається найпопулярнішим backend-рішенням для мобільних додатків у 2026 році, забезпечуючи автентифікацію, бази даних реального часу та хмарні функції в мільйонах застосунків. З виходом FlutterFire SDK v4.15 інструментарій суттєво дозрів — від спрощеного налаштування через flutterfire configure до нативної підтримки Firestore Pipelines та Firebase Admin Dart SDK.

FlutterFire SDK v4.15 (2026)

Останній реліз FlutterFire містить firebase_auth v6.1.2 та cloud_firestore v6.4.1 з підтримкою Firestore Pipelines, TOTP-автентифікації на macOS та API validatePassword для застосування політик паролів.

Налаштування Firebase в проекті Flutter

Перед написанням будь-якого коду Firebase проект потребує коректної ініціалізації. FlutterFire CLI автоматизує реєстрацію платформи та генерує конфігураційні файли для Android, iOS, web та 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());
}

Файл firebase_options.dart генерується автоматично після запуску flutterfire configure. Він містить API-ключі, ідентифікатори проекту та специфічні для платформи ідентифікатори — без необхідності ручного копіювання з консолі Firebase.

Автентифікація Firebase: Email, Google та багатофакторна автентифікація

Firebase Authentication керує ідентифікацією користувачів з мінімальним шаблонним кодом. Пакет firebase_auth підтримує email/пароль, провайдерів OAuth (Google, Apple, GitHub), телефонну верифікацію через SMS та багатофакторну автентифікацію (MFA).

Реєстрація за допомогою email та пароля

Найпоширеніший потік починається з email/пароль. Метод createUserWithEmailAndPassword повертає об'єкт UserCredential, що містить автентифікований об'єкт User.

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();
}

Потік authStateChanges() — це рекомендований спосіб відстежувати стан авторизації в усьому додатку. Він емітує поточного користувача User при підписці та спрацьовує знову при кожному вході або виході.

Інтеграція входу через Google

Провайдери OAuth потребують додаткового пакета. Вхід через Google запускає нативний вибирач облікових записів, а потім обмінює токен з 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);
}

Цей патерн застосовується до всіх провайдерів OAuth: отримати токен провайдера, загорнути його в credential Firebase, а потім викликати signInWithCredential.

Валідація політики паролів

Firebase Auth v6.1+ містить API validatePassword, що дозволяє застосовувати політики паролів на стороні сервера. Політики можуть вимагати мінімальної довжини, великих та малих літер, цифр та спеціальних символів — усе налаштовується з консолі Firebase.

CRUD-операції у Firestore та моделювання даних

Cloud Firestore організовує дані в колекції та документи. На відміну від SQL-баз даних, Firestore не має схеми — кожен документ може містити різні поля. Ця гнучкість добре підходить для додатків Flutter, але вимагає дисциплінованого моделювання даних для уникнення проблем з продуктивністю.

Запис та читання документів

Операції Firestore є простими з пакетом cloud_firestore. Кожне читання та запис спрямоване на конкретний шлях документа.

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() забезпечує послідовні мітки часу на всіх пристроях, використовуючи годинник сервера Firestore замість годинника клієнта.

Потоки реального часу з snapshots()

Firestore відзначається синхронізацією в реальному часі. Метод snapshots() повертає Stream, що емітує нові дані щоразу, коли базові документи змінюються — без необхідності опитування.

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());
  }
}

У віджеті Flutter цей потік інтегрується безпосередньо зі StreamBuilder для перебудови інтерфейсу при кожній зміні в базі даних. Firestore автоматично керує з'єднаннями, кешуванням та офлайн-зберіганням.

Готовий до співбесід з Flutter?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Правила безпеки Firestore для додатків Flutter

Правила безпеки працюють на сервері Firestore та контролюють доступ до читання/запису на рівні документа. Без належних правил будь-який автентифікований користувач міг би прочитати або змінити будь-який документ.

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

Змінна request.auth.uid містить ідентифікатор автентифікованого користувача з Firebase Auth. Правила, що посилаються на resource.data, перевіряють існуючі поля документа, а request.resource.data валідує вхідні дані запису.

Правила безпеки не є необов'язковими

Firestore за замовчуванням відхиляє весь доступ у виробничому режимі. Кожна колекція потребує явних правил. Поширена помилка в проектах Flutter: розробка з правилами тестового режиму (allow read, write: if true) та забуття про захист перед виробничим розгортанням.

Офлайн-зберігання та стратегія кешування

Firestore за замовчуванням вмикає офлайн-зберігання на мобільних платформах. Коли пристрій втрачає з'єднання, операції читання повертають дані з кешу, а операції запису ставляться в локальну чергу до відновлення з'єднання.

Ця поведінка є прозорою для коду Flutter — ті самі виклики get() та snapshots() працюють офлайн. Проте для виробничих додатків важливі два аспекти:

  • Розмір кешу: Firestore кешує всі документи, які клієнт прочитав. Для додатків з великим обсягом даних варто налаштувати Settings(cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED) або встановити конкретний ліміт. Реліз v6.4.1 виправив баг, де необмежений кеш не застосовувався коректно на iOS.
  • Індикатор очікуваних записів: SnapshotMetadata.hasPendingWrites дозволяє показати користувачам, коли локальні зміни ще не синхронізовані з сервером.
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),
      ],
    );
  },
)

Архітектура виробничого додатку Flutter з Firebase

Масштабована архітектура відокремлює логіку Firebase від коду інтерфейсу. Патерн репозиторію забезпечує чисту абстракційну верстку, що спрощує тестування та майбутні міграції бекенду.

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
}

З цим патерном рішення для керування станом, такі як Riverpod або BLoC, споживають інтерфейс репозиторію без відомості про Firestore. Модульні тести можуть підставити мок репозиторію, що повертає заздалегідь визначені дані.

Поширені питання на співбесідах: Flutter та Firebase

Співбесіди часто перевіряють як концептуальне розуміння, так і практичну реалізацію сервісів Firebase. Наступні питання регулярно з'являються на співбесідах з розробниками Flutter у 2026 році.

Чим authStateChanges() відрізняється від idTokenChanges() та userChanges()?

authStateChanges() спрацьовує лише при подіях входу та виходу. idTokenChanges() також спрацьовує при оновленні токена ідентифікації (приблизно кожних 60 хвилин). userChanges() охоплює все вищевказане плюс оновлення профілю, такі як updateDisplayName(). Для більшості навігаційних захистів достатньо authStateChanges().

Що відбувається, коли запис у Firestore здійснюється, коли пристрій в офлайні?

Запис зберігається в локальному кеші та черзі очікуваних записів. Лістенери snapshots() негайно отримують оновлені дані з metadata.hasPendingWrites == true. Коли з'єднання відновлюється, Firestore автоматично синхронізує очікувані записи, використовуючи розв'язання конфліктів «останній запис перемагає» на рівні поля.

Як слід моделювати дані Firestore для додатку чату?

Поширений підхід: колекція chats, де кожен документ містить метадані (учасники, мітка часу останнього повідомлення), та підколекція messages під кожним документом чату. Ця структура дозволяє ефективно запитувати список чатів користувача, одночасно сторінкуючи повідомлення в кожній розмові. Денормалізація останнього повідомлення в батьківський документ дозволяє уникнути читання підколекції при відображенні списку.

Чому FieldValue.serverTimestamp() переважніший над DateTime.now()?

DateTime.now() використовує годинник пристрою, який може бути неточним або зманіпульованим. FieldValue.serverTimestamp() використовує годинник сервера Firestore, гарантуючи послідовне сортування на всіх клієнтах. Це важливо для таких функцій, як порядок повідомлень, стрічки активності та журнали аудиту.

Як обробляються складені запити у Firestore та які вимоги до індексів?

Firestore вимагає складного індексу для запитів, що поєднують кілька полів з клаузулами where та orderBy. Однопольні індекси створюються автоматично, але складені індекси треба визначити вручну у файлі firestore.indexes.json або через консоль Firebase. Коли запит завершується невдачею, Firestore логує пряме посилання для створення відсутнього індексу.

Більше матеріалів для підготовки до співбесід з Flutter та Firebase доступно в інтерактивних модулях, які охоплюють ці теми з детальними поясненнями.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Теги

#flutter
#firebase
#автентифікація
#firestore
#dart
#мобільна розробка

Поділитися

Пов'язані статті