An atomic state management library for dart (and Flutter). Simple, fast and flexible.

Overview

nucleus

An atomic dependency and state management toolkit.

Design goals

  • Simplicity - simple API with no surprises
  • Performance - allow for millions of Atom's without a high penalty
  • Composability - make it easy to create your own functionality

Installation

If you are using flutter, then all you need is the flutter_nucleus package:

flutter pub add flutter_nucleus

If you are working only with dart, then install the nucleus package:

dart pub add nucleus

A quick tour

The first thing you might need in your Flutter app is some shared state.

With nucleus, you can use a stateAtom to achieve this.

stateAtom

First define your atom, in this case we want to represent the state of a counter.

final counter = stateAtom(0);

By default, all atom's are created lazily (only when they are used) and are automatically disposed when they are no longer needed.

If you want to disable the automatic disposal of your atom, then call keepAlive():

final counter = stateAtom(0).keepAlive();

We can then use the AtomBuilder widget from the flutter_nucleus package to listen for changes to our counter:

class CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AtomBuilder((context, get, child) {
      final count = get(counter);
      return Text(count.toString());
    });
  }
}

Or if you are using flutter_hooks, you can useAtom:

class CounterText extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final count = useAtom(counter);
    return Text(count.toString());
  }
}

flutter_nucleus BuildContext methods

You can then update the counter state by using the updateAtom method, which is added to the BuildContext with an extension.

This creates an updater function which you can use in your widgets:

class CounterButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final updateCount = context.updateAtom(counter);

    return ElevatedButton(
      onPressed: () {
        updateCount((count) => count + 1);
      },
      child: const Text("Increment"),
    );
  }
}

Other methods on BuilderContext include:

  • getAtom - for reading an atom once.

  • setAtom - similar to updateAtom, but allows you to set the atom value directly.

    final setCount = context.setAtom(counter);
    
    // ...
    
    setCount(2); // Sets the value of the counter atom to 2.
  • refreshAtom - if you flagged an atom as refreshable(), then you can use this method to refresh it.

    final users = atom((get) => get(usersRepo).allUsers()).refreshable();
    
    // ...
    
    context.refreshAtom(users);
  • registry - access the AtomRegistry directly

    final registry = context.registry(listen: true);

atom

We can now create and track some shared state in our app, but now we might want to create derived state from other atoms.

A common use for this is dependency injection. First we can define our shared dependency:

final httpClient = atom((get) => MyHttpClient());

It is worth noting that atom creates a read-only atom (the type is ReadOnlyAtom<T>). Earlier stateAtom(0) created a WritableAtom<int, int> (the first generic type is the read type, the second is the write type).

Now we can create our derived atom:

final userRepository = atom((get) => UserRepository(
  httpClient: get(httpClient), // Here we are accessing another atom within our atom
));

If the httpClient ever changed, then our userRepository would also re-create itself.

If you want to allow your atom to be refreshed / rebuilt externally, then call .refreshable() (similar to .keepAlive()):

final userRepository = atom((get) => UserRepository(
  httpClient: get(httpClient), // Here we are accessing another atom within our atom
)).refreshable();

// then using a BuildContext:

context.refreshAtom(userRepository);

The get parameter is actually an AtomContext instance, which also has some other helpful methods attached. View them all here:

https://pub.dev/documentation/nucleus/latest/nucleus/AtomContext-class.html#instance-methods


At this point, with stateAtom and atom you probably have all you need to build a simple stateful app. But what about async / await and Stream?

futureAtom

If you need to work with Future's in your app (which pretty much every app does!), then futureAtom makes it easy.

Earlier we created a userRepository atom, let's use that to fetch some users:

final allUsers = futureAtom((get) => get(usersRepository).fetchAll());

A futureAtom gives us back a FutureValue<T>, which can then be used in your widgets:

@override
Widget build(BuildContext context) => AtomBuilder((context, get, child) {
  final users = get(allUsers);

  return users.when(
    data: (users) => UserList(users: users),
    loading: (previousData) => LoadingIndicator(),
    error: (err, stackTrace) => ErrorMessage(error: err),
  );
});

As you can see, FutureValue allows us to write async code is an easy to understand way. It has a couple of other methods to aid us too, take a look here:

https://pub.dev/documentation/nucleus/latest/nucleus/FutureValue-class.html#instance-methods

Need to access the actual Future?

futureAtom uses atomWithParent in its implementation (take a look), which exposes the Future through the .parent property. Here we await the allUsers future, and return a count of the results.

final userCount = futureAtom((get) async {
  final users = await get(allUsers.parent);
  return users.length;
});

streamAtom

Similar to futureAtom, streamAtom turns a Stream into a FutureValue containing the latest value.

final latestUser = streamAtom((get) => get(userRepository).latestUserStream());

You can then use it in your widgets:

@override
Widget build(BuildContext context) => AtomBuilder((context, get, child) {
  final userState = get(latestUser);

  // `dataOrNull` will be `null` until we first receive some data.
  final user = userState.dataOrNull;

  // `isLoading` will be `true`, until the `Stream` completes.
  final isLoading = userState.isLoading;

  if (user == null) {
    return LoadingIndicator();
  }

  return UserProfile(
    user: user,
    isLoading: isLoading,
  );
});

The underlying Stream is available through latestUser.parent.

atomWithParent

Both futureAtom and streamAtom use atomWithParent to tie the state with the thing that generates it.

You can use this pattern too! Lets create our own custom atom type for using a ValueNotifier from the flutter SDK. It will:

  • Correctly dispose the notifier when done
  • Correctly remove listeners
  • Allow us to directly access the notifier so we can set its value
AtomWithParent<T, Atom<ValueNotifier<T>>> valueNotifierAtom<T>(
  AtomReader<ValueNotifier<T>> create,
) =>
    atomWithParent(atom((get) {
      final notifier = create(get);
      get.onDispose(notifier.dispose);
      return notifier;
    }), (get, parent) {
      final notifier = get(parent);

      void handler() {
        get.setSelf(notifier.value);
      }

      notifier.addListemer(handler);
      get.onDispose(() => notifier.removeListener(handler));

      return notifier.value;
    });

Now lets use it.

final counter = valueNotifierAtom((get) => ValueNotifier(0));

// ... in your widgets

// Watch the current count
AtomBuilder((context, get, child) => Text('${get(counter)}'));

// Update the notifier value
context.getAtom(counter.parent).value = 123;

We can now get the current count by watching counter, and access the notifier through counter.parent.

We just implemented our own custom atom type! I hope this shows you just how composable atoms can be.

atomFamily

If you ever need to group atoms together, then you can use atomFamily.

Lets say you wanted to fetch a user by their ID number, and wanted to represent this as an atom. You need a way to pass in the id parameter.

Using our userRepository from earlier, this is how you would do it with atomFamily:

final userById = atomFamily((int id) {
  return futureAtom((get) => get(userRepository).fetchUser(id));
});

Now in your widgets, you can use the atom just like another futureAtom:

@override
Widget build(BuildContext context) {
  return AtomBuilder((context, get, child) {
    final futureValue = get(userById(userId));

    return futureValue.when(
      data: (user) => UserProfile(user: user),
      loading: (previousData) => LoadingIndicator(),
      error: (err, stackTrace) => ErrorMessage(error: err),
    );
  });
}

stateAtomWithStorage

Have a stateAtom, but want to make sure it is persisted between app restarts? Then you can use stateAtomWithStorage.

First, you need to define your NucleusStorage atom. Let's use the SharedPrefsStorage implementation from the flutter_nucleus package.

/// This atom will eventually hold our [SharedPreferences] instance.
/// We need to make sure we call `keepAlive` so it doesn't get automatically
/// disposed.
final sharedPrefsAtom = atom<SharedPreferences>((get) => throw UnimplementedError()).keepAlive();

/// This atom will eventually hold our [SharedPrefsStorage] instance, which
/// implements [NucleusStorage].
final sharedPrefsStorage = atom((get) => SharedPrefsStorage(get(sharedPrefsAtom)));

void main() async {
  // Build our [SharedPreferences] instance
  final sharedPrefs = await SharedPreferences.build();

  return runApp(AtomScope(
    // Pass the instance to [initialValues]
    initialValues: [sharedPrefsAtom.withInitialValue(sharedPrefs)],
    child: const YourApp(),
  ));
}

Now instead of using stateAtom, we can use stateAtomWithStorage:

final counter = stateAtomWithStorage(
  0,
  storage: sharedPrefsStorage, // Atom we defined earlier
  key: 'counter', // Unique string key

  // Override these depending on your state model
  fromJson: (json) => json as int,
  toJson: (count) => count,
);

atomWithStorage

For more advanced usage of persistence, you can use atomWithStorage.

Let's use our valueNotiferAtom from earlier, and add persistence support!

AtomWithParent<T, Atom<ValueNotifier<T>>> valueNotifierAtomWithStorage<T>(
  ValueNotifier<T> Function(
    AtomContext<ValueNotifier<T>> get,
    T? initialValue,
  ) create, {
  required String key,
  required T Function(dynamic) fromJson,
  required dynamic Function(T) toJson,
}) =>
    // Use `atomWithStorage` for the parent atom
    atomWithParent(atomWithStorage(
      (get, read, write) {
        // Create the notifier, potentially using the stored value
        final notifier = create(get, read());

        // Add a listener to persist the value to storage
        notifier.addListener(() {
          write(notifier.value);
        });

        get.onDispose(notifier.dispose);
        return notifier;
      },
      key: key,
      fromJson: fromJson,
      toJson: toJson,

      // We will use the `NucleusStorage` atom from the `stateAtomWithStorage`
      // example
      storage: sharedPrefsStorage,
    ), (get, parent) {
      final notifier = get(parent);

      void handler() {
        get.setSelf(notifier.value);
      }

      notifier.addListemer(handler);
      get.onDispose(() => notifier.removeListener(handler));

      return notifier.value;
    });

Then we can use it, and the counter will not reset back to 0 after the app restarts:

final counter = valueNotifierAtomWithStorage(
  (get, int? initialValue) => ValueNotifier(initialValue ?? 0),
  key: 'counter',
  toJson: (count) => count,
  fromJson: (json) => json as int,
);

// ... in your widgets

// Watch the current count
AtomBuilder((context, get, child) => Text('${get(counter)}'));

// Update the notifier value
context.getAtom(counter.parent).value = 123;

writableAtom

TODO

Until some docs are written, take a look at the stateAtomWithStorage implementation:

https://pub.dev/documentation/nucleus/latest/nucleus/stateAtomWithStorage.html#source

writableAtom gives you full access to the atom read / write API surface.

You might also like...

An extension to the bloc state management library which automatically persists and restores bloc states.

An extension to the bloc state management library which automatically persists and restores bloc states.

⚠️ Attention: This repository has been moved to https://github.com/felangel/bloc and is now read-only! An extension to the bloc state management libra

Nov 17, 2022

Nexus is a state management library that makes it easy to create and consume your application's reactive data to the user interface.

Nexus 🚀 Nexus is a state management library that makes it easy to create and consume your application's reactive data to the user interface. With nex

Sep 7, 2022

A state management library that enables concise, fluid, readable and testable business logic code.

A state management library that enables concise, fluid, readable and testable business logic code.

Creator is a state management library that enables concise, fluid, readable, and testable business logic code. Read and update state with compile time

Dec 24, 2022

An easy Flutter state management library

An easy Flutter state management library

EbloX An easy Flutter state management library.It is similar to Bloc, but it uses a lot of annotations and separates business logic from UI through th

Oct 29, 2022

💻 Flutter clean architecture using the bloc & cubit library for state management

💻 Flutter clean architecture using the bloc & cubit library for state management

Egymation 🎥 · This application was developed using a well-defined and decoupled architecture, following TDD (test-driven programming) as a working me

Nov 21, 2022

This project follows the Reso Coder course for flutter test-driven-development with clean architecture and BloC state management for a random trivia simple app.

This project follows the Reso Coder course for flutter test-driven-development with clean architecture and BloC state management for a random trivia simple app.

Jan 5, 2022

A simple state management solution that combine the power of inherited widget and rxdart

Inherited RxDart is a state management library that combine the power of InheritedWidget and RxDart, a simple and elegant state management solution for apps of any scale

Oct 26, 2022

a simple yet powerful state management technique for Flutter

a simple yet powerful state management technique for Flutter

States_rebuilder `states_rebuilder` is Flutter state management combined with a dependency injection solution and an integrated router to get the best

Jan 2, 2023
Owner
Tim
Tim
State Persistence - Persist state across app launches. By default this library store state as a local JSON file called `data.json` in the applications data directory. Maintainer: @slightfoot

State Persistence Persist state across app launches. By default this library store state as a local JSON file called data.json in the applications dat

Flutter Community 70 Sep 28, 2022
An extension to the bloc state management library which lets you create State Machine using a declarative API

An extension to the bloc state management library which lets you create State Machine using a declarative API

null 25 Nov 28, 2022
Flutter Control is complex library to maintain App and State management. Library merges multiple functionality under one hood. This approach helps to tidily bound separated logic into complex solution.

Flutter Control is complex library to maintain App and State management. Library merges multiple functionality under one hood. This approach helps to

Roman Hornak 23 Feb 23, 2022
A Dart dependency injection library aimed to be flexible, predictable and easy to use.

dino Dino is a Dart dependency injection library with optional code generation. It was inspired by DI in .NET and aimed to be flexible, predictable an

null 3 Dec 20, 2022
Simple and fast Entity-Component-System (ECS) library written in Dart.

Fast ECS Simple and fast Entity-Component-System (ECS) library written in Dart. CPU Flame Chart Demonstration of performance for rotation of 1024 enti

Stanislav 8 Nov 16, 2022
Practice building basic animations in apps along with managing app state by BLoC State Management, Flutter Slider.

Practice building basic animations in apps along with managing app state by BLoC State Management including: Cubit & Animation Widget, Flutter Slider.

TAD 1 Jun 8, 2022
A powerful state machine for MobX management, that can be used in almost any application state.

A powerful state machine for MobX management, which can be used in almost any application state. It has 3 states - loading, success, error - and is pe

Daniel Magri 8 Oct 31, 2022
Shopify Tag and Product Management App using Flutter and Riverpod State Management

Myshopify App A Simple Flutter Application project to get List of Tags, Products and Product Details from shopify https://shopicruit.myshopify.com/adm

Idowu Tomiwa 5 Nov 12, 2022
Fluro is a Flutter routing library that adds flexible routing options like wildcards, named parameters and clear route definitions.

English | Português The brightest, hippest, coolest router for Flutter. Features Simple route navigation Function handlers (map to a function instead

Luke Pighetti 3.5k Jan 4, 2023
QR.Flutter is a Flutter library for simple and fast QR code rendering via a Widget or custom painter.

QR.Flutter is a Flutter library for simple and fast QR code rendering via a Widget or custom painter. Need help? Please do not submit an issue for a "

Yakka 614 Jan 8, 2023