Provider + MVVM + Firebase — The Beginner-Friendly Stack

Guidestate management#provider#mvvm#firebase#flutter#beginners

Provider + MVVM + Firebase: ChangeNotifier ViewModels, data/ui folders, and Firebase email-password auth for beginners and small teams.

Arjun Mahar
Arjun Mahar@arjun_mahar1
4 min read

Stack Configuration — what FlutterInit generates for this guide

Architecture
MVVM
State Management
Provider
Backend
Firebase
Navigation
go_router

When to choose this stack

Choose this stack when you're learning Flutter, shipping with a small team, or want less ceremony than Clean Architecture. Provider's ChangeNotifier model is easy to teach, MVVM's data/ui split is enough structure for most apps under ~20 screens, and Firebase Auth email-password gets you signed-in users without standing up a backend.

Provider + MVVM + Firebase is the beginner-friendly Flutter stack: ChangeNotifier ViewModels, a flat data / ui split, and Firebase email-password auth. You get structure without Clean Architecture's three-layer tax. FlutterInit wires the folders, provider package, go_router redirects, and Firebase Auth for you.

What This Stack Generates

Select Provider + MVVM + Firebase and you get:

  • MVVM layoutlib/src/data for models/repos, lib/src/ui for screens and providers
  • ProviderAuthProvider / SessionProvider as ChangeNotifiers
  • Firebasefirebase_core + firebase_auth, email-password login/signup, google-services.json / GoogleService-Info.plist setup in SETUP.md
  • go_router — auth-aware redirects between login and home

No use-case classes. No domain entities folder. The repository interface lives next to its Firebase-backed impl under data/.

Project Structure

Understanding Each Layer

Data — Models and Repositories

MVVM here is two folders, not three Clean layers. data/ owns the user model, the repository contract, and the Firebase-backed implementation.

data/repositories/auth_repository.dart
abstract class AuthRepository {
  Stream<AppUser?> get onAuthStateChanged;

  FutureEither<AppUser> login({
    required String email,
    required String password,
  });

  FutureEither<AppUser> signUp({
    required String name,
    required String email,
    required String password,
  });

  FutureEither<void> logout();
}

The impl talks to AuthService, which wraps Firebase Auth — screens never import firebase_auth directly.

data/repositories/auth_repository_impl.dart
class AuthRepositoryImpl implements AuthRepository {
  final AuthService _authService = AuthService.instance;

  @override
  FutureEither<AppUser> login({
    required String email,
    required String password,
  }) async {
    final result = await _authService.login(
      email: email,
      password: password,
    );

    return result.flatMap((userData) {
      if (userData == null) {
        return left(const ServerFailure('Login failed'));
      }
      return right(AppUser(
        id: userData['id'],
        email: userData['email'] ?? email,
        name: userData['name'],
        photoUrl: userData['photoUrl'],
      ));
    });
  }
}

Firebase Auth — Email and Password

AuthService is the thin SDK edge. Email-password is the default pattern FlutterInit wires:

services/auth_service.dart
FutureEither<Map<String, dynamic>?> login({
  required String email,
  required String password,
}) async {
  return runTask(() async {
    final credentials = await _firebaseAuth.signInWithEmailAndPassword(
      email: email,
      password: password,
    );
    final user = credentials.user;
    if (user == null) return null;
    return {
      'id': user.uid,
      'email': user.email ?? '',
      'name': user.displayName ?? '',
      'photoUrl': user.photoURL,
    };
  }, requiresNetwork: true);
}

You still drop in google-services.json (Android) and GoogleService-Info.plist (iOS) per SETUP.md. Firebase initializes once in AppConfig.init().

UI — Provider as the ViewModel

In this stack, the "ViewModel" is a ChangeNotifier registered with the provider package. Login calls go through AuthProvider; session listening lives in SessionProvider.

ui/auth/providers/auth_provider.dart
class AuthProvider extends ChangeNotifier {
  final AuthRepository _repository;

  AuthProvider({required AuthRepository repository}) : _repository = repository;

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  void _setLoading(bool value) {
    _isLoading = value;
    notifyListeners();
  }

  void login({
    required BuildContext context,
    required String email,
    required String password,
  }) async {
    _setLoading(true);
    final result = await _repository.login(email: email, password: password);
    _setLoading(false);

    result.fold(
      (failure) {
        if (context.mounted) {
          showToast(context, message: failure.message, status: 'error');
        }
      },
      (_) {
        if (context.mounted) context.go(AppRoutes.home);
      },
    );
  }
}

In the screen: context.watch<AuthProvider>() in build, context.read<AuthProvider>() in button callbacks. That's the whole mental model.

Auth redirects watch the session stream. Unauthenticated users land on login; authenticated users skip auth screens and go home. Same pattern as the other FlutterInit stacks — only the state glue changes.

When to Choose This vs Alternatives

Pick Provider + MVVM + Firebase when:

  • You're a beginner or onboarding juniors — ChangeNotifier is the easiest Flutter state story to teach
  • The app is small-to-medium and you don't need feature-sliced Clean folders yet
  • You want Firebase Auth without inventing your own backend

Skip it when:

  • You need strict domain isolation for a large team — use Clean Architecture instead
  • You want compile-safe providers and test overrides — look at Riverpod + Clean + Firebase
  • You need event/state predictability for enterprise flows — Bloc fits better

For a deeper MVVM walkthrough, see the practical MVVM guide. For Firebase Auth setup details, see Firebase Auth wired up.

Ready to Generate?

Open the dashboard, pick MVVM + Provider + Firebase + go_router, and download a project with this exact shape — folders, auth flow, and pubspec.yaml already wired.

Ready to build?

Generate this project in seconds

FlutterInit scaffolds the entire structure described in this guide — wired up, typed, and ready for flutter run.

Start Generating →