Рубрики
Flutter

Firebase во Flutter: Полное руководство 2025

Полное руководство по интеграции Firebase во Flutter: Authentication, Firestore, Cloud Functions, Push Notifications.

Firebase — это облачная платформа от Google, которая предоставляет полный бэкенд для мобильных и веб-приложений. В этом руководстве мы разберём полную интеграцию Firebase во Flutter приложение, от настройки проекта до продвинутых функций.

Что такое Firebase?

Firebase — это Backend-as-a-Service (BaaS) платформа, которая предоставляет готовые решения для common задач мобильной разработки:

  • Authentication — готовая система аутентификации с множеством провайдеров
  • Cloud Firestore — NoSQL база данных real-time
  • Cloud Functions — serverless функции на Node.js
  • Cloud Storage — хранилище файлов (изображения, видео, документы)
  • Cloud Messaging — push-уведомления
  • Analytics — аналитика пользователей
  • Crashlytics — отчёты о крашах

Преимущества Firebase

  • Быстрый старт — не нужно настраивать сервер
  • Real-time — мгновенное обновление данных
  • Масштабируемость — автоматическое масштабирование
  • Бесплатный tier — generous free plan для небольших проектов
  • Интеграция с Google —无缝 интеграция с Google сервисами

Настройка проекта

1. Создание проекта в Firebase Console

  1. Перейдите на Firebase Console
  2. Нажмите «Add project»
  3. Введите название проекта и включите Google Analytics (опционально)

2. Добавление Flutter приложений

Для Android:

  1. В Firebase Console выберите «Add app» → Android
  2. Введите package name (например, com.example.myapp)
  3. Скачайте google-services.json
  4. Положите файл в android/app/

Для iOS:

  1. В Firebase Console выберите «Add app» → iOS
  2. Введите bundle ID (например, com.example.myapp)
  3. Скачайте GoogleService-Info.plist
  4. Положите файл в ios/Runner/

3. Зависимости

dependencies:
  firebase_core: ^3.0.0
  firebase_auth: ^5.0.0
  cloud_firestore: ^5.0.0
  firebase_storage: ^12.0.0
  firebase_messaging: ^15.0.0
  firebase_analytics: ^11.0.0
  google_sign_in: ^6.2.0

Выполните flutter pub get.

Инициализация Firebase

Перед использованием任何 Firebase сервиса нужно инициализировать Firebase в приложении:

import 'package:firebase_core/firebase_core.dart';

Future<void> main() async {
  // Обязательно для Firebase
  WidgetsFlutterBinding.ensureInitialized();

  // Инициализация Firebase
  await Firebase.initializeApp();

  runApp(MyApp());
}

Если вы используете несколько Firebase проектов или специфическую конфигурацию:

await Firebase.initializeApp(
  options: DefaultFirebaseOptions.currentPlatform,
);

Класс DefaultFirebaseOptions генерируется автоматически при добавлении Flutter приложений в Firebase Console через FlutterFire CLI.

Authentication

Firebase Authentication поддерживает множество провайдеров: email/password, Google, Facebook, Apple, GitHub, телефон и другие.

Email/Password аутентификация

Сначала включите этот провайдер в Firebase Console → Authentication → Sign-in method.

import 'package:firebase_auth/firebase_auth.dart';

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  // Текущий пользователь
  User? get currentUser => _auth.currentUser;

  // Stream изменений аутентификации
  Stream<User?> get authStateChanges => _auth.authStateChanges();

  // Регистрация
  Future<Result<UserCredential>> register(
    String email,
    String password,
  ) async {
    try {
      final credential = await _auth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );

      return Result.success(credential);
    } on FirebaseAuthException catch (e) {
      return Result.failure(e.message ?? 'Registration failed');
    } catch (e) {
      return Result.failure('An error occurred');
    }
  }

  // Вход
  Future<Result<UserCredential>> login(
    String email,
    String password,
  ) async {
    try {
      final credential = await _auth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );

      return Result.success(credential);
    } on FirebaseAuthException catch (e) {
      switch (e.code) {
        case 'user-not-found':
          return Result.failure('User not found');
        case 'wrong-password':
          return Result.failure('Wrong password');
        default:
          return Result.failure(e.message ?? 'Login failed');
      }
    } catch (e) {
      return Result.failure('An error occurred');
    }
  }

  // Выход
  Future<void> logout() async {
    await _auth.signOut();
  }

  // Сброс пароля
  Future<Result<void>> resetPassword(String email) async {
    try {
      await _auth.sendPasswordResetEmail(email);
      return Result.success(null);
    } catch (e) {
      return Result.failure('Failed to send reset email');
    }
  }
}

// Helper класс для результата
class Result<T> {
  final bool isSuccess;
  final T? data;
  final String? error;

  Result.success(this.data)
      : isSuccess = true,
        error = null;

  Result.failure(this.error)
      : isSuccess = false,
        data = null;
}

Google Sign-In

Google Sign-In — один из самых удобных способов аутентификации.

import 'package:google_sign_in/google_sign_in.dart';

class GoogleAuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final GoogleSignIn _googleSignIn = GoogleSignIn();

  Future<Result<UserCredential>> signInWithGoogle() async {
    try {
      // 1. Запускаем Google Sign-In
      final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();

      if (googleUser == null) {
        // Пользователь отменил вход
        return Result.failure('Sign-in cancelled');
      }

      // 2. Получаем authentication данные
      final GoogleSignInAuthentication googleAuth =
          await googleUser.authentication;

      // 3. Создаём credential для Firebase
      final credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      // 4. Входим через Firebase
      final userCredential = await _auth.signInWithCredential(credential);

      return Result.success(userCredential);
    } catch (e) {
      return Result.failure('Google sign-in failed: $e');
    }
  }

  Future<void> signOut() async {
    await _googleSignIn.signOut();
    await _auth.signOut();
  }
}

Отслеживание состояния аутентификации

Для автоматического перенаправления пользователя на нужный экран используйте StreamBuilder:

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

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        // Загрузка
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Scaffold(
            body: Center(child: CircularProgressIndicator()),
          );
        }

        // Пользователь авторизован
        if (snapshot.hasData) {
          return HomeScreen();
        }

        // Пользователь не авторизован
        return LoginScreen();
      },
    );
  }
}

Cloud Firestore

Firestore — это NoSQL база данных real-time. Данные хранятся в виде коллекций документов.

Базовые операции CRUD

import 'package:cloud_firestore/cloud_firestore.dart';

class DatabaseService {
  final FirebaseFirestore _db = FirebaseFirestore.instance;

  // CREATE: Создать документ
  Future<Result<void>> createUser(User user) async {
    try {
      await _db.collection('users').doc(user.id).set(user.toMap());

      return Result.success(null);
    } catch (e) {
      return Result.failure('Failed to create user');
    }
  }

  // READ: Прочитать документ
  Future<Result<User>> getUser(String userId) async {
    try {
      final doc = await _db.collection('users').doc(userId).get();

      if (!doc.exists) {
        return Result.failure('User not found');
      }

      final user = User.fromMap(doc.data() as Map<String, dynamic>);

      return Result.success(user);
    } catch (e) {
      return Result.failure('Failed to get user');
    }
  }

  // UPDATE: Обновить документ
  Future<Result<void>> updateUser(User user) async {
    try {
      await _db.collection('users').doc(user.id).update(user.toMap());

      return Result.success(null);
    } catch (e) {
      return Result.failure('Failed to update user');
    }
  }

  // DELETE: Удалить документ
  Future<Result<void>> deleteUser(String userId) async {
    try {
      await _db.collection('users').doc(userId).delete();

      return Result.success(null);
    } catch (e) {
      return Result.failure('Failed to delete user');
    }
  }
}

Real-time обновления

Один из главных преимуществ Firestore — real-time обновления. Используйте snapshots() вместо get():

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

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: FirebaseFirestore.instance
          .collection('users')
          .orderBy('createdAt', descending: true)
          .snapshots(),
      builder: (context, snapshot) {
        // Обработка ошибок
        if (snapshot.hasError) {
          return Center(child: Text('Error: ${snapshot.error}'));
        }

        // Загрузка
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Center(child: CircularProgressIndicator());
        }

        // Нет данных
        if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
          return Center(child: Text('No users found'));
        }

        // Отображение списка
        final users = snapshot.data!.docs;

        return ListView.builder(
          itemCount: users.length,
          itemBuilder: (context, index) {
            final user = User.fromMap(
              users[index].data() as Map<String, dynamic>,
            );

            return ListTile(
              title: Text(user.name),
              subtitle: Text(user.email),
              trailing: Text(user.createdAt.toString()),
            );
          },
        );
      },
    );
  }
}

Запросы

Firestore поддерживает сложные запросы:

class QueryService {
  final FirebaseFirestore _db = FirebaseFirestore.instance;

  // Где (where)
  Future<List<User>> getUsersOlderThan(int age) async {
    final snapshot = await _db
        .collection('users')
        .where('age', isGreaterThan: age)
        .get();

    return snapshot.docs
        .map((doc) => User.fromMap(doc.data() as Map<String, dynamic>))
        .toList();
  }

  // Сортировка
  Future<List<User>> getUsersSortedByAge() async {
    final snapshot = await _db
        .collection('users')
        .orderBy('age', descending: true)
        .get();

    return snapshot.docs
        .map((doc) => User.fromMap(doc.data() as Map<String, dynamic>))
        .toList();
  }

  // Лимит
  Future<List<User>> getFirst10Users() async {
    final snapshot = await _db
        .collection('users')
        .limit(10)
        .get();

    return snapshot.docs
        .map((doc) => User.fromMap(doc.data() as Map<String, dynamic>))
        .toList();
  }

  // Сложные запросы
  Future<List<Product>> searchProducts({
    required String category,
    required double maxPrice,
  }) async {
    final snapshot = await _db
        .collection('products')
        .where('category', isEqualTo: category)
        .where('price', isLessThan: maxPrice)
        .orderBy('rating', descending: true)
        .limit(20)
        .get();

    return snapshot.docs
        .map((doc) => Product.fromMap(doc.data() as Map<String, dynamic>))
        .toList();
  }

  // Пагинация
  Future<List<User>> getUsersPaginated({
    required User? lastUser,
    int limit = 10,
  }) async {
    Query query = _db
        .collection('users')
        .orderBy('name')
        .limit(limit);

    if (lastUser != null) {
      query = query.startAfterDocument(lastUser);
    }

    final snapshot = await query.get();

    return snapshot.docs
        .map((doc) => User.fromMap(doc.data() as Map<String, dynamic>))
        .toList();
  }
}

Транзакции

Для атомарных операций используйте транзакции:

Future<Result<void>> transferMoney({
  required String fromUserId,
  required String toUserId,
  required double amount,
}) async {
  try {
    await FirebaseFirestore.instance.runTransaction((transaction) async {
      // 1. Читаем документы
      final fromUserRef =
          FirebaseFirestore.instance.collection('users').doc(fromUserId);
      final toUserRef =
          FirebaseFirestore.instance.collection('users').doc(toUserId);

      final fromUserDoc = await transaction.get(fromUserRef);
      final toUserDoc = await transaction.get(toUserRef);

      if (!fromUserDoc.exists || !toUserDoc.exists) {
        throw Exception('One or both users not found');
      }

      final fromBalance = fromUserDoc.data()!['balance'] as double;
      final toBalance = toUserDoc.data()!['balance'] as double;

      // 2. Проверяем баланс
      if (fromBalance < amount) {
        throw Exception('Insufficient balance');
      }

      // 3. Обновляем документы
      transaction.update(fromUserRef, {'balance': fromBalance - amount});
      transaction.update(toUserRef, {'balance': toBalance + amount});
    });

    return Result.success(null);
  } catch (e) {
    return Result.failure('Transaction failed: $e');
  }
}

Batch операции

Для нескольких записей используйте batch операции:

Future<void> deleteAllMessages(String chatId) async {
  final batch = FirebaseFirestore.instance.batch();

  final messages = await FirebaseFirestore.instance
      .collection('chats')
      .doc(chatId)
      .collection('messages')
      .limit(500)
      .get();

  for (var doc in messages.docs) {
    batch.delete(doc.reference);
  }

  await batch.commit();
}

Firebase Storage

Cloud Storage используется для хранения файлов: изображений, видео, документов и других бинарных данных.

Загрузка файлов

import 'package:firebase_storage/firebase_storage.dart';

class StorageService {
  final FirebaseStorage _storage = FirebaseStorage.instance;

  // Загрузить файл
  Future<Result<String>> uploadFile({
    required File file,
    required String path,
  }) async {
    try {
      final ref = _storage.ref().child(path);

      final uploadTask = ref.putFile(file);

      // Отслеживание прогресса
      uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) {
        final progress = snapshot.bytesTransferred / snapshot.totalBytes;
        print('Upload progress: ${(progress * 100).toStringAsFixed(0)}%');
      });

      // Ожидание завершения
      await uploadTask;

      // Получение URL
      final url = await ref.getDownloadURL();

      return Result.success(url);
    } catch (e) {
      return Result.failure('Upload failed');
    }
  }

  // Загрузить изображение с metadata
  Future<Result<String>> uploadImage(File imageFile) async {
    try {
      final fileName = '${DateTime.now().millisecondsSinceEpoch}.jpg';
      final ref = _storage.ref().child('images/$fileName');

      final metadata = SettableMetadata(
        contentType: 'image/jpeg',
        customMetadata: {
          'uploaded_by': 'user_id',
          'description': 'Profile picture',
        },
      );

      await ref.putFile(imageFile, metadata);

      final url = await ref.getDownloadURL();

      return Result.success(url);
    } catch (e) {
      return Result.failure('Image upload failed');
    }
  }

  // Удалить файл
  Future<Result<void>> deleteFile(String path) async {
    try {
      await _storage.ref().child(path).delete();

      return Result.success(null);
    } catch (e) {
      return Result.failure('Delete failed');
    }
  }

  // Получить URL файла
  Future<Result<String>> getFileUrl(String path) async {
    try {
      final url = await _storage.ref().child(path).getDownloadURL();

      return Result.success(url);
    } catch (e) {
      return Result.failure('Failed to get URL');
    }
  }
}

Cloud Functions

Cloud Functions позволяют выполнять serverless код на backend. Это полезно для: — Отправки email уведомлений — Обработки платежей — Агрегации данных — Интеграции с внешними API

Вызов функций из Flutter

dependencies:
  cloud_functions: ^5.0.0
import 'package:cloud_functions/cloud_functions.dart';

class FunctionsService {
  final FirebaseFunctions _functions = FirebaseFunctions.instance;

  Future<Result<void>> sendWelcomeEmail(String userId) async {
    try {
      final result = await _functions.httpsCallable('sendWelcomeEmail').call({
        'userId': userId,
      });

      return Result.success(result.data);
    } on FirebaseFunctionsException catch (e) {
      return Result.failure(e.message);
    } catch (e) {
      return Result.failure('Function call failed');
    }
  }

  Future<Result<String>> processPayment(PaymentRequest request) async {
    try {
      final result =
          await _functions.httpsCallable('processPayment').call(request.toMap());

      final transactionId = result.data['transactionId'] as String;

      return Result.success(transactionId);
    } catch (e) {
      return Result.failure('Payment failed');
    }
  }
}

Push Notifications

Firebase Cloud Messaging (FCM) позволяет отправлять push-уведомления на устройства пользователей.

Настройка FCM

import 'package:firebase_messaging/firebase_messaging.dart';

class NotificationService {
  final FirebaseMessaging _messaging = FirebaseMessaging.instance;

  Future<void> initialize() async {
    // 1. Запрос permissions на iOS
    NotificationSettings settings = await _messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      print('User granted permission');
    }

    // 2. Получить FCM token
    final token = await _messaging.getToken();

    if (token != null) {
      print('FCM Token: $token');

      // Сохранить token в Firestore для отправки уведомлений
      await _saveTokenToServer(token);
    }

    // 3. Слушать сообщения в foreground
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Got message in foreground: ${message.notification?.title}');

      // Показать local notification
      _showLocalNotification(message);
    });

    // 4. Сообщения при открытии приложения из notification
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('App opened from notification');
      _handleNotificationTap(message);
    });
  }

  Future<void> _saveTokenToServer(String token) async {
    // Сохранить token в Firestore
    final userId = FirebaseAuth.instance.currentUser?.uid;

    if (userId != null) {
      await FirebaseFirestore.instance
          .collection('users')
          .doc(userId)
          .update({'fcmToken': token});
    }
  }

  void _showLocalNotification(RemoteMessage message) {
    // Используйте flutter_local_notifications для показа
  }

  void _handleNotificationTap(RemoteMessage message) {
    // Навигация к нужному экрану
  }
}

Обработка фоновых сообщений

Для обработки сообщений в свернутом приложении:

// Обязательно top-level функция
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();

  print('Background message: ${message.messageId}');

  // Обработка сообщения
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  // Регистрируем background handler
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  runApp(MyApp());
}

Analytics

Firebase Analytics автоматически собирает данные о пользователях. Для custom events:

import 'package:firebase_analytics/firebase_analytics.dart';

class AnalyticsService {
  final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;

  // Логировать custom событие
  Future<void> logEvent({
    required String name,
    Map<String, Object?>? parameters,
  }) async {
    await _analytics.logEvent(
      name: name,
      parameters: parameters,
    );
  }

  // Просмотр экрана
  Future<void> logScreenView(String screenName) async {
    await _analytics.logScreenView(
      screenName: screenName,
      screenClass: screenName,
    );
  }

  // Покупка
  Future<void> logPurchase({
    required String itemId,
    required double price,
    required String currency,
  }) async {
    await _analytics.logPurchase(
      transactionId: DateTime.now().millisecondsSinceEpoch.toString(),
      itemId: itemId,
      value: price,
      currency: currency,
    );
  }

  // Поиск
  Future<void> logSearch(String searchTerm) async {
    await _analytics.logSearch(searchTerm: searchTerm);
  }

  // Выбор контента
  Future<void> logSelectContent({
    required String contentType,
    required String itemId,
  }) async {
    await _analytics.logSelectContent(
      contentType: contentType,
      itemId: itemId,
    );
  }
}

Заключение

Firebase предоставляет мощный и простой бэкенд для Flutter приложений. В этом руководстве мыcovered:

  • Authentication — email/password и Google sign-in
  • Cloud Firestore — CRUD операции, real-time обновления, транзакции
  • Cloud Storage — загрузка файлов
  • Cloud Functions — serverless функции
  • FCM — push-уведомления
  • Analytics — tracking событий

Используйте Firebase для быстрого прототипирования и MVP. Для больших проектов с complex requirements может потребоваться custom backend.