flutter
/

GetX with Firebase: Build Reactive Apps with Firestore & Auth

Last Sync: Today

On this page

13
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX with Firebase: Build Reactive Apps with Firestore & Auth

Introduction

Firebase provides a suite of backend services (authentication, real‑time database, Firestore, storage) that are ideal for Flutter apps. Combining Firebase with GetX gives you reactive, real‑time data synchronization and state management with minimal boilerplate. This guide covers how to integrate Firebase Authentication and Firestore with GetX, using streams, reactive state, and dependency injection to build scalable apps.

  1. Setting Up Firebase

Follow the official FlutterFire documentation to add Firebase to your Flutter project. You'll need to create a Firebase project, add the configuration files (google-services.json for Android, GoogleService-Info.plist for iOS), and add the required dependencies.

YAMLRead-only
1
dependencies:
  flutter:
    sdk: flutter
  get: ^4.6.6
  firebase_core: ^2.24.0
  firebase_auth: ^4.16.0
  cloud_firestore: ^4.14.0

Initialize Firebase in your main() function before running the app.

DARTRead-only
1
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

  1. Firebase Service with GetX

Create a service class that extends GetxService (or GetxController) to encapsulate Firebase functionality. This service can be made permanent so it lives throughout the app.

DARTRead-only
1
// firebase_service.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:get/get.dart';

class FirebaseService extends GetxService {
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;

  // Reactive user state
  Rx<User?> user = Rx<User?>(null);

  @override
  void onInit() {
    super.onInit();
    // Listen to auth state changes
    _auth.authStateChanges().listen((User? user) {
      this.user.value = user;
    });
  }

  // Authentication methods
  Future<User?> signIn(String email, String password) async {
    try {
      final result = await _auth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
      return result.user;
    } on FirebaseAuthException catch (e) {
      throw Exception(e.message);
    }
  }

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

  // Firestore collection reference
  CollectionReference<Map<String, dynamic>> get todosCollection =>
      _firestore.collection('todos');
}

  1. Registering the Service

Register the service in an initial binding with permanent: true so it stays alive across the app.

DARTRead-only
1
class AppBinding extends Bindings {
  @override
  void dependencies() {
    Get.put(FirebaseService(), permanent: true);
  }
}

// In main.dart
GetMaterialApp(
  initialBinding: AppBinding(),
  // ...
);

  1. Authentication Controller

Create a controller that uses the FirebaseService. It will manage login state, loading, and error messages reactively.

DARTRead-only
1
class AuthController extends GetxController {
  final FirebaseService firebase = Get.find();
  var isLoading = false.obs;
  var error = ''.obs;

  Future<void> login(String email, String password) async {
    isLoading.value = true;
    error.value = '';
    try {
      final user = await firebase.signIn(email, password);
      if (user != null) {
        Get.offAllNamed('/home');
      }
    } catch (e) {
      error.value = e.toString();
      Get.snackbar('Login Failed', error.value);
    } finally {
      isLoading.value = false;
    }
  }

  void logout() async {
    await firebase.signOut();
    Get.offAllNamed('/login');
  }
}

  1. Reactive Firestore Data

To display Firestore data reactively, you can use a stream inside a controller and convert it to an RxList using RxList.fromStream or by listening to the stream and updating a reactive list.

DARTRead-only
1
class TodoController extends GetxController {
  final FirebaseService firebase = Get.find();
  var todos = <Todo>[].obs;
  StreamSubscription? _subscription;

  @override
  void onInit() {
    super.onInit();
    // Listen to Firestore collection where the user's ID matches
    final userId = firebase.user.value?.uid;
    if (userId != null) {
      _subscription = firebase.todosCollection
          .where('userId', isEqualTo: userId)
          .snapshots()
          .listen((snapshot) {
        final list = snapshot.docs.map((doc) {
          return Todo.fromMap(doc.data(), doc.id);
        }).toList();
        todos.assignAll(list);
      });
    }
  }

  @override
  void onClose() {
    _subscription?.cancel();
    super.onClose();
  }

  Future<void> addTodo(String title) async {
    await firebase.todosCollection.add({
      'title': title,
      'completed': false,
      'userId': firebase.user.value?.uid,
      'createdAt': FieldValue.serverTimestamp(),
    });
  }

  Future<void> toggleTodo(String id, bool completed) async {
    await firebase.todosCollection.doc(id).update({'completed': !completed});
  }
}

Note: Make sure to cancel the stream subscription in onClose to avoid memory leaks.

  1. Using Rx Streams with GetX

GetX provides a handy RxStream class that you can use to convert a stream into a reactive variable. However, for Firestore, it’s often easier to use a manual subscription as above to have more control.

DARTRead-only
1
// Alternative: using RxStream
var todosStream = RxStream<QuerySnapshot>(firebase.todosCollection.snapshots());
// Then in UI: Obx(() => StreamBuilder(...)) – but this still requires a StreamBuilder.

  1. Protecting Routes with Middleware

Use GetX middleware to guard routes that require authentication. The middleware can check the Firebase user state.

DARTRead-only
1
class AuthMiddleware extends GetMiddleware {
  @override
  RouteSettings? redirect(String? route) {
    final firebase = Get.find<FirebaseService>();
    if (firebase.user.value == null && route != '/login') {
      return RouteSettings(name: '/login');
    }
    return null;
  }
}

// In GetPage
GetPage(
  name: '/home',
  page: () => HomePage(),
  middlewares: [AuthMiddleware()],
);

  1. Error Handling

FirebaseAuth methods throw FirebaseAuthException. Catch these and convert them to user‑friendly messages.

DARTRead-only
1
String handleAuthError(FirebaseAuthException e) {
  switch (e.code) {
    case 'user-not-found':
      return 'No user found with this email.';
    case 'wrong-password':
      return 'Wrong password provided.';
    case 'email-already-in-use':
      return 'Email already in use.';
    default:
      return 'An error occurred. Please try again.';
  }
}

Best Practices

  • Keep Firebase logic in a service – Centralize Firebase calls to avoid duplication.
  • Use GetxService for the Firebase service – It’s permanent and will survive route changes.
  • Dispose stream subscriptions – Always cancel Firestore subscriptions in onClose to prevent memory leaks.
  • Use reactive variables for user state – Update the UI automatically when the auth state changes.
  • Use middleware for route protection – Keep the authentication check in one place.
  • Handle errors gracefully – Show user‑friendly messages for Firebase exceptions.
  • Use Firestore security rules – Never rely solely on client‑side checks; always secure your data at the backend.

Common Mistakes

  • ❌ Not initializing Firebase before runApp – Causes runtime errors. ✅ Call await Firebase.initializeApp() in main before running the app.
  • ❌ Keeping Firestore listeners active after disposing controller – Memory leaks. ✅ Cancel subscriptions in onClose.
  • ❌ Hardcoding Firestore collection names – Prone to typos. ✅ Use constants or a dedicated service.
  • ❌ Assuming the user is always logged in – Always check firebase.user.value != null before accessing user‑specific data.

FAQ

  • Q: Can I use GetX with Firebase Realtime Database?
    A: Yes, the same principles apply. Use databaseReference.onValue and listen to the stream, updating an Rx variable.
  • Q: How do I handle Firebase Cloud Messaging (FCM) with GetX?
    A: Create a NotificationService that extends GetxService and listens to FCM events. Use Get.find to access other controllers if needed.
  • Q: Should I use GetX for all Firebase interactions?
    A: Not necessarily, but GetX provides convenient tools for reactive state, dependency injection, and lifecycle management. It's a good fit.
  • Q: How to handle Firestore offline persistence?
    A: Firestore automatically enables offline persistence. Your data will be cached and synced when back online. No extra code needed.
  • Q: Can I use GetX with Firebase Storage?
    A: Yes, you can create a service for storage uploads/downloads, and use reactive state to show progress.

Conclusion

Integrating Firebase with GetX gives you a powerful, reactive backend solution. With a well‑structured service and controllers, you can build real‑time apps that automatically reflect data changes and handle authentication seamlessly. Combine it with GetX’s routing, state management, and dependency injection for a complete, production‑ready architecture.

Test Your Knowledge

Q1
of 3

Which GetX class is best suited for creating a long‑lived Firebase service?

A
GetxController
B
GetxService
C
Obx
D
GetBuilder
Q2
of 3

How do you listen to authentication state changes in GetX?

A
Using GetX middleware
B
Using FirebaseAuth.authStateChanges() inside a GetxService
C
Using GetBuilder with a stream
D
Using a timer
Q3
of 3

Where should you dispose a Firestore stream subscription?

A
onInit
B
onClose
C
build method
D
onReady

Previous

getx multi module project

Next

getx with rest api architecture

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.