Skip to content
  • Flutter
  • Dart
  • Provider
  • Bloc
  • MVVM
  • State Management

Provider vs Bloc in Flutter: When I Use Each in an MVVM App

By Published 4 min read (or skim, I won't know)

Use Provider with a ChangeNotifier view model when a screen's state is simple and local. Use Bloc when a feature has many events, async steps and states that must stay predictable and testable. In an MVVM Flutter app you can, and often should, use both.

The example app

Throughout this post the example is an EdTech-style Flutter app: course modules, video playback and quizzes, backed by Firebase and REST APIs, structured with MVVM and using both Provider and Bloc. The question is never "which one wins", it is "which one fits this feature".

MVVM in 30 seconds

LayerFlutter pieceJob
ModelRepositories, data classesTalk to Firebase or the REST API
ViewModelChangeNotifier, Cubit or BlocHold state, run logic
ViewWidgetsRender state, send user actions

Widgets never call the API directly. They talk to a view model, and the view model talks to a repository.

Provider: the simple view model

A quiz screen is a good fit. The state is local, the actions are obvious, and nothing outside the screen cares:

class QuizViewModel extends ChangeNotifier {
  QuizViewModel(this.questions);

  final List<Question> questions;
  int _index = 0;
  int _score = 0;

  bool get isFinished => _index == questions.length;
  Question get current => questions[_index]; // read only while !isFinished
  int get score => _score;

  void answer(int option) {
    if (isFinished) return;
    if (option == current.correctOption) _score++;
    _index++;
    notifyListeners();
  }
}
ChangeNotifierProvider(
  create: (_) => QuizViewModel(questions),
  child: const QuizScreen(),
);

// Inside QuizScreen.build
final vm = context.watch<QuizViewModel>();
if (vm.isFinished) return ResultView(score: vm.score);
return QuestionView(question: vm.current, onAnswer: vm.answer);

Why Provider here: one class, no boilerplate, easy to read. Adding Bloc would mean events and states for what is really three fields and one method.

Bloc: the predictable async feature

Loading a course is different. It can be loading, loaded, empty or failed, it can be retried, and other parts of the app react to it. That is where Bloc's explicit events and states earn their extra lines:

sealed class CourseEvent {}
final class CourseRequested extends CourseEvent {
  CourseRequested(this.courseId);
  final String courseId;
}

sealed class CourseState {}
final class CourseLoading extends CourseState {}
final class CourseLoaded extends CourseState {
  CourseLoaded(this.course);
  final Course course;
}
final class CourseFailed extends CourseState {
  CourseFailed(this.message);
  final String message;
}

class CourseBloc extends Bloc<CourseEvent, CourseState> {
  CourseBloc(this._repo) : super(CourseLoading()) {
    on<CourseRequested>((event, emit) async {
      emit(CourseLoading());
      try {
        emit(CourseLoaded(await _repo.fetchCourse(event.courseId)));
      } catch (_) {
        emit(CourseFailed('Could not load this course. Try again.'));
      }
    });
  }

  final CourseRepository _repo;
}

With Dart 3 sealed classes, the view can switch over every state and the compiler complains if one is missing:

BlocBuilder<CourseBloc, CourseState>(
  builder: (context, state) => switch (state) {
    CourseLoading() => const Center(child: CircularProgressIndicator()),
    CourseLoaded(:final course) => CourseView(course: course),
    CourseFailed(:final message) => RetryView(
        message: message,
        onRetry: () => context.read<CourseBloc>().add(CourseRequested(courseId)),
      ),
  },
);

Why Bloc here: every transition is an event you can log and test, and no screen can end up in a half-loaded state nobody planned for.

How I decide

SignalProvider (ChangeNotifier)Bloc / Cubit
State lives on one screen✅
A few fields, synchronous updates✅
Several async steps (load, page, retry)✅
Many user events that change state✅
Other features react to this state✅
You need to unit-test every transitionPossible✅ Easiest

A middle option worth knowing: Cubit (from the bloc package) keeps Bloc's explicit states but replaces events with plain methods. It is a good step up when a ChangeNotifier starts to feel messy.

Mistakes to avoid

  • Calling the API from a widget. Put it in a repository so both Provider and Bloc view models can reuse it.
  • One giant app-wide ChangeNotifier. Every notifyListeners() rebuilds everything listening. Keep view models per feature.
  • Bloc for everything. A text field toggle does not need three classes.
  • Ignoring context.read vs context.watch. Use watch in build to rebuild, read in callbacks to call methods.

Key takeaways

  • MVVM first: widgets talk to view models, view models talk to repositories.
  • Provider for simple, local screen state.
  • Bloc (or Cubit) for async, event-heavy features that must be predictable.
  • Mixing both in one app is normal, not a compromise.

Building a Flutter app for iOS and Android? I take this on as a Flutter app developer.

Frequently asked questions

Should I use Provider or Bloc in Flutter?

Use Provider with ChangeNotifier view models for screens with simple, local state such as forms, toggles and quizzes. Use Bloc when a feature has many events, async flows or states that must be predictable and testable, such as loading, paging and retrying data from an API.

Can you use Provider and Bloc in the same Flutter app?

Yes. Provider handles dependency injection and simple view models, while Bloc handles complex features. flutter_bloc itself is built on top of Provider, so the two fit together naturally.

What is MVVM in Flutter?

MVVM separates the Model (data and repositories), the View (widgets) and the ViewModel (state and logic the view binds to). In Flutter the ViewModel is usually a ChangeNotifier exposed with Provider, or a Bloc or Cubit.

Is Bloc overkill for small Flutter apps?

Often, yes. For a screen with a few fields and one API call, a ChangeNotifier view model with Provider is less code and just as clear. Reach for Bloc when the number of events and states starts to grow.