How to Migrate Your Flutter App from Provider to MVVM+

Richard Coutts
5 min readSep 25, 2022

--

Migrating your app from Provider to another state management library can be a very, very large lift. Think refactoring ChangeNotifierProviders into Cubits. Ouch.

MVVM+ and Provider are fundamentally different approaches to state management, but have remarkably similar APIs. So, migrating from Provider to MVVM+ is a surprisingly small lift, typically swapping out one statement or classname for another.

In this article, we see that, with a relatively minor code changes, you can remove Provider from your pubspec.yaml file.

And why would I migrate away from Provider?

While there is much love for Provider, there are also much challenges

  • ChangeNotifierProviders make your data available to descendants. This architectural approach is usually great but can be challenging when widgets on another branch need the data.
  • Provider is a loosely structured framework that can lead to unruly code.
  • Provider’s static of command listens to changes by default. This can lead to unnecessary widget rebuilds.

And why should I migrate to MVVM+?

Maybe you shouldn’t. Choosing state management packages is a personal choice, with developers singing the praises of one package while berating another. There are lots of reputable state management packages to choose from, include BLoC, Riverpod, Redux, and more.

One argument for migrating to MVVM+ is that MVVM+’s API is very close to Provider’s, so will be an easier migration than other packages.

MVVM+ addresses Provider’s issues listed above:

  • Instead of ChangeNotifierProviders, MVVM+ uses Bilocators. As the name suggests, Bilocators give you the option of storing your model in one of two locations: a global registry (like GetIt) or on the widget tree (like Provider).
  • MVVM+ follows the MVVM pattern, so instead of a loosely defined framework, your Views and ViewModels are tightly coupled.
  • MVVM+ does not listen to changes by default. You use listenTo when your want to listen for changes to your data and get when you don’t. So, fewer unnecessary widget builds.

For more information on MVVM+, check out my medium story on the architecture of MVVM+. And, of course, check out the MVVM+ documentation. The MVVM+ documentation describes the MVVM+ VSCode extension, which includes snippets for writing MVVM+ classes, so will help greatly with the migration.

The VSCode Extension for MVVM+

MVVM+ has boiler plate code that is very similar to StatefulWidget. So, much as the Flutter extension provides the stful snippet for inserting code, the MVVM+ extension provides the mvvm+ command for inserting the boilerplate for its View and ViewModel classes:

The MVVM+ extension is available on the VSCode marketplace.

Taking MVVM+ out for a test spin

MVVM+ happily coexists with other state management libraries, including Provider. So, if you’re interested in MVVM+ but not yet ready to fully commit, no problem. Just refactor a feature or even a single widget of your app. You can compile and run your hybrid app running MVVM+ and Provider.

If you decide your MVVM+ refactor experiment wasn’t too painful, you can try another refactor. And another. Compiling, running, and testing your app as you go to ensure you haven’t broken anything. Little by little you’ll move Provider out of your app until that very last step when you remove it from your pubspec.yaml file.

The refactors

MVVM+ was inspired by Provider’s use of ChangeNotifiers. So, MVVM+’s ViewModel class extends ChangeNotifiers:

class ViewModel extends ChangeNotifier {
// ...
}

When migrating from Provider to MVVM+, you can keep all of your ChangeNotifiers by simply refactoring them to extend ViewModels.

class MyNotifier extends C̶h̶a̶n̶g̶e̶N̶o̶t̶i̶f̶i̶e̶r̶ ViewModel {
// ...
}

Important note: Above should show “ChangeNotifier” with strikethrough characters to signify that it’s been deleted. If you see boxes instead, like[C][h][a][n][g][e][N][o][t][i][f][i][e][r], or other characters then your device does not support strikethrough, so you’ll have to use your imagination to replace the boxes with strikethrough! :) Or, switch to another device.

Provider uses ChangeNotifierProvider to add ChangeNotifiers to the widget tree. MVVM+ allows View’s to share their ViewModel instances, so often you don’t need the equivalent of ChangeNotifierProvider. On occasions when you do, you can refactor your ChangeNotifierProvider into a Bilocator widget.

̶C̶h̶a̶n̶g̶e̶N̶o̶t̶i̶f̶i̶e̶r̶P̶r̶o̶v̶i̶d̶e̶r̶ Bilocator<MyData>(
̶ ̶ ̶c̶r̶e̶a̶t̶e̶:̶ ̶(̶c̶o̶n̶t̶e̶x̶t̶)̶ builder: () => MyData(),
child: MyChild(),
}

If you want to add multiple providers, Provider has the MultiChangeNotifierProvider widget. The MVVM+ equivalent is the Bilocators widget:

Bilocators(
delegates: [
BilocatorDelegate<ServiceA>(builder: () => ServiceA()),
BilocatorDelegate<ServiceB>(builder: () => ServiceB()),
],
child: MyWidget(),
);

Provider uses the static of function to get ancestor providers. MVVM+ also has the of function, but as an extension of BuildContext:

̶final myData = P̶r̶o̶v̶i̶d̶e̶r̶context.of<MyData>(c̶o̶n̶t̶e̶x̶t̶);

Developers often forget that theof function introduces a dependency that causes widgets to rebuild, which can lead to unnecessary builds. So, which MVVM+ supports of, a best practice is to use its listenTo function to explicitly introduce this dependency:

final myData = P̶r̶o̶v̶i̶d̶e̶r̶.̶o̶f̶ listenTo<MyData>(c̶o̶n̶t̶e̶x̶t̶);

And to use the get function when no dependency is wanted:

̶final myData = P̶r̶o̶v̶i̶d̶e̶r̶.̶o̶f̶ get<MyData>(c̶o̶n̶t̶e̶x̶t̶,̶ ̶l̶i̶s̶t̶e̶n̶:̶ ̶f̶a̶l̶s̶e̶);

With MVVM+, you are free to no longer use StatefulWidget and its State sidekick. Instead, you refactor your StatefulWidgets and their States into View classes and ViewModel classes:

class MyWidget extends S̶t̶a̶t̶e̶f̶u̶l̶W̶i̶d̶g̶e̶t̶ View<MyViewModel> {
const MyWidget({super.key}) :
super(viewModelBuilder: () => MyViewModel());
̶@̶o̶v̶e̶r̶r̶i̶d̶e̶
̶ ̶ ̶S̶t̶a̶t̶e̶<̶M̶y̶W̶i̶d̶g̶e̶t̶>̶ ̶c̶r̶e̶a̶t̶e̶S̶t̶a̶t̶e̶(̶)̶ ̶=̶>̶ ̶_̶M̶y̶W̶i̶d̶g̶e̶t̶S̶t̶a̶t̶e̶(̶)̶;̶
̶}̶
̶
̶c̶l̶a̶s̶s̶ ̶_̶M̶y̶W̶i̶d̶g̶e̶t̶S̶t̶a̶t̶e̶ ̶e̶x̶t̶e̶n̶d̶s̶ ̶S̶t̶a̶t̶e̶<̶M̶y̶W̶i̶d̶g̶e̶t̶>̶ ̶{̶
@override
Widget build(BuildContext context) {
return OutlinedButton(
onPressed: viewModel.incrementCounter,
child: Text('${viewModel.counter}'),
);
}
class MyViewModel extends ViewModel {
int _counter = 0;
int get counter => _counter;
void incrementCounter() => _counter++;
});

You can also free to no longer use StatelessWidgets. Instead, you extend the ViewWithStatelessViewModel class, which provides the get and listenTo functions described above.

class MyWidget extends S̶t̶a̶t̶e̶l̶e̶s̶s̶W̶i̶d̶g̶e̶t̶ ViewWithStatelessViewModel {
// Here, just replace Provider.of
// as described above
}

So, while MVVM+ and Provider have many differences, there is a near one-to-one comparison in their APIs. So, refactoring a Provider app is often a typing exercise of replacing a Provider definition or function call with another.

Your mileage may vary

Because Provider is a flexible state management library, developers use it in different ways. So, there may be additional changes in your code that are not covered in this article. If so, feel free to add them to the comments section and I’ll add it to this article.

In summary, migrating from a Provider to another state management library is a small lift. If you move to MVVM+, that is.

Thanks for reading!

I hope this review of MVVM+ was helpful. If you have questions or suggestions for improving clarity, please add a comment and I promise to respond.

💙 Please 👏 if you’d like me to write more articles on Flutter. 💙

--

--