nucleus

Last updated:

0 purchases

nucleus Image
nucleus Images
Add to Cart

Description:

nucleus

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

Table of contents #

Installation
A quick tour

stateAtom - A simple way of adding state to your app
BuildContext methods - Use these for interacting with your atoms
atom - Create derived state, or manage your app dependencies
futureAtom - Easy way to add data from an API into your app
streamAtom - Easy way to add stream data from an API into your app
atomWithParent - Create your own custom atom types!
atomFamily - For grouping atom's with a common parameter. Like fetching something from an API by its ID.
stateAtomWithStorage - stateAtom, but with storage. Persisted between app restarts!
atomWithStorage - For more advanced persistence
writableAtom - Unlock the full power of atom's



Installation #
If you are using flutter, then all you need is the flutter_nucleus package:
flutter pub add flutter_nucleus
copied to clipboard
If you are working only with dart, then install the nucleus package:
dart pub add nucleus
copied to clipboard
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);
copied to clipboard
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();
copied to clipboard
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());
});
}
}
copied to clipboard
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());
}
}
copied to clipboard
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"),
);
}
}
copied to clipboard
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.
copied to clipboard


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);
copied to clipboard


registry - access the AtomRegistry directly
final registry = context.registry(listen: true);
copied to clipboard


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());
copied to clipboard

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
));
copied to clipboard
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);
copied to clipboard
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());
copied to clipboard
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),
);
});
copied to clipboard
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;
});
copied to clipboard

streamAtom #
Similar to futureAtom, streamAtom turns a Stream into a FutureValue containing the latest value.
final latestUser = streamAtom((get) => get(userRepository).latestUserStream());
copied to clipboard
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,
);
});
copied to clipboard
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;
});
copied to clipboard
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;
copied to clipboard
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));
});
copied to clipboard
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),
);
});
}
copied to clipboard
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(),
));
}
copied to clipboard
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,
);
copied to clipboard
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;
});
copied to clipboard
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;
copied to clipboard
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.

License:

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Files In This Product:

Customer Reviews

There are no reviews.