Last updated:
0 purchases
live cells
Live Cells is a reactive programming library for Dart, intended to be used with Flutter.
Specifically Live Cells provides a replacement (the cell) for ChangeNotifier and ValueNotifier
that is simpler to use and more flexible.
Features #
Cells offers the following benefits over ChangeNotifier / ValueNotifier:
Implementing a cell which is an expression of other cells, e.g. a + b,
can be done in a functional manner without manually adding and removing listeners.
Simpler resource management, no need to call dispose.
Integrated with a library of widgets which allow their properties to
be controlled and observed by cells. This allows for a style of
programming which fits in with the reactive paradigm of Flutter.
Effortless state restoration with minimal boilerplate.
This library also has the following advantages over other similar libraries:
Supports two-way data flow, whereas most other libraries, if not all, only support
one-way data flow.
Cells are designed to be unobtrusive and indistinguishable, as much
as is possible, from the values they hold.
Live Cells is unopinionated. You're not forced to change the way you
write your apps. Use as much of its functionality as you need.
Integrated with a library of widgets which allow for effortless data
binding between UI elements and cells.
Getting Started #
If you haven't used Live Cells before, please head to the full
documentation.
The remainder of this README shows brief examples that demonstrate the main features of this library.
Usage #
Defining Cells #
Constant cells can be defined either using .cell or ValueCell.value:
final a = 1.cell;
final b = 'hello'.cell;
final c = ValueCell.value(someValue);
copied to clipboard
Mutable cells are defined using MutableCell:
final a = MutableCell(0);
final b = MutableCell(1);
copied to clipboard
Computed Cells #
Basic computed cells can be defined directly as an expression of cells:
final sum = a + b;
copied to clipboard
More complex computed cells are defined using ValueCell.computed:
final computed = ValueCell.computed(() => sqrt(a() * a() + b() * b()));
copied to clipboard
The values of computed cells are recomputed whenever the values of their argument cells
change:
print(sum.value); // 1
a.value = 6;
print(sum.value); // 7
copied to clipboard
Observing Cells #
Cells can be observed using ValueCell.watch. The watch function is called whenever the values of
the cells it references change:
ValueCell.watch(() {
print('${a()} + ${b()} = ${sum()}');
});
a.value = 8; // Prints: 8 + 1 = 9
copied to clipboard
Batch Updates #
The values of multiple mutable cells can be set simultaneously using MutableCell.batch:
MutableCell.batch(() {
a.value = 1;
b.value = 3;
});
copied to clipboard
Cells in Widgets #
CellWidget.builder creates a widget that is rebuilt whenever the values of the cells referenced by
it change:
// Rebuilt when a, b, or sum change
CellWidget.builder(() => Text('${a()} + ${b()} = ${sum()}'));
copied to clipboard
Exception Handling #
Exceptions thrown during the computation of a cell's value are propagated to all points where the
value is referenced.
This allows exceptions to be handled using try catch:
final str = MutableCell('0');
final n = ValueCell.computed(() => int.parse(str()));
final isValid = ValueCell.computed(() {
try {
return n() > 0;
}
catch (e) {
return false;
}
});
print(isValid.value); // Prints false
str.value = '5';
print(isValid.value); // Prints true
str.value = 'not a number';
print(isValid.value); // Prints false
copied to clipboard
Or more succinctly using onError:
final str = MutableCell('0');
final n = ValueCell.computed(() => int.parse(str()));
final isValid = (n > 0.cell).onError(false.cell);
copied to clipboard
Previous Values #
The previous value of a cell can be accessed using .previous, which is itself a cell:
final a = MutableCell(0);
final prev = a.previous;
final sum = a + prev;
a.value = 1;
print(a.value); // Prints 1
print(prev.value); // Prints 0
print(sum.value); // Prints 1
a.value = 5;
print(a.value); // Prints 5
print(prev.value); // Prints 1
print(sum.value); // Prints 6
copied to clipboard
Binding Cells to Widget Properties #
This package also provides a collection of widgets, which allow their properties to be controlled
and observed by cells:
For example CellSwitch is this library's equivalent of Switch:
final state = MutableCell(false);
return Column(
children: [
// This binds the value of the switch to `state`
CellSwitch(
value: state
);
// This widget is rebuilt whenever the switch is toggled
CellWidget.builder(() => Text(state() ? 'On' : 'Off'));
]
);
copied to clipboard
Resetting the switch is as simple as setting the value of state:
state.value = false;
copied to clipboard
Two-Way Data Flow #
Two-way data flow allows for complex logic to be implemented entirely using concise declarative
code:
final n = MutableCell<num>(0);
// CellTextField is a Live Cells TextField
//
// This binds the content of the field to `n`
return CellTextField(
// Example of two-way data flow:
//
// When the value of `n` is set, `mutableString()` converts it to
// a string and forwards it to the field's content.
//
// When the content of the field changes, `mutableString()` converts
// the string content to a number and forwards it to `n`
content: n.mutableString();
)
copied to clipboard
Property Accessors for your own types: #
With live_cell_extension you can generate cell
accessors for your own classes:
@CellExtension(mutable: true)
class Person {
final String firstName;
final String lastName;
Person({
required this.firstName,
required this.lastName
});
}
copied to clipboard
You can now access firstName and lastName directly on cell's holding a Person:
final person = MutableCell(Person(...));
ValueCell.watch(() {
print('${person.firstName()} ${person.LastName()}');
});
// This triggers the watch function defined above:
person.firstName.value = 'John';
copied to clipboard
Asynchronous Cells #
Cells can hold and manipulate a Future like any other value:
ValueCell<Future<int>> cell1;
ValueCell<Future<int>> cell2;
...
final sum = ValueCell.computed(() async {
final (a, b) = await (cell1(), cell2()).wait;
return a + b;
});
copied to clipboard
Cells can await a Future held in another cell:
ValueCell<Future<int>> cell1;
ValueCell<Future<int>> cell2;
...
// The value of `sum` is only computed once the futures
// in both `cell1` and `cell2` have completed
final sum = ValueCell.computed(() {
final (a, b) = (cell1, cell2).wait();
return a + b;
});
copied to clipboard
Cells can also check if, and be notified when, a Future in another cell has completed:
ValueCell<Future<int>> cell1;
ValueCell<Future<int>> cell2;
...
// isLoading is true until both the Futures in cell1
// and cell2 have completed
final isLoading = (cell1, cell2).isCompleted.not();
copied to clipboard
ValueListenable (Integration with other tools) #
The .listenable property returns a ValueListenable that notifies its observers whenever the
value of the cell changes:
final count = MutableCell<int>(0);
final countListenable = count.listenable;
copied to clipboard
This allows cells to be used as a drop-in replacement for ValueNotifier, whenever a
ValueListenable is expected:
ValueListenableBuilder(
valueListenable: count.listenable,
...
)
copied to clipboard
Additional information #
If you discover any issues or have any feature requests, please open an issue on the package's Github
repository.
Please visit the full documentation for a full
introduction to the library's features. Also take a look at the example directory for more
complete examples of how to use this library.
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.