Giter Site home page Giter Site logo

subtree's Introduction

Build Status codecov

Subtree is a state manager for those who like BLoC but don't like BLoC verbosity.

BLoC difference

  • Subtree separates state injection from state observing mechanism.
  • Providing a compact mechanism for observing state instead of verbose Builder concept.
  • Events (Actions) are not separate objects, just regular function calls, which hugely reduce boilerplate.
  • Don't try go into all application layers. Stays only on presentation layer.
class ExamplePageState {
  final title = ValueNotifier("...");
}

abstract class ExamplePageActions {
  void buttonClicked();
}

// .....

void build(BuildContext context) {
  final state = context.subtreeGet<ExamplePageState>(); // getting subtree state
  final actions = context.subtreeGet<ExamplePageActions>();

  // watch on state.title change with ref.watch function.
  return Obx((ref) =>
      TextButton(
          onPressed: actions.buttonClicked, child: Text(
          ref.watch(state.title)
      )));
}

The state is a simple class, you can put any reactive primitives into it. Obx and ref.watch is designed to reduce the verbosity of ValueListenableBuilder. You can watch any primitive that implements the ValueListenable interface.

But if you need you can use other reactive primitives and builders for them, like StreamBuilder.

class ExamplePageState {
  final title = BehaviorSubject<String>();
}

void build(BuildContext context) {
  final state = context.subtreeGet<ExamplePageState>();

  return StreamBuilder<String>(
      stream: state.title,
      builder: (BuildContext context, AsyncSnapshot<String> titleSnapshot) {
        ...
      });
}

A trivial example of a counter

It is common practice to use a "counter" example to demonstrate state management usage.
But it is too far from the real use case. Let's a little bit complicate it and assume the counter state is located on the backend.

// CounterAPI doing http call to backend API.
class CounterAPI {
  Future<int> getCounterValue();

  Future<int> incCounterValue();

  Future<int> decCounterValue();
}


// counter_model.dart
class CounterState {
  final counter = Rx<int>(0);
  final loaded = Rx<bool>(false);
  final blocked = Rx<bool>(false);
}

abstract class CounterActions {
  void incCounter();

  void decCounter();
}

//counter_controller.dart

class CounterController extends SubtreeController implements CounterActions {
  final state = CounterState();

  @protected
  final CounterAPI counterAPI;

  CounterController({required this.counterAPI}) {
    subtreeModel.putState(state);
    subtreeModel.put<CounterActions>(this);
    loadData();
  }

  void loadData() async {
    final counter = await counterAPI.getCounterValue();
    state.counter.value = counter;
    state.loaded.value = true;
  }

  // Actions implementations
  @override
  void incCounter() async {
    state.blocked.value = true;

    final newCounterValue = await counterAPI.incCounterValue();
    state.counter.value = newCounterValue;

    state.blocked.value = false;
  }

  @override
  void decCounter() async {
    // implemented in the same way as incCounter.
  }
}

// counter_screen.dart
class CounterScreen extends StatelessWidget {
  const CounterScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final state = context.subtreeGet<CounterState>();
    final actions = context.subtreeGet<CounterActions>();

    return Scaffold(
        appBar: AppBar(title: const Text('Counter')),
        body: Obx((ref) {
          if (!ref.watch(state.loaded)) {
            return const Center(child: CircularProgressIndicator());
          }
          return Stack(children: [
            Center(
              child: Column(children: [
                Text('Counter: ${ref.watch(state.counter)}'),
                MaterialButton(
                  onPressed: actions.incCounter,
                  child: const Text('+'),
                ),
                MaterialButton(
                  onPressed: actions.decCounter,
                  child: const Text('-'),
                )
              ]),
            ),
            if (ref.watch(state.blocked))
              ...[const Opacity(
                opacity: 0.2,
                child: ModalBarrier(dismissible: false, color: Colors.black),
              ),
                const Center(child: CircularProgressIndicator())
              ]
          ]);
        }));
  }
}

Now we need to bind all things together. Usually, this is done on the router level.

ControlledSubtree(
  subtree: const CounterScreen(),
  controller: () => CounterController(counterAPI: services.counterAPI),
);

Because subtree widgets and the controller not depending directly on each other, they can be independently tested. Also, you can have different widgets for different platforms, or special mock controllers with preset data for demo mode.

ControlledSubtree(
  subtree:  isDesktop ? const CounterScreenDesktop() : const CounterScreen(),
  controller: () => isDemo ? MockCounterController() : CounterController(counterAPI: services.counterAPI),
);

subtree's People

Contributors

vkubiv avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.