mwb 2.0.0 icon indicating copy to clipboard operation
mwb: ^2.0.0 copied to clipboard

Flutter package for the Model-Widget-BLoC pattern

example/README.md

MWB samples #

These sample apps demonstrate how the MWB pattern could be implemented within a small scope.

Both app use the JSON placeholder API for simple data display/usage.

General Architecture #

The general approach with the MWB pattern is to separate business logic and data access from UI classes. This can benefit the app since you'll be able to test abstract logic separately, have a more clean architecture and refrain from mixing code in places where it doesn't really belong.

In the image above you see how the sample app uses this pattern. A page (analog to an Activity or a ViewController) accesses the Bloc instance via the enveloping BlocProvider widget and executes functions inside its bloc member variable.

Setup #

In order to provide a Bloc instance to it's widget scope you'll first need to create a BlocProvider widget.

NOTE: Any instance of a BlocProvider widget needs to be created "above" the widget-tree scope it provides for to work properly.

To keep things simple for that you can use a static "factory pattern" (but not an actual factory function) instead of a widget instance, which would always create a BlocProvider and a Bloc instance around your page.

class PostsPage extends StatefulWidget {
  static Widget create() {
    return BlocProvider(
      bloc: PostsPageBloc(),
      child: PostsPage(),
    );
  }
}

That way you'd have less of a hassle to make Bloc creation consistent.

Bloc access (example/reactive_ui) #

Whenever you'll need to access a bloc instance you should first assign it to a local variable inside the State of your widget.

@override
void initState() {
  super.initState();
  _bloc = BlocProvider.of(context);
}

You can then access data or execute functions of that Bloc.

For example, inside the reactive_ui sample, you can refresh your list of posts by tapping the IconButton inside the AppBar. That Button executes a Bloc functions that updates the list of posts.

IconButton(
    icon: Icon(Icons.refresh),
    onPressed: _bloc.refreshList,
)

Reactive UI #

Oftentimes your UI needs to display data from another source and react to its changes along the way. The MWB pattern does that by combining Subjects from RxDart and simple StreamBuilders.

The reactive_ui sample demonstrates this by storing the list of all loaded posts inside a BehaviorSubject.

final BehaviorSubject<List<Post>> _postsSubject =
      BehaviorSubject.seeded(null);
Stream<List<Post>> get postsStream => _postsSubject.stream;

Short explanation: A Subject in RxDart is essentially just a wrapper with a Stream and a StreamSink to receive and broadcast data. A BehaviorSubject in particular also stores it's latest received value and broadcasts it for every new listener.

After receiving a new list of posts within the pages Bloc (see: Setup) the page UI can rebuild it's list according to the changes with a StreamBuilder.

StreamBuilder(
  stream: _bloc.postsStream,
  builder: (BuildContext context, AsyncSnapshot<List<Post>> snapshot) {
    if (snapshot.hasData) {
      return ListView(
        children: snapshot.data
          .map((post) => _createPostListItem(post))
          .toList(),
      );
    } else {
      return Center(
        child: Text("No posts yet"),
      );
    }
  },
)

Why use RxDart? #

Unlike in other languages and frameworks that are supported by Rx, RxDart doesn't come with a whole lot of patterns and structures that need to be implemented first so that the developer/user can benefit from it. RxDart is merely an extension of the already existing Stream API of Dart, which you should be familiar with in Flutter. What RxDart brings along are a set of helper- and extension-functions and classes based on Dart Stream API which are especially useful for business logic.

Bloc-UI contracts (example/bloc_ui_contract) #

Even though using Subjects and StreamBuilders has very clear and powerful benefits, not all cases are covered in a simple way with them.

This is where MWB diverges from the usual Bloc pattern and becomes more like MVP. Sometimes it is just enough to use an interface and trigger actions of the UI directly.

The bloc_ui_contract sample shows this. Since a Bloc class is supposed to be kept independent from the UIs logic, it can't execute any functions of a State or a Widget. That's why MWB uses an interface as a "contract" to tell a Bloc, what kind of functions can be executed on the UI.

abstract class CreatePostReceiver {
  void showMessage(String message);
}

The showMessage function in this case is a one-time action which would be difficult to implement with a StreamBuilder. That's why the pages State implements the defined contract and assigns itself as a receiver for any Bloc-driven actions

class _CreatePostState extends State<CreatePostPage> implements CreatePostReceiver{
    @override
    void initState() {
      super.initState();
      _bloc = BlocProvider.of(context);
      _bloc.receiver = this;
    }
    
    @override
    void showMessage(String message) {
      _scaffoldKey.currentState.showSnackBar(SnackBar(
        content: Text(message),
      ));
    }
}

which the Bloc instance then uses.

receiver?.showMessage("New post created with ID ${post.id}");

Global app architecture #

The MWB pattern can also be used to create a global app state which provides access to different kind of objects from anywhere within the app. This is done with an AppBloc. Like an Application class in Android this specific Bloc has only 1 instance for the entire app and acts as a holder for all global singletons. This also applies for the AppBloc's BlocProvider widget instance.

In order to keep the AppBloc instance global you should inject it at the highest possible place inside the widget tree. A usual place would be where the MaterialApp instance is being created.

class TestApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      bloc: AppBloc(),
      child: MaterialApp(
        title: 'Flutter Demo',
        home: PostsPage.create(),
      ),
    );
  }
}

For global access to singletons inside your AppBloc a useful thing to have is a mixin.

mixin AppBlocProvider {
  AppBloc get appBloc => AppBloc._instance;
  AppSettings get appSettings => appBloc.appSettings;
  Repository get repository => appBloc.repository;
  NavigationService get navService => appBloc.navigationService;
}
0
likes
130
pub points
43%
popularity

Publisher

unverified uploader

Flutter package for the Model-Widget-BLoC pattern

Documentation

API reference

License

Icon for licenses.BSD-3-Clause (LICENSE)

Dependencies

flutter

More

Packages that depend on mwb