Using Flutter for Line Of Business apps – State management with provider

History

When I started exploring Flutter, there was (and there still is) a lot of discussion about state management. At that time, Google promoted Inherited Widget as the way to propagate information down the widget tree. I tried Inherited Widget, and indeed with some extra code, you could use it to make your state accessible to your widgets. But it involved quite some boilerplate, which was not easy to read and understand. It felt overly complex and likely to introduce errors that are difficult to spot.

After that, I tried get_it. That was easy and simple. It looked a lot that I was used to when I was working in C#. But I was not sure. I found a lot of happy users of get_it. But there were also a lot of people calling the foundation of get_it an anti-pattern. Although I did not really understand the discussion at that time, the choice was made for me by Google. They explicitly recommended Provider in their talk Pragmatic State Management in Flutter (Google I/O’19)

Provider

With Provider, you can wrap your class with one statement into an Inherited Widget.

In the code below, a Provider of an S080StateService is created. When this S080StateService is created, it’s dependencies are injected via its constructor. Provider is used to find these dependencies in the tree above.

  Provider<S080StateService>(
    create: (BuildContext context) {
      return S080StateService(
        Provider.of<LoggingService>(context, listen: false),
        Provider.of<LadenLossenBoxService>(context, listen: false),
        Provider.of<ConfiguratieBoxService>(context, listen: false),
        Provider.of<UnloadingStateService>(context, listen: false),
        Provider.of<CommunicatieBoxService>(context, listen: false),
        Provider.of<NotInteractiveCommunicationService>(context, listen: false),
        Provider.of<NavigatorService>(context, listen: false),
      );
    },
  )

Below this provider, the instance of the S080StateService is accessible by:

var state = Provider.of<S080StateService>(context);

The way this works is readable and easy to understand until you have a lot of services, that depends on a lot of other services.

Avoid super-nesting

When your code is dependent on many services, the code gets easily unreadable. Every Provider creates a parent-child relationship and therefore indents your code.

The indentation can easily be solved by using the MultiProvider widget. When you supply a list of widgets to the MultiProvider widget, the MultiProvider will take care of the parent-child relationship at runtime.

In code:

  • MultiProvider
    • Provider1
    • Provider2
    • Provider3
    • Provider4

At runtime:

  • MultiProvider
    • Provider1
      • Provider2
        • Provider3
          • Provider4

This improves readability a lot.

Another improvement can be made to move the list of Providers that you feed to the MultiProvider to a separate file.

class S080UnloadingSignaturePage extends StatefulWidget {
  static get Route => RouteEx<S080UnloadingSignaturePage>(
        builder: (context) => S080UnloadingSignaturePage(),
      );
  @override
  _S080UnloadingSignaturePageState createState() =>
      _S080UnloadingSignaturePageState();
}

class _S080UnloadingSignaturePageState
    extends State<S080UnloadingSignaturePage> {
  final SignatureController _controller =
      SignatureController(penColor: CompanyColor.companyBlue);

  @override
  Widget build(BuildContext context) {
    double bottomHeight = 80.0;
    return MultiProvider(
      providers: providers,
      child: Builder(builder: (BuildContext context) {
        var unloadingStateService = Provider.of<UnloadingStateService>(context);
        var state = Provider.of<S080StateService>(context);
        return Scanner(

Here the list of providers is just one line, on line 19. The actual list of providers is in a separate file:

List<SingleChildWidget> providers = [
  Provider<DialogService>(
    create: (BuildContext context) {
      return DialogService(
        context,
        Provider.of<ValidatorService>(context, listen: false),
        Provider.of<DatawedgeService>(context, listen: false),
        Provider.of<SoundService>(context, listen: false),
      );
    },
  ),
  Provider<S080StateService>(
    create: (BuildContext context) {
      return S080StateService(
        Provider.of<LoggingService>(context, listen: false),
        Provider.of<LadenLossenBoxService>(context, listen: false),
        Provider.of<ConfiguratieBoxService>(context, listen: false),
        Provider.of<UnloadingStateService>(context, listen: false),
        Provider.of<CommunicatieBoxService>(context, listen: false),
        Provider.of<NotInteractiveCommunicationService>(context, listen: false),
        Provider.of<NavigatorService>(context, listen: false),
      );
    },
  ),
];

Although I am not convinced that this is the very best way, it gets the job done. The code is readable and understandable. And, if you make a mistake, the error message is usually so well detailed, that finding and fixing the issue is easy.

Published by Sander Roest

Professional app developer.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: