Firebase у Flutter
Firebase — це платформа від Google, яка надає різноманітні сервіси для мобільних та веб-застосунків: аутентифікацію, бази даних, хмарні функції, аналітику тощо.
Налаштування Firebase
Встановлення FlutterFire CLI
bash
# Встановлення CLI
dart pub global activate flutterfire_cli
# Конфігурація проєкту
flutterfire configureЗалежності
yaml
# pubspec.yaml
dependencies:
firebase_core: ^2.24.0
firebase_auth: ^4.15.0
cloud_firestore: ^4.13.0
firebase_storage: ^11.5.0
firebase_messaging: ^14.7.0
firebase_analytics: ^10.7.0Ініціалізація
dart
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MyApp());
}Firebase Authentication
Реєстрація та вхід
dart
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Поточний користувач
User? get currentUser => _auth.currentUser;
// Потік змін аутентифікації
Stream<User?> get authStateChanges => _auth.authStateChanges();
// Реєстрація з email/password
Future<UserCredential> signUpWithEmail({
required String email,
required String password,
}) async {
try {
return await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
throw _handleAuthException(e);
}
}
// Вхід з email/password
Future<UserCredential> signInWithEmail({
required String email,
required String password,
}) async {
try {
return await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
throw _handleAuthException(e);
}
}
// Вихід
Future<void> signOut() async {
await _auth.signOut();
}
// Скидання паролю
Future<void> resetPassword(String email) async {
await _auth.sendPasswordResetEmail(email: email);
}
// Обробка помилок
String _handleAuthException(FirebaseAuthException e) {
switch (e.code) {
case 'email-already-in-use':
return 'Цей email вже використовується';
case 'invalid-email':
return 'Невірний формат email';
case 'weak-password':
return 'Пароль занадто слабкий';
case 'user-not-found':
return 'Користувача не знайдено';
case 'wrong-password':
return 'Невірний пароль';
default:
return 'Помилка аутентифікації: ${e.message}';
}
}
}Вхід через Google
dart
import 'package:google_sign_in/google_sign_in.dart';
Future<UserCredential?> signInWithGoogle() async {
try {
// Запуск процесу входу через Google
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
if (googleUser == null) return null;
// Отримання деталей аутентифікації
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
// Створення credentials
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// Вхід у Firebase
return await FirebaseAuth.instance.signInWithCredential(credential);
} catch (e) {
print('Помилка входу через Google: $e');
return null;
}
}Віджет перевірки аутентифікації
dart
class AuthWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return LoadingScreen();
}
if (snapshot.hasData) {
return HomeScreen();
}
return LoginScreen();
},
);
}
}Cloud Firestore
Базові операції
dart
import 'package:cloud_firestore/cloud_firestore.dart';
class FirestoreService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Додавання документа
Future<DocumentReference> addUser(Map<String, dynamic> userData) async {
return await _db.collection('users').add({
...userData,
'createdAt': FieldValue.serverTimestamp(),
});
}
// Додавання з власним ID
Future<void> setUser(String id, Map<String, dynamic> userData) async {
await _db.collection('users').doc(id).set(userData);
}
// Оновлення документа
Future<void> updateUser(String id, Map<String, dynamic> data) async {
await _db.collection('users').doc(id).update(data);
}
// Видалення документа
Future<void> deleteUser(String id) async {
await _db.collection('users').doc(id).delete();
}
// Отримання одного документа
Future<DocumentSnapshot> getUser(String id) async {
return await _db.collection('users').doc(id).get();
}
// Отримання всіх документів
Future<QuerySnapshot> getAllUsers() async {
return await _db.collection('users').get();
}
}Запити
dart
class UserQueries {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Фільтрація
Future<List<User>> getUsersByAge(int minAge) async {
final snapshot = await _db
.collection('users')
.where('age', isGreaterThanOrEqualTo: minAge)
.get();
return snapshot.docs.map((doc) => User.fromFirestore(doc)).toList();
}
// Складні запити
Future<List<User>> getActiveAdminUsers() async {
final snapshot = await _db
.collection('users')
.where('isActive', isEqualTo: true)
.where('role', isEqualTo: 'admin')
.orderBy('createdAt', descending: true)
.limit(10)
.get();
return snapshot.docs.map((doc) => User.fromFirestore(doc)).toList();
}
// Пагінація
Future<List<User>> getUsersPage(DocumentSnapshot? lastDoc) async {
Query query = _db
.collection('users')
.orderBy('createdAt')
.limit(20);
if (lastDoc != null) {
query = query.startAfterDocument(lastDoc);
}
final snapshot = await query.get();
return snapshot.docs.map((doc) => User.fromFirestore(doc)).toList();
}
// Пошук за масивом
Future<List<Post>> getPostsByTags(List<String> tags) async {
final snapshot = await _db
.collection('posts')
.where('tags', arrayContainsAny: tags)
.get();
return snapshot.docs.map((doc) => Post.fromFirestore(doc)).toList();
}
}Реальний час (Realtime)
dart
class RealtimeService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Потік одного документа
Stream<User?> userStream(String userId) {
return _db
.collection('users')
.doc(userId)
.snapshots()
.map((doc) => doc.exists ? User.fromFirestore(doc) : null);
}
// Потік колекції
Stream<List<Message>> messagesStream(String chatId) {
return _db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timestamp', descending: true)
.limit(50)
.snapshots()
.map((snapshot) =>
snapshot.docs.map((doc) => Message.fromFirestore(doc)).toList());
}
}
// Використання з StreamBuilder
class ChatScreen extends StatelessWidget {
final String chatId;
const ChatScreen({required this.chatId});
@override
Widget build(BuildContext context) {
return StreamBuilder<List<Message>>(
stream: RealtimeService().messagesStream(chatId),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Помилка: ${snapshot.error}');
}
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
final messages = snapshot.data!;
return ListView.builder(
reverse: true,
itemCount: messages.length,
itemBuilder: (context, index) {
return MessageTile(message: messages[index]);
},
);
},
);
}
}Транзакції та пакетні операції
dart
class TransactionService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// Транзакція
Future<void> transferMoney({
required String fromUserId,
required String toUserId,
required double amount,
}) async {
await _db.runTransaction((transaction) async {
// Читання документів
final fromDoc = await transaction.get(
_db.collection('accounts').doc(fromUserId),
);
final toDoc = await transaction.get(
_db.collection('accounts').doc(toUserId),
);
final fromBalance = fromDoc.get('balance') as double;
final toBalance = toDoc.get('balance') as double;
if (fromBalance < amount) {
throw Exception('Недостатньо коштів');
}
// Оновлення документів
transaction.update(fromDoc.reference, {
'balance': fromBalance - amount,
});
transaction.update(toDoc.reference, {
'balance': toBalance + amount,
});
});
}
// Пакетна операція
Future<void> updateMultipleUsers(List<User> users) async {
final batch = _db.batch();
for (final user in users) {
final docRef = _db.collection('users').doc(user.id);
batch.update(docRef, user.toMap());
}
await batch.commit();
}
}Модель даних
dart
class User {
final String id;
final String name;
final String email;
final int age;
final DateTime createdAt;
User({
required this.id,
required this.name,
required this.email,
required this.age,
required this.createdAt,
});
factory User.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
return User(
id: doc.id,
name: data['name'] ?? '',
email: data['email'] ?? '',
age: data['age'] ?? 0,
createdAt: (data['createdAt'] as Timestamp?)?.toDate() ?? DateTime.now(),
);
}
Map<String, dynamic> toMap() {
return {
'name': name,
'email': email,
'age': age,
'createdAt': Timestamp.fromDate(createdAt),
};
}
}Firebase Storage
Завантаження файлів
dart
import 'package:firebase_storage/firebase_storage.dart';
import 'dart:io';
class StorageService {
final FirebaseStorage _storage = FirebaseStorage.instance;
// Завантаження файлу
Future<String> uploadFile(File file, String path) async {
final ref = _storage.ref().child(path);
final uploadTask = ref.putFile(
file,
SettableMetadata(contentType: 'image/jpeg'),
);
// Відстеження прогресу
uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) {
final progress = snapshot.bytesTransferred / snapshot.totalBytes;
print('Прогрес: ${(progress * 100).toStringAsFixed(2)}%');
});
await uploadTask;
return await ref.getDownloadURL();
}
// Завантаження зображення профілю
Future<String> uploadProfileImage(String userId, File imageFile) async {
final path = 'users/$userId/profile.jpg';
return await uploadFile(imageFile, path);
}
// Видалення файлу
Future<void> deleteFile(String path) async {
await _storage.ref().child(path).delete();
}
// Отримання URL
Future<String> getDownloadUrl(String path) async {
return await _storage.ref().child(path).getDownloadURL();
}
}Віджет завантаження з прогресом
dart
class ImageUploadWidget extends StatefulWidget {
@override
_ImageUploadWidgetState createState() => _ImageUploadWidgetState();
}
class _ImageUploadWidgetState extends State<ImageUploadWidget> {
double _uploadProgress = 0;
bool _isUploading = false;
Future<void> _uploadImage() async {
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile == null) return;
setState(() {
_isUploading = true;
_uploadProgress = 0;
});
final file = File(pickedFile.path);
final ref = FirebaseStorage.instance
.ref()
.child('uploads/${DateTime.now().millisecondsSinceEpoch}.jpg');
final uploadTask = ref.putFile(file);
uploadTask.snapshotEvents.listen((snapshot) {
setState(() {
_uploadProgress = snapshot.bytesTransferred / snapshot.totalBytes;
});
});
await uploadTask;
setState(() {
_isUploading = false;
});
final url = await ref.getDownloadURL();
print('Файл завантажено: $url');
}
@override
Widget build(BuildContext context) {
return Column(
children: [
if (_isUploading)
LinearProgressIndicator(value: _uploadProgress)
else
ElevatedButton(
onPressed: _uploadImage,
child: Text('Скачать зображення'),
),
],
);
}
}Firebase Cloud Messaging (FCM)
Налаштування push-сповіщень
dart
import 'package:firebase_messaging/firebase_messaging.dart';
class NotificationService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
Future<void> initialize() async {
// Запит дозволу
final settings = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
print('Дозвіл на сповіщення отримано');
}
// Отримання токена
final token = await _messaging.getToken();
print('FCM Token: $token');
// Обробка повідомлень
_setupMessageHandlers();
}
void _setupMessageHandlers() {
// Повідомлення при відкритому застосунку
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Отримано повідомлення: ${message.notification?.title}');
_showLocalNotification(message);
});
// Повідомлення при натисканні
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('Відкрито з повідомлення: ${message.data}');
_handleMessageTap(message);
});
}
void _showLocalNotification(RemoteMessage message) {
// Показ локального сповіщення
}
void _handleMessageTap(RemoteMessage message) {
// Навігація на основі даних повідомлення
final route = message.data['route'];
if (route != null) {
// Перехід на екран
}
}
}
// Обробка фонових повідомлень
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Фонове повідомлення: ${message.messageId}');
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}Firebase Analytics
dart
import 'package:firebase_analytics/firebase_analytics.dart';
class AnalyticsService {
final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
// Логування події
Future<void> logEvent({
required String name,
Map<String, dynamic>? parameters,
}) async {
await _analytics.logEvent(
name: name,
parameters: parameters,
);
}
// Логування покупки
Future<void> logPurchase({
required String itemId,
required String itemName,
required double price,
}) async {
await _analytics.logPurchase(
currency: 'UAH',
value: price,
items: [
AnalyticsEventItem(
itemId: itemId,
itemName: itemName,
price: price,
),
],
);
}
// Логування перегляду екрану
Future<void> logScreenView(String screenName) async {
await _analytics.logScreenView(screenName: screenName);
}
// Встановлення властивостей користувача
Future<void> setUserProperties({
required String userId,
String? userType,
}) async {
await _analytics.setUserId(id: userId);
if (userType != null) {
await _analytics.setUserProperty(name: 'user_type', value: userType);
}
}
}
// Навігаційний observer для автоматичного логування
class AnalyticsObserver extends NavigatorObserver {
final FirebaseAnalytics analytics = FirebaseAnalytics.instance;
@override
void didPush(Route route, Route? previousRoute) {
super.didPush(route, previousRoute);
if (route.settings.name != null) {
analytics.logScreenView(screenName: route.settings.name);
}
}
}
// Використання
MaterialApp(
navigatorObservers: [AnalyticsObserver()],
home: HomeScreen(),
)Найкращі практики
Використовуйте правила безпеки — завжди налаштовуйте Firestore Security Rules.
Оптимізуйте запити — використовуйте індекси та обмежуйте кількість документів.
Кешуйте дані — увімкніть офлайн-підтримку Firestore.
Обробляйте помилки — завжди використовуйте try/catch.
Слідкуйте за квотами — моніторте використання Firebase Console.
Висновок
Firebase надає повний набір інструментів для створення сучасних мобільних застосунків. Правильне використання Firebase у Flutter дозволяє швидко реалізувати аутентифікацію, роботу з даними, push-сповіщення та аналітику без необхідності створення власного бекенду.