Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
## 3.0.0-beta-5

**BREAKING CHANGES**

- To eliminate excessive _O(N)_ memory allocations during bulk collection operations (`clear`, `addAll`, `insertAll`, `addEntries`), `CurrentStateChanged` events no longer contain deep clones of the collection by default. The `previousValue` payload will now be `null`. If you need the previous state (e.g., for your own fine-grained Undo feature for example), you must now explicitly opt-in by passing `capturePrevious: true` to these methods.
- I recognize this could be a real pain for those of you who were relying on the old behavior, but this is a massive improvement for performance and memory efficiency for 99% of use cases, and the opt-in approach doesn't leave you hanging without a path forward if you do need the previous state.
- `CurrentListProperty.where()` and `CurrentListProperty.reversed` now return a lazy `Iterable<T>` instead of eagerly allocating a new `List<T>`. This avoids unnecessary memory allocations and aligns with standard Dart iterable semantics. If you strictly need a `List`, simply append `.toList()` to the call.
- I also recognize this could be a real pain. Same deal as above, this can result in significant performance and memory improvements for large lists. Plus as mentioned above, it's how standard Dart collections work so it should be more intuitive and less surprising in the long run.

### Performance Improvements

- Fixed a double evaluation performance issue in `CurrentListProperty.firstWhereOrNull`.
- Fixed excessively sending events in `CurrentMapProperty.updateAll` and `CurrentMapProperty.removeWhere`. These methods now emit a single bulk change event instead of spamming the state stream with one event per map entry.

## 3.0.0-beta-4

- Update README to reflect the VS Code extenion name change and to highlight the new features of the extension such as Quick Fix actions and Command Palette commands.
- Update README to reflect the VS Code extension name change and to highlight the new features of the extension such as Quick Fix actions and Command Palette commands.

## 3.0.0-beta-3

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ In your Flutter project, add the dependency to your `pubspec.yaml`.

```yaml
dependencies:
current: ^3.0.0-beta-4
current: ^3.0.0-beta-5
```

**Tip:** It is highly recommended to install the [Flutter Current](https://marketplace.visualstudio.com/items?itemName=ThirdVersionTechnologyLtd.current-flutter-snippets) extension in Visual Studio Code. It provides super helpful code snippets, commands, and Quick Fix actions to speed up your development workflow when using Flutter Current.
Expand Down
66 changes: 42 additions & 24 deletions lib/src/current_list_property.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class CurrentListProperty<T> extends CurrentProperty<List<T>> {
/// ```
bool get isNotEmpty => _value.isNotEmpty;

/// Returns a new [List] with all elements that satisfy the
/// Returns a new lazy [Iterable] with all elements that satisfy the
/// predicate [test].
///
/// Example:
Expand All @@ -105,8 +105,8 @@ class CurrentListProperty<T> extends CurrentProperty<List<T>> {
/// result = numbers.where((x) => x > 5); // (6, 7)
/// result = numbers.where((x) => x.isEven); // (2, 6)
/// ```
List<T> where(bool Function(T element) test) {
return _value.where(test).toList();
Iterable<T> where(bool Function(T element) test) {
return _value.where(test);
}

/// Returns the first element that satisfies the given predicate [test].
Expand Down Expand Up @@ -144,13 +144,12 @@ class CurrentListProperty<T> extends CurrentProperty<List<T>> {
///
/// If no element satisfies [test], null is returned
T? firstWhereOrNull(bool Function(T element) test) {
final results = value.where(test);

if (results.isEmpty) {
return null;
} else {
return results.first;
for (final element in _value) {
if (test(element)) {
return element;
}
}
return null;
}

/// Adds [value] to the end of this list,
Expand Down Expand Up @@ -181,20 +180,24 @@ class CurrentListProperty<T> extends CurrentProperty<List<T>> {
///
/// Extends the length of the list by the number of objects in [values].
/// The list must be growable.
///
/// Set [capturePrevious] to true if you need the [CurrentStateChanged] event to
/// contain a snapshot of the list prior to the items being added.
///
/// ```dart
/// final numbers = CurrentListProperty<int>([1, 2, 3]);
/// numbers.addAll([4, 5, 6]);
/// print(numbers); // [1, 2, 3, 4, 5, 6]
/// ```
void addAll(Iterable<T> values, {bool notifyChanges = true}) {
final addedValues = List<T>.from(values);
_value.addAll(addedValues);
void addAll(Iterable<T> values, {bool notifyChanges = true, bool capturePrevious = false}) {
final previousValue = capturePrevious ? List<T>.from(_value) : null;
_value.addAll(values);

if (notifyChanges) {
viewModel.notifyChanges([
CurrentStateChanged.addedAllToList(
addedValues,
values,
previousValue: previousValue,
propertyName: propertyName,
sourceHashCode: sourceHashCode,
)
Expand Down Expand Up @@ -236,21 +239,26 @@ class CurrentListProperty<T> extends CurrentProperty<List<T>> {
/// and shifts all later objects towards the end of the list.
/// The list must be growable.
/// The [index] must be a valid index in the list or [length].
///
/// Set [capturePrevious] to true if you need the [CurrentStateChanged] event to
/// contain a snapshot of the list prior to the items being inserted.
///
/// ```dart
/// final numbers = CurrentListProperty<int>([1, 2, 3, 4]);
/// const index = 2;
/// numbers.insertAll(index, [10, 11, 12]);
/// print(numbers); // [1, 2, 10, 11, 12, 3, 4]
/// ```
void insertAll(int index, Iterable<T> values, {bool notifyChanges = true}) {
final insertedValues = List<T>.from(values);
_value.insertAll(index, insertedValues);
void insertAll(int index, Iterable<T> values, {bool notifyChanges = true, bool capturePrevious = false}) {
final previousValue = capturePrevious ? List<T>.from(_value) : null;
_value.insertAll(index, values);

if (notifyChanges) {
viewModel.notifyChanges([
CurrentStateChanged.insertAllIntoList(
index,
insertedValues,
values,
previousValue: previousValue,
propertyName: propertyName,
sourceHashCode: sourceHashCode,
)
Expand All @@ -262,21 +270,26 @@ class CurrentListProperty<T> extends CurrentProperty<List<T>> {
/// This increases the length of the list by the length of [values]
///
/// The list must be growable.
///
/// Set [capturePrevious] to true if you need the [CurrentStateChanged] event to
/// contain a snapshot of the list prior to the items being inserted.
///
/// ```dart
/// final numbers = CurrentListProperty<int>([1, 2, 3, 4]);
/// numbers.insertAllAtEnd([10, 11, 12]);
/// print(numbers); // [1, 2, 3, 4, 10, 11, 12]
/// ```
void insertAllAtEnd(Iterable<T> values, {bool notifyChanges = true}) {
void insertAllAtEnd(Iterable<T> values, {bool notifyChanges = true, bool capturePrevious = false}) {
final insertIndex = _value.length;
final insertedValues = List<T>.from(values);
_value.insertAll(insertIndex, insertedValues);
final previousValue = capturePrevious ? List<T>.from(_value) : null;
_value.insertAll(insertIndex, values);

if (notifyChanges) {
viewModel.notifyChanges([
CurrentStateChanged.insertAllIntoList(
insertIndex,
insertedValues,
values,
previousValue: previousValue,
propertyName: propertyName,
sourceHashCode: sourceHashCode,
)
Expand Down Expand Up @@ -376,16 +389,21 @@ class CurrentListProperty<T> extends CurrentProperty<List<T>> {
///
/// The list must be growable.
///
/// Note: To avoid O(N) memory allocations, the generated [CurrentStateChanged] event
/// does not contain a snapshot of the list's items prior to being cleared. If you
/// need to preserve the previous items, you must explicitly pass `capturePrevious: true`
/// or cache them yourself.
///
/// ```dart
/// final numbers = CurrentListProperty<int>([1, 2, 3]);
/// numbers.clear();
/// print(numbers.length); // 0
/// print(numbers); // []
/// ```
void clear({bool notifyChanges = true}) {
final previousItems = List<T>.from(_value);
void clear({bool notifyChanges = true, bool capturePrevious = false}) {
final previousItems = capturePrevious ? List<T>.from(_value) : null;
final stateChangedEvent = CurrentStateChanged.clearedList(
previousItems,
previousItems: previousItems,
propertyName: propertyName,
sourceHashCode: sourceHashCode,
);
Expand Down
87 changes: 45 additions & 42 deletions lib/src/current_map_property.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,18 @@ class CurrentMapProperty<K, V> extends CurrentProperty<Map<K, V>> {
/// Adds all key/value pairs of [other] to this map.
///
/// If a key of [other] is already in this map, its value is overwritten.
void addAll(Map<K, V> other, {bool notifyChanges = true}) {
final addedMap = Map<K, V>.from(other);
_value.addAll(addedMap);
///
/// Set [capturePrevious] to true if you need the [CurrentStateChanged] event to
/// contain a snapshot of the map prior to the items being added.
void addAll(Map<K, V> other, {bool notifyChanges = true, bool capturePrevious = false}) {
final previousValue = capturePrevious ? Map<K, V>.from(_value) : null;
_value.addAll(other);

if (notifyChanges) {
viewModel.notifyChanges([
CurrentStateChanged.addedMapToMap(
addedMap,
other,
previousValue: previousValue,
propertyName: propertyName,
sourceHashCode: sourceHashCode,
)
Expand Down Expand Up @@ -118,6 +122,10 @@ class CurrentMapProperty<K, V> extends CurrentProperty<Map<K, V>> {
///
/// The operation is equivalent to doing `this[entry.key] = entry.value`
/// for each [MapEntry] of the iterable.
///
/// Set [capturePrevious] to true if you need the [CurrentStateChanged] event to
/// contain a snapshot of the map prior to the items being added.
///
/// ```dart
/// final planets = CurrentMapProperty<int, String>{1: 'Mercury', 2: 'Venus',
/// 3: 'Earth', 4: 'Mars'};
Expand All @@ -130,13 +138,14 @@ class CurrentMapProperty<K, V> extends CurrentProperty<Map<K, V>> {
/// // 7: Uranus, 8: Neptune}
/// ```
void addEntries(Iterable<MapEntry<K, V>> entries,
{bool notifyChanges = true}) {
final addedEntries = List<MapEntry<K, V>>.from(entries);
_value.addEntries(addedEntries);
{bool notifyChanges = true, bool capturePrevious = false}) {
final previousValue = capturePrevious ? _value.entries.toList() : null;
_value.addEntries(entries);

if (notifyChanges) {
viewModel.notifyChanges([
CurrentStateChanged.addedEntriesToMap(addedEntries,
CurrentStateChanged.addedEntriesToMap(entries,
previousValue: previousValue,
propertyName: propertyName, sourceHashCode: sourceHashCode)
]);
}
Expand Down Expand Up @@ -212,23 +221,18 @@ class CurrentMapProperty<K, V> extends CurrentProperty<Map<K, V>> {
/// ```
void updateAll(V Function(K key, V value) update,
{bool notifyChanges = true}) {
final stateChangedEvents = <CurrentStateChanged<V>>[];

_value.updateAll((key, value) {
final previousValue = value;
final updatedValue = update(key, value);
stateChangedEvents.add(CurrentStateChanged.updateMapEntry(
key,
previousValue,
updatedValue,
propertyName: propertyName,
sourceHashCode: sourceHashCode,
));
return updatedValue;
});
_value.updateAll(update);

if (notifyChanges) {
viewModel.notifyChanges(stateChangedEvents);
viewModel.notifyChanges([
CurrentStateChanged(
_value,
null,
propertyName: propertyName,
description: 'Map updated all entries',
sourceHashCode: sourceHashCode,
)
]);
}
}

Expand Down Expand Up @@ -269,40 +273,39 @@ class CurrentMapProperty<K, V> extends CurrentProperty<Map<K, V>> {
/// ```
void removeWhere(bool Function(K key, V value) test,
{bool notifyChanges = true}) {
final stateChangedEvents = <CurrentStateChanged<V>>[];

_value.removeWhere((key, value) {
final shouldRemove = test(key, value);
_value.removeWhere(test);

if (shouldRemove) {
stateChangedEvents.add(CurrentStateChanged.removedFromMap(
key,
value,
if (notifyChanges) {
viewModel.notifyChanges([
CurrentStateChanged(
_value,
null,
propertyName: propertyName,
description: 'Removed items from map based on condition',
sourceHashCode: sourceHashCode,
));
}

return shouldRemove;
});

if (notifyChanges) {
viewModel.notifyChanges(stateChangedEvents);
)
]);
}
}

/// Removes all entries from the map.
///
/// After this, the map is empty.
///
/// Note: To avoid O(N) memory allocations, the generated [CurrentStateChanged] event
/// does not contain a snapshot of the map's items prior to being cleared. If you
/// need to preserve the previous items, you must explicitly pass `capturePrevious: true`
/// or cache them yourself.
///
/// ```dart
/// final planets = CurrentMapProperty<int, String>{1: 'Mercury', 2: 'Venus', 3: 'Earth'};
/// planets.clear(); // {}
/// ```
void clear({bool notifyChanges = true}) {
final previousItems = Map<K, V>.from(_value);
void clear({bool notifyChanges = true, bool capturePrevious = false}) {
final previousValue = capturePrevious ? Map<K, V>.from(_value) : null;
final stateChangedEvent = CurrentStateChanged(
<K, V>{},
previousItems,
previousValue,
propertyName: propertyName,
sourceHashCode: sourceHashCode,
);
Expand Down
28 changes: 16 additions & 12 deletions lib/src/current_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,8 @@ class CurrentStateChanged<T> {
///A factory method which creates a single [CurrentStateChanged] object with a description
///describing all the values that were added to the list
static CurrentStateChanged addedAllToList<V>(Iterable<V> newValues,
{String? propertyName, int? sourceHashCode}) =>
CurrentStateChanged(newValues, null,
{Iterable<V>? previousValue, String? propertyName, int? sourceHashCode}) =>
CurrentStateChanged(newValues, previousValue,
propertyName: propertyName,
description: 'Added All To List: $newValues',
sourceHashCode: sourceHashCode);
Expand All @@ -522,8 +522,8 @@ class CurrentStateChanged<T> {
///A factory method which creates a single [CurrentStateChanged] object with a description
///describing all the values that were inserted into the list at the specified index
static CurrentStateChanged insertAllIntoList<V>(int index, Iterable<V> values,
{String? propertyName, int? sourceHashCode}) =>
CurrentStateChanged(values, null,
{Iterable<V>? previousValue, String? propertyName, int? sourceHashCode}) =>
CurrentStateChanged(values, previousValue,
propertyName: propertyName,
description: 'Inserted All $values into List as index $index',
sourceHashCode: sourceHashCode);
Expand All @@ -538,19 +538,23 @@ class CurrentStateChanged<T> {
sourceHashCode: sourceHashCode);

///A factory method which creates a single [CurrentStateChanged] object with a description
///stating that the entire list was cleared
static CurrentStateChanged<Iterable<V>> clearedList<V>(Iterable<V> iterable,
{String? propertyName, int? sourceHashCode}) =>
CurrentStateChanged(<V>[], iterable,
///stating that the entire list was cleared.
///
///Note: To avoid O(N) memory allocations, this event does not contain a snapshot
///of the list's items prior to being cleared. If you need the previous value, you
///must explicitly request it using `capturePrevious: true` or cache it yourself.
static CurrentStateChanged<Iterable<V>> clearedList<V>(
{Iterable<V>? previousItems, String? propertyName, int? sourceHashCode}) =>
CurrentStateChanged(<V>[], previousItems,
propertyName: propertyName,
description: 'Iterable Cleared',
sourceHashCode: sourceHashCode);

///A factory method which creates a single [CurrentStateChanged] object with a description
///describing what new map values were added to another map
static CurrentStateChanged addedMapToMap<K, V>(Map<K, V> addedMap,
{String? propertyName, int? sourceHashCode}) {
return CurrentStateChanged(addedMap, null,
{Map<K, V>? previousValue, String? propertyName, int? sourceHashCode}) {
return CurrentStateChanged(addedMap, previousValue,
propertyName: propertyName,
description: 'Added Map To Map: $addedMap',
sourceHashCode: sourceHashCode);
Expand All @@ -571,9 +575,9 @@ class CurrentStateChanged<T> {
///describing what [MapEntry] objects were added to the map
static CurrentStateChanged addedEntriesToMap<K, V>(
Iterable<MapEntry<K, V>> entries,
{String? propertyName,
{Iterable<MapEntry<K, V>>? previousValue, String? propertyName,
int? sourceHashCode}) {
return CurrentStateChanged(entries, null,
return CurrentStateChanged(entries, previousValue,
propertyName: propertyName,
description: 'Added Entries To Map: $entries',
sourceHashCode: sourceHashCode);
Expand Down
Loading
Loading