Converting a Stateful Widget App to Using Provider for State Management in Flutter

Kristin Newman
8 min readOct 30, 2020
Photo by Chris Lawton on Unsplash

When first learning Flutter, nearly every course and tutorial teaches how to manage state using Stateless and Stateful widgets. When your apps are small with few interactions, Stateful widgets work fine.

But as you progress writing to larger and more complex apps, you will want to separate your UI from your logic for a couple main reasons:

  1. It is easier to read and debug. As you read your code, you know you’re looking at either the UI or the logic. Separating them allows for smaller files that you don’t have to scroll and search through to find what you’re looking for, allowing for more options in organizing your code.
  2. Faster development of larger applications. When you’re dealing with a larger application, separating the state from the UI allows you to write it and test your logic independently, allowing for faster development in the long run.
  3. Faster apps. Stateful widgets have to get repainted every time there is a change. This doesn’t make much of a difference with smaller apps, but once you add more complexity, it can really slow things down. Provider allows your app to repaint just the widget that houses that change.

When I first started using Flutter, all the tutorials used Stateful widgets. As I moved to using the Provider package as a method of state management, I could not find any guidance on how to convert the results of these tutorials into into this new architecture.

After figuring out how to convert the following camera tutorial from a Stateful widget set up to a Provider architecture, I realized these patterns will be useful in any other stateful tutorial going forward.

Let’s get started

I’m going to use this camera tutorial that I found on YouTube as the starting point. You can find my starting code on GitHub, which is based on the above tutorial with some modifications based on Flutter updates.

If you want to jump ahead to the end, you can find the finished code here, and just do the comparison yourself.

Caveat: I work in Android Studio, so some instructions, particularly around navigating your IDE, will be specific to Android Studio.

Install Provider and Boilerplate Code

Take yourself over to pub.dev and type “provider” in the search field. It will be the first result. Use the most recent version under the “Installing” section to update your pubspec file:

Make sure to run “Pub get” before navigating away.

Now let’s add a file where we will organize our logic. Most of my smaller applications, I just call this state.dart but you might want to call it something more descriptive as a better habit.

In that file, we’ll import the standard material package and create a class that will hold our functions and variables. This class will extend the ChangeNotifier class, which is an analog to using setState() in Stateful widgets.

Your state class should look something like this:

Now we need to go to the top of our app and add create a directory of sorts so our app knows where to find our functions and state changes when we call them.

Go back to your main.dart file and import (1) your new state file and (2) import the provider package (see lines 4 and 5 below).

Then insert a widget into your MyApp by wrapping your Material App with a widget and building your list of providers. In this case we only have one. See lines 15 through 20. The MaterialApp is now a child of MultiProvider.

For larger apps that have multiple state files, make sure you import all those file and list them all as new ChangeNotifierProvider entries.

Here is where we start to really break things before we clean them back up again:

Moving Functions and Variables to Your State Class

At the bottom of landing_screen.dart you’ll want to copy everything from PickedFile imageFile; down through the last _openCamera() function and paste is directly inside your CameraController class you created in your state file.

Copy these lines of code at the bottom of your landing_screen.dart file

After you paste the code into your CameraController class, you’ll see some things break. Makes these changes to get rid of your red squiggles:

  1. Move your image_picker import to your state file
  2. Remove the underscore in the function names so they’re no longer private
  3. Delete the setState() lines from the functions (commented out in example below)
  4. Add notifyListeners(); at the end of each function

Your state file should look like this now:

If you go back to your landing screen, you might notice your _decideImageView() function is now broken since we moved the variable off this file. So, we’re next going to do nearly the exact same thing with Widget _decideImageView(). Copy the entire function, move it to the bottom of your CameraController class, and remove the underscore.

Now your state file should look like this:

Quick Recap

What we’ve done so far is pretty boiler plate and standard, and going forward is where personal preferences can play a role in certain decisions. I’ll explain why I make certain choices, but first I want recap the what and why of what we already did.

  1. We installed the provider package and created a file and class where all of our state management will be contained. Keep in mind that as apps get bigger, you might need multiple files to organize your logic.
  2. We created a directory at the top of our app, listing out the classes we created.
  3. We moved (most) of our logic to the CameraController class and made them public so we can continue to use them throughout the app.

One Last Adjustment…

This particular piece is personal preference, but I like to have every new screen, widget, pop-up, bottom sheet, etc. have it’s own file. I find it easier to organize my code, easier to debug, and easier overall to think about my application.

So, entertain me, and we’re going to move the dialog box to a new file.

Create a few dart file named choice_dialog.dart, and add your normal Material import. This will also be a Stateless widget, so instead of copying everything, I’ll start out by manually adding the first section. Just type stless and you’ll automatically have the Stateless widget structure added with a prompt to name it. Call it ChoiceDialog.

Now comes the copy/paste. It automatically returns a Container(), but we want an AlertDialog(). Go back to your landing_page.dart file and copy the entire AlertDialog(), being mindful of including the right closing parenthesis and semicolons. Go to your new file, delete the Container(); and replace with everything you copied.

Your choice_dialog.dart file should look like this now:

You probably saw a lot of things break in this process, so let’s clean them up.

The original Alert Dialog was a function that returned a widget, so let’s move the function to the state file. Copy everything from the Future<void> all the way to the closing curly bracket and paste it into our CameraController class, and remove the underscore to make the function accessible.

Right now the return is empty, so let’s add ChoiceDialog();there. In Android Studio, it will import the appropriate file automatically so if it still looks broken to you, make sure to manually import your choice_dialog.dart file.

Your state.dart file should look like this now:

Everything is going smoothly, but our Landing Page is still a Stateful widget. There isn’t really any nice, smooth way to switch this over, so we’re going to just manually add and delete code to make the changes.

If you look at your new ChoiceDialog widget you can see how little code there is to implement it compared to what is used to implement a Stateful widget. There are two steps here:

  1. change StatefulWidget to StatelessWidget
  2. Delete everything from the first @override through State<LandingScreen>{

You should still have the second @override showing before your Widget build statement. It should look like this:

Fixing our Functions

If everything has gone smoothly, you should only have the red error squiggles underlining your function calls. I’m a big fan of using TODO comments to help keep track of everything I have to do, so I’m going to replace each of these functions with a TODO comment and description. Your mileage may vary.

Here is another place where personal preference plays a role, there are two methods of using the provider package to access your functions: Provider and Consumer.

When using the Provider method, you’re calling a function at the lowest part of the widget tree and only one function at a time.

For the Consumer method, you still want to use it a the lowest point possible, but you can “wrap” multiple children with it and call multiple functions inside.

We’ll use each one to set an example of each.

Staring in our choice_dialog.dart file, we’re going to go over the Provider structure.

The boiler plate code to invoke your function is as follows:

Provider.of<YourStateClassHere>(context, listen:false).yourFunctionHere();

Following this structure, we’re going to replace each “broken” function call with the above code, inserting the proper names. Android Studio allows you to automatically import the provider package and the state file as you type. If your IDE doesn’t do this, make sure you manually import these files.

When complete, your choice_dialog.dart file should look like this:

Notice that we’re using the Provider call in each place we need to call a function. If you have a section of your code where you have a lot of function calls (like a form or log in page) doing this every time would get messy. That’s where the Consumer comes in.

To use consumer, you’ll want to go up higher in your widget tree, but only high enough to capture all your functions so that you’re only repainting the smallest section possible each time a function is called. We’re going to use this method on the landing_screen.dart file.

Here we see that our widget tree goes Container -> Center -> Column, and that final Column is where we have two missing functions. So we want to wrap that Column in a Consumer widget. The boiler plate looks like this:

Consumer<YourStateClassHere>(builder: (context, stateNickName, _){
return Widget()
}),

In Android Studio, the easiest way to lay down the consumer boiler plate is to highlight the widget you want to wrap, hit Alt + Enter to bring up the menu of widget options, and choose “Wrap with Stream Builder.” Then your landing screen widget will look like this:

This makes sure all the right pieces and parenthesis are added to the tree, which can get really messy if you try to do manually. Now make the following changes:

  1. Replace <Object> with <CameraController>
  2. Change StreamBuilder to Consumer
  3. Delete the stream: null line
  4. Change snapshot to controller (or any nickname of your choice)
  5. Add , _ after your chosen nickname.

After these changes, all the error lines should disappear. If your IDE didn’t automatically import the Provider package, make sure you add it at the top of the file.

Now your landing_screen.dart file should look like this:

Now, anywhere lower in the widget tree, you can call any function from your CameraController class using the format nickname.functionName();

We have two locations here, one to decide which view you get (chosen image or blank screen) and another to call the dialog box. Changing those out will end up looking like this:

And that’s it!

Go ahead and run it on your simulator of choice, and everything should work exactly the same.

Clearly switching a Stateful widget to a different state management structure isn’t the most efficient method, and we’re going to be writing our apps with our preferred management style right from the get go. However, with the vast majority of tutorials out there teach using stateful set ups, and converting those over will look very similar to this process. It seems like A LOT of steps the first time through, but you’ll get the pattern faster than you think.

Resources

YouTube camera tutorial: LINK

Starting code: LINK

Finished code: LINK

--

--

Kristin Newman

Self taught Flutter developer. Level 2 weightlifting coach. Masters in Medical Sciences. Lover of aerial circus arts.