Flutter: Global Access vs Scoped Access with Provider

Flutter: Global Access vs Scoped Access with Provider

Flutter: Global Access vs Scoped Access with Provider

In the previous article we have seen how to build a simple authentication flow with Flutter & Firebase.

As part of this, we created three widgets:

  • A SignInPage, used to sign in the user
  • A HomePage, used to sign out the user
  • A LandingPage, used to decide which page to show

And these are composed together in the following widget tree:

As the diagram shows, all these widgets access FirebaseAuth as a global singleton variable:

// SignInPage
await FirebaseAuth.instance.signInAnonymously();
// HomePage
await FirebaseAuth.instance.signOut();
// LandingPage
FirebaseAuth.instance.onAuthStateChanged;

global-access-scoped-access-singletons.dart

Many 3rd party libraries expose their APIs via singletons, so it is not uncommon to see them used like this.

However, global access via singletons leads to code that is not testable.

And in this case, our widgets become tightly coupled to FirebaseAuth.

In practice, this means that:

  • we cannot write a test to verify that signInAnonymously() is called when the sign-in button is pressed.
  • we can’t easily swap FirebaseAuth with a different authentication service if we want to.

And we need to address these issues if we want to write clean, testable code.

So in this article we will explore a better alternative to global access.

And I will show how to write an API wrapper for FirebaseAuth in a follow-up article.

Global Access vs Scoped Access

There is one main problem with global access. And we can use our example code to illustrate this:

class SignInPage extends StatelessWidget {
  Future<void> _signInAnonymously() async {
    try {
      await FirebaseAuth.instance.signInAnonymously();
    } catch (e) {
      print(e); // TODO: show dialog with error
    }
  }
  ...
}

global-access-scoped-access-sign-in-page.dart

Here, the SignInPage asks for an instance of FirebaseAuth directly.

A better approach would be to tell our SignInPage what it needs, by passing a FirebaseAuth object to its constructor:

class SignInPage extends StatelessWidget {
  SignInPage({@required this.firebaseAuth});
  final FirebaseAuth firebaseAuth;
  Future<void> _signInAnonymously() async {
    try {
      await firebaseAuth.signInAnonymously();
    } catch (e) {
      print(e); // TODO: show dialog with error
    }
  }
  ...
}

global-access-scoped-access-sign-in-page-injection.dart

This approach is called constructor injection.

Note: Widgets in the Flutter SDK are created this way. Arguments are passed to the constructor, and used inside the widget. This guarantees that there are no side effects, because widgets are pure components that only depend on arguments that are passed explicitly.

So, should we always pass dependencies to the constructors of our widgets?

Not so fast.

Flutter uses composition heavily.

This often results in very nested widget trees, and can lead to this scenario:

Here, we have three consumer widgets that require synchronous access to a FirebaseUser object.

This object is only available inside the StreamBuilder of the LandingPage.

In order for our consumer widgets to access FirebaseUser, we would have to inject it to all intermediate widgets, even if they don’t need it directly.

I’ve been there. Trust me, it’s not fun.

To avoid this, you may be tempted to get the user with a call to _FirebaseAuth.instance.currentUser()_. However, this is an asynchronous method that returns a _Future<FirebaseUser>_. And we shouldn’t need to call an async API to get an object that we already retrieved.

So constructor injection doesn’t scale well when we need to propagate data down the widget tree.

And it is also very impractical with widgets that require a lot of arguments. Example:

class SomeComplexWidget extends StatelessWidget {
  SignInPage({@required this.firebaseAuth, @required this.firestore, @required this.sharedPreferences});
  final FirebaseAuth firebaseAuth;
  final Firestore firestore;
  final SharedPreferences sharedPreferences;
  
  // complex logic here
}

global-access-scoped-access-many-arguments.dart

I love a lot of boilerplate code — nobody ever said.

Luckily, there is a solution.

Scoped Access

Scoped access is about making objects available to an entire widget sub-tree.

Flutter already uses this technique. If you ever called Navigator.of(context), MediaQuery.of(context) or Theme.of(context), then you have used scoped access.

Under the hood, this is implemented with a special widget called [InheritedWidget](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html).

And while you could build your own widgets based on InheritedWidget, this can also lead to a lot of boilerplate code.

Well, thanks to the community, there is a better way.

Enter Provider

This library by Remi Rousselet and the Flutter team makes life easier.

In a nutshell, Provider is a dependency injection system for your Flutter apps. We can use it to expose objects of any kind (values, streams, models, BLoCs) to our widgets.

So let’s see how to use it.

We can add Provider to our pubspec.yaml file:

// pubspec.yamldependencies:  provider: ^3.0.0

And we can update our example app to use it, by making two changes:

  • Add a Provider<FirebaseAuth> to the top of our widget tree
  • Use Provider.of<FirebaseAuth>(context) where needed, instead of FirebaseAuth.instance.

Note: As an alternative to _Provider.of_, you could use _Consumer_, which is also part of the Provider package. This page on the official Flutter documentation covers their usage and differences in detail.

The updated widget tree for our example app looks like this:

The top three widgets (MyApp, Provider and MaterialApp), can be composed like this:

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider<FirebaseAuth>(
      builder: (_) => FirebaseAuth.instance,
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.indigo,
        ),
        home: LandingPage(),
      ),
    );
  }
}

global-access-scoped-access-main-provider.dart

Then, we can update our LandingPage to use Provider:

class LandingPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // retrieve firebaseAuth from above in the widget tree
    final firebaseAuth = Provider.of<FirebaseAuth>(context);
    return StreamBuilder<FirebaseUser>(
      stream: firebaseAuth.onAuthStateChanged,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.active) {
          FirebaseUser user = snapshot.data;
          if (user == null) {
            return SignInPage();
          }
          return HomePage();
        } else {
          return Scaffold(
            body: Center(
              child: CircularProgressIndicator(),
            ),
          );
        }
      },
    );
  }
}

global-access-scoped-access-landing-page-provider.dart

And do the same with the SignInPage and HomePage to use Provider:

class SignInPage extends StatelessWidget {
  Future<void> _signInAnonymously() async {
    try {
      // retrieve firebaseAuth from above in the widget tree
      final firebaseAuth = Provider.of<FirebaseAuth>(context);
      await firebaseAuth.signInAnonymously();
    } catch (e) {
      print(e); // TODO: show dialog with error
    }
  }
  ...
}

class HomePage extends StatelessWidget {
  Future<void> _signOut() async {
    try {
      // retrieve firebaseAuth from above in the widget tree
      final firebaseAuth = Provider.of<FirebaseAuth>(context);
      await firebaseAuth.signOut();
    } catch (e) {
      print(e); // TODO: show dialog with error
    }
  }
  ...
}

global-access-scoped-access-sign-in-home-page-provider.dart

Note about BuildContext: We can retrieve the FirebaseAuth object because we pass a BuildContext variable to Provider.of<FirebaseAuth>(context). You can think of context as the position of a widget in the widget tree.

Remember when I said that constructor injection is impractical with deeply nested widget trees?

Well, Provider makes objects accessible to an entire widget sub-tree.

So every time you need to propagate data down the widget tree, Provider should light-up in your brain. 💡

Wrap up

In this article we have learned about scoped access as an alternative to global access.

And we have seen how to use Provider as a dependency injection system for Flutter.

By the way, this was just a basic introduction.

There are many advanced use cases where we need to make multiple objects accessible to our widgets. Some objects may have inter-dependencies. And we may need different kinds of providers.

Provider supports all these cases. I will write more about it in the future.

For now, you can see how I’m using it in my Reference Authentication Flow with Flutter & Firebase on GitHub:

And for a more in-depth coverage of Provider and how to use it for state management, you can check my Flutter & Firebase Udemy course. This is available for early access at this link (discount code included):

Happy coding!

References

30s ad

Flutter - SQLite Database - CRUD - SUPER EASY!

Flutter & Dart Development For Building iOS and Android Apps

The Complete Flutter App Development Course for Android, iOS

The Complete Flutter and Firebase Developer Course

Dart 2 Complete Bootcamp - Go Hero from Zero in Dart Flutter

ECommerce Mobile App Development | Ecommerce Mobile App Development Services

We are leading ecommerce mobile application development company. Hire our ecommerce mobile app developer for your custom Ecommerce project at competitive rates. **Know about [Top ECommerce Mobile App Development...

We are leading ecommerce mobile application development company. Hire our ecommerce mobile app developer for your custom Ecommerce project at competitive rates.

Know about Top ECommerce Mobile App Development Company

Flutter Grocery Shopping Mobile App

Flutter Grocery Shopping Mobile App

This Flutter grocery app is a pro version that has all features of online grocery purchase for your customers.

FLUTTER GROCERY APP
Are you looking to launch your online grocery shop or any E-Commerce mobile app then this flutter grocery app will help you to build the app in just days? We offer an online grocery software system. The mobile application offers amazing features to build a powerful online ordering system or app for your grocery shop. It enhances online home grocery stuffs ordering experience for your customers with your mobile grocery app. This Flutter grocery app is a fully functional mobile app that has all features of online grocery purchase for your customers. So what are you waiting for? Start your online business with your Mobile app today!

YOu check live demo and feature video at:
https://www.youtube.com/watch?v=Y0bn_3Uu2u0

Flutter App Development Company | Hire Flutter App Developers

When it comes to Hybrid Mobile App Development Framework, Flutter is gaining the momentum. And, we as top development company don't hesitate to say that it is going to be future cross-platform mobile app development. Though there are few other frameworks available for cross-platform and hybrid mobile app development, Flutter is an increasing trend. In fact, intellectuals are comparing Flutter vs. React Native vs. Ionic and finding the best solutions out of them. Some of the top companies have already adapted Flutter, and they are very positive about it.

When it comes to Hybrid Mobile App Development Framework, Flutter is gaining the momentum. And, we as top development company don't hesitate to say that it is going to be future cross-platform mobile app development. Though there are few other frameworks available for cross-platform and hybrid mobile app development, Flutter is an increasing trend. In fact, intellectuals are comparing Flutter vs. React Native vs. Ionic and finding the best solutions out of them. Some of the top companies have already adapted Flutter, and they are very positive about it.