Firebase
Wiring Firebase into a Flutter app gives you Auth, Firestore, Storage, Functions, Crashlytics, Analytics, and Remote Config from one SDK. The plumbing is mostly running `flutterfire configure` and `runApp` after `Firebase.initializeApp`. The rest is reading the docs for the product you actually need.
Auth + Firestore + Crashlytics in one Flutter app
EXAMPLE
// pubspec.yaml
// dependencies:
// firebase_core: ^2.31.0
// firebase_auth: ^4.20.0
// cloud_firestore: ^4.17.5
// firebase_crashlytics: ^3.5.7
// firebase_app_check: ^0.2.2
// google_sign_in: ^6.2.1 # for Google sign in
// dev_dependencies:
// flutterfire_cli: ^1.0.0
// 1) One-time setup
// npm i -g firebase-tools && firebase login
// dart pub global activate flutterfire_cli
// flutterfire configure # generates lib/firebase_options.dart
// 2) main.dart
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_app_check/firebase_app_check.dart';
import 'firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
// 3) App Check before any Auth/Firestore call
await FirebaseAppCheck.instance.activate(
androidProvider: AndroidProvider.playIntegrity,
appleProvider: AppleProvider.deviceCheck,
);
// 4) Pipe errors to Crashlytics
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(!kDebugMode);
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(const ShopApp());
}
class ShopApp extends StatelessWidget {
const ShopApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(home: const AuthGate());
}
// 5) Auth gate — switches between sign-in and main screen
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
@override
Widget build(BuildContext context) => StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snap) {
if (snap.connectionState != ConnectionState.active) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
return snap.data == null ? const SignInScreen() : const HomeScreen();
},
);
}
class SignInScreen extends StatelessWidget {
const SignInScreen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: ElevatedButton.icon(
icon: const Icon(Icons.person_outline),
label: const Text('Continue as guest'),
onPressed: () async {
await FirebaseAuth.instance.signInAnonymously();
},
),
),
);
}
// 6) Home — read + write a Firestore collection scoped to the user
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
CollectionReference<Map<String, dynamic>> get _notes =>
FirebaseFirestore.instance.collection('notes');
@override
Widget build(BuildContext context) {
final uid = FirebaseAuth.instance.currentUser!.uid;
return Scaffold(
appBar: AppBar(title: const Text('Notes')),
body: StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: _notes.where('uid', isEqualTo: uid)
.orderBy('createdAt', descending: true)
.limit(50)
.snapshots(),
builder: (context, snap) {
if (!snap.hasData) return const Center(child: CircularProgressIndicator());
final docs = snap.data!.docs;
return ListView(children: [
for (final d in docs) ListTile(
title: Text(d.data()['body'] ?? ''),
trailing: IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () => d.reference.delete(),
),
),
]);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await _notes.add({
'uid': uid,
'body': 'hello at ${DateTime.now()}',
'createdAt': FieldValue.serverTimestamp(),
});
},
child: const Icon(Icons.add),
),
);
}
}
// 7) Firestore security rules — paste in firestore.rules
// rules_version = '2';
// service cloud.firestore {
// match /databases/{db}/documents {
// match /notes/{id} {
// allow read, update, delete: if request.auth != null && resource.data.uid == request.auth.uid;
// allow create: if request.auth != null
// && request.resource.data.uid == request.auth.uid;
// }
// }
// }
// 8) Add Crashlytics user id + custom keys for richer crash context
FirebaseCrashlytics.instance.setUserIdentifier(uid);
FirebaseCrashlytics.instance.setCustomKey('build', '1.4.0+142');
// 9) Decision matrix
// - Real-time data sync -> Firestore + snapshots
// - Tiny key/value config -> Remote Config
// - Push notifications -> Firebase Cloud Messaging
// - Crash + non-fatal reports -> Crashlytics
// - Files (images, audio) -> Cloud Storage
// - Background jobs / webhooks -> Cloud Functions
// - Per-user analytics -> Analytics (auto-exports to BigQuery)
// 10) Pitfalls
// - Skipping App Check -> instant scraper / abuse on launch
// - 'allow read, write: if true' shipped from a tutorial
// - Loading 10k docs into memory; paginate with cursors
// - Crashlytics missing dSYMs (iOS) / mapping (Android) -> unresolved stacks
Why it matters
`flutterfire configure` + App Check + Crashlytics + rules tests are the four steps that take a Flutter + Firebase app from "works in dev" to "ready for the first 1000 users". Skip any of them and the first day in production becomes the first day of incident response.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// pubspec.yaml: firebase_core, firebase_auth, cloud_firestore
await Firebase.initializeApp();
final u = await FirebaseAuth.instance.signInAnonymously();
await FirebaseFirestore.instance.collection('events').add({'kind': 'open'});
Try it Yourself »
Discussion
Loading…