flutter_gadgets 0.1.0+1
flutter_gadgets: ^0.1.0+1 copied to clipboard

Flutter Gadgets, a library for state (model) management and service location in Flutter.

Flutter Gadgets 🎩 #

Flutter Gadgets logo

A library for simplified state (model) management in Flutter.

Introduction #

State management in Flutter is tough. Flutter Gadgets provides a simple solution to that, along the same principles as Redux, but in our opinion much simpler to adopt.

In essence, the library provides a main "gadget", called ObservableModel, that is the main store for application state, i.e., atomic values or references to business-logic components. All of the state is stored within the ObservableModel, and Flutter Gadgets provides the needed infrastructure to allow to access and modify state from any application component.

This has the great advantage of keeping business-logic components clean from UI and widget-lifecycle-code.

With respect to other state-management solutions, like BLoC or Provider, Flutter Gadgets has the main advantage of having a very low initial learning step, and to be felt by developers as very similar to well-known state-management techniques from other platforms, like, for example, session management in J2EE Web app.

The library closes the gap between reactive and imperative programming, by providing a simple framework that takes the best from both worlds and mixes them in an easy and understandable way.

Flutter Gadgets also provides additional features, the most prominent one being a simple service-location framework.

How it works #

Flutter Gadgets consists of six main components:

  1. AppGadget
  2. ServiceContainer
  3. ObservableModel
  4. ModelGadget
  5. SubscriberGadget
  6. ViewModelGadget
Service Location

Service location is based on a ServiceContainer, a repository of services that is fully indipendent from anything Flutter related (in particular BuildContext).

Model Management

On the other hand, model management is insured by the usage of ObservableModel and ModelGadget, combined with one or more SubscriberGadget(s).

By observing an object from the model, a subscriber gadget gets notified when its associated model instance changes and rebuilds instantly.

How It Works

The advantage of this approach is that the business logic components will remain pure data holders with logic: in this way, model instances could be easily shared between frontend and backend projects. Moreover, by providing fine control over the subscription scope (instance level or property level), the number of rebuilds needed when an object is update can always be minimized.


Counter Example #

import 'package:flutter/material.dart';
import 'package:flutter_gadgets/flutter_gadgets.dart';

class Counter {
  int count = 0;

  void increment() => count++;
}

void main() {
  runApp(
    AppGadget(
      beans: {toBeanKey(Counter): Counter()},
      child: App(),
    ),
  );
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Counter'),
        ),
        body: Center(
          child: SubscriberGadget<Counter>(
            builder: (_, counter) => Text('Count: ${counter.count}'),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            final model = ServiceContainer.instance.observableModel;
            model.getBean<Counter>().increment();
            model.notifyFor(Counter);
          },
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

The Counter class is pretty straightforward: is a plain Dart object that holds a count property and exposes an increment method.

When the application starts, AppGadget registers a new Counter instance in the ObservableModel: now the counter object is globally available.

To bind the value of the counter instance to a widget, a Text widget is wrapped in a SubscriberGadget: in this way, it gets access to the counter object and will update when necessary.

The task of updating the model instances and the interface is delegated to a control action: in this case, the onPressed callback of the FloatingActionButton will increment the count variable of the counter instance and will trigger the rebuild of the counter subscriber.


Components #

AppGadget #

The root of a Flutter Gadgets application.

It sets up the whole infrastructure, initializing the provided services and business logic components.

void main() {
  runApp(AppGadget(
    beans: {
      toBeanKey(MyBean): MyBean()
    },
    services: {
      MyService: MyService()
    },
    child: MyApp(),
  ));
}

The beans parameter is a <String, Object> map of all the logic components needed at the start of the application: AppGadget automatically adds them to the ObservableModel. Every instance in the model is identified using a String: the toBeanKey(Type type, {String token, String property}) utility method can be used to generate a String key starting from a type and combining it with an optional token and / or property.

The services parameter is a <Type, Object> map that stores all the services of the application (Controllers, DAO, ...).

ServiceContainer #

A singleton used to store the immutable services map.

A service can be added to the container only by using the services property of AppGadget and can be retrieved by using the ServiceContainer.instance.get<MyServiceType>() method.

The ServiceContainer.instance.observableModel is a shortcut for retrieving the ObservableModel.

ObservableModel #

A component used to store the shared state of the application.

It is automatically added as a service by AppGadget.

Since a business logic component is identified in the bean map using a string, whenever you need to access an instance, you can use the getBean<Type>([String token]) method: the bean key is generated by the method itself.

A component can be manually added to the map by using the void putBean<T>(Object bean, {String token, List<String> properties, bool notify = true}) method: if the notify parameter is set as false, the ObservableModel will not notify its subscribers.

The notifyFor(Type type, {String token, List<String> properties}) method can be used to notify the change of a bean identified by the given type and the given token, while the optional properties list identifies all the properties that changed.

For example, the following code shows how to trigger the update for a given type:

ServiceContainer.instance.observableModel.notifyFor(MyType);

Instead, if you want to specify what properties changed, you can use the following code:

ServiceContainer.instance.observableModel.notifyFor(
  MyType, 
  properties: ['first', 'second']
);

It is implemented as a ChangeNotifier and its only subscriber is ModelGadget.

ModelGadget #

Implemented as an InheritedModel, it is the only ObservableModel subscriber and it's used for updating SubscriberGadget(s).

Its usage is completely transparent to the user since it is fully managed by the library.

Do not use this widget manually!

SubscriberGadget #

A widget that observes a bean.

It can watch a single bean (or even single / multiple properties of it!) and rebuild accordingly.

Whenever you need to access a business logic component in a widget, you can wrap it in a SubscriberGadget:

SubscriberGadget<MyBean>(
  builder: (context, myBean) {
    return Text(myBean.internalState);
  }
)

In this way, when the ObservableModel will notify a change for MyBean it will automatically rebuild.

You can also specify a list of properties you want to observe:

SubscriberGadget<MyBean>(
  properties: ['first', 'second']
  builder: (context, myBean) {
    return Text('${myBean.first} ${myBean.second}');
  }
)

By specifing the properties, the subscriber will rebuild only when the ObservableModel will notify a change for those specific properties of the bean.

Remeber: if you just need to get access to a model instance without subscribing to it, you can always retrieve the bean directly from the ObservableModel by getting a reference to it from the ServiceContainer.

ViewModelGadget #

A gadget that encapsulates a view model.

A view model is intended as the internal mutable state of a view. An example would be:

class MyViewModel {
  String errorText;
  TextEditingController controller;
}

In order to correctly initialize and dispose the view model, a ViewModelGadget can be created specifying the initState and dispose callbacks, that get executed when the underlying stateful widget is initialized and disposed.

ViewModelGadget<MyViewModel>(
  MyViewModel(),
  initStateCallback: (myViewModel) {
    myViewModel.controller = TextEditingController();
  },
  disposeCallback: (myViewModel) {
    myViewModel.controller.dispose();
  },
  child: MyView()
)

When the ViewModelGadget is created, it automatically registers and handles a MyViewModel instance in the ObservableModel: in this way, MyView can access its state and subscribe to it by using:

SubscriberGadget<MyViewModel>(
  builder: (_, viewModel) => TextField(
    controller: viewModel.controller,
    decoration: InputDecoration(
      labelText: 'Name',
      hintText: 'Enter your name',
      errorText: viewModel.errorText,
    ),
  ),
)
6
likes
110
pub points
0%
popularity

Publisher

svelto.tech

Flutter Gadgets, a library for state (model) management and service location in Flutter.

Repository

Documentation

API reference

License

BSD (LICENSE)

Dependencies

flutter

More

Packages that depend on flutter_gadgets