Containers

After capsules, Containers are the next high-level piece you should understand in ReArch. Containers get their name from the fact that they "contain" capsules' data. Even if you exclusively use Flutter with flutter_rearch (which provides the RearchBootstrapper widget over using containers directly), it is still a good idea to understand how data is handled in the background.

So What Exactly Is a Container?

In short, containers are just a fancy HashMap wrapper. They map capsules to their internal states and data. Containers act lazily and cache data where possible, invoking capsules (remember--capsules are just functions) to instantiate the container's cache.

Containers provide:

  • read() to read the current data of some capsules
  • listen() to temporarily listen to changes in a set of capsule(s)

Each implementation may include other methods on containers, but you can consult the api level reference/documentation to learn more about those particular methods. They tend to solve very niche/advanced use cases.

Reading Capsules

Reading Capsules
int countCapsule(CapsuleHandle _) => 0;
int countPlusOneCapsule(CapsuleHandle use) => use(countCapsule) + 1;

final container = CapsuleContainer();
final count = container.read(countCapsule);
final countPlusOne = container.read(countPlusOneCapsule);

Important Rust-only Information

Notice how we passed a tuple into read() above; this is very important. This ensures a consistent read amongst capsules, in addition to reducing overhead. Take the following scenario:

let count = container.read(count_capsule);
let count_plus_one = container.read(count_plus_one_capsule);

Say another thread increments the container's count after that first line; count would then be 0 and count_plus_one would be 2! Thus, group all container reads together when possible, even if you might not always need the state of the capsules you are reading! (This differs from within a capsule's build, where you are encouraged to conditionally call get(some_capsule) in order to skip some rebuilds.)

Also of importance:

  • Containers have cheap clones (Arc is used internally), so clone away
  • Containers are thread safe and are meant to be shared across threads
  • Containers are lock free and are highly readable, with writes blocked by a Mutex

Listening to Changes

To temporarily listen to capsules, see the following example.

Temporarily Listening to Capsules
final container = CapsuleContainer();
final disposeListener = container.listen((use) => print(use(fooBarCapsule)));

// When you no longer need to listen to changes:
disposeListener();

If you want to listen to capsule changes throughout the lifetime of a Container, do not use listen(). Instead, create a non-idempotent capsule (a fancy way to say a capsule that registers a side effect) and read it once to ensure it has been initialized and is listening.

Indefinitely Listening to Capsules
void listener(CapsuleHandle use) {
  // asListener is a no-op side effect that you can use
  // to declare capsules as listeners
  use.asListener();

  // Listen to your data like normal
  print(use(countCapsule));
}

// Initialize the listener so it will get updates
final container = CapsuleContainer();
container.read(listener);