Skip to content

Commit bbec79c

Browse files
committed
Release v5.2.1: Improved coverage and code cleanup
Internal Changes: - Removed eager initialization from operator chains (now true lazy init) - Chains only subscribe to sources when first listener is added - Cleaned up redundant init() calls from constructors - Converted constructors to use super parameters Documentation: - Updated terminology from "hot subscription model" to "lazy initialization" - Updated README.md, best_practices.md, and operators/overview.md - Clearer explanation of chain lifecycle behavior Tests: - Improved test coverage from 83.7% to 97.6% (+13.9 points) - Added comprehensive lazy initialization tests - Added tests for timer cancellation, error handling, disposal - Fixed misleading test names - All 199 tests pass Code Quality: - Fixed all analyzer warnings - All code properly formatted
1 parent bc399cf commit bbec79c

8 files changed

Lines changed: 1038 additions & 67 deletions

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
## [5.2.1] - 2025-01-12
2+
3+
### Internal Changes
4+
5+
- **Removed eager initialization from operator chains**
6+
- Chains now use true lazy initialization - they only subscribe to sources when the first listener is added
7+
- Removed redundant `init()` calls from constructors (leftover from v2.0.2 workaround)
8+
- This cleanup was possible after v4.0.0 removed automatic unsubscribe propagation
9+
- No behavior changes - chains still maintain persistent subscriptions after initialization
10+
11+
### Documentation
12+
13+
- **Updated terminology throughout documentation**
14+
- Changed "hot subscription model" to "lazy initialization with persistent subscriptions"
15+
- Updated README.md, best_practices.md, and operators/overview.md
16+
- Clearer explanation of chain lifecycle behavior
17+
18+
### Tests
19+
20+
- **Improved test coverage from 83.7% to 97.6%** (+13.9 percentage points)
21+
- Added comprehensive lazy initialization tests
22+
- Added tests for timer cancellation in debounce
23+
- Added tests for error handling, disposal, and reentrant listener removal
24+
- Fixed misleading test names to accurately describe what they test
25+
- All 199 tests pass ✓
26+
127
## [5.2.0] - 2025-01-11
228

329
### New Feature

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Previously published as `functional_listener`. Now includes reactive collections
3232

3333
[Learn more about listen_it →](https://flutter-it.dev/documentation/listen_it/listen_it)
3434

35-
> ⚠️ **Important:** Operator chains use a "hot" subscription model. See the [best practices guide](https://flutter-it.dev/documentation/listen_it/best_practices) to avoid memory leaks when creating chains inline. TL;DR: Use watch_it (automatic caching!) or create chains outside build methods.
35+
> 💡 **Performance Tip:** Operator chains use lazy initialization - they only subscribe to their sources when you add a listener. Once initialized, chains stay subscribed for efficiency. For best practices with watch_it integration, see the [complete documentation](https://flutter-it.dev/documentation/listen_it/best_practices).
3636
3737
## Quick Start
3838

@@ -197,7 +197,7 @@ Transform and combine observables:
197197

198198
### ⚠️ Important: Chain Lifecycle & Memory Management
199199

200-
Operator chains (like `source.map(...).where(...)`) use a **"hot" subscription model** - they subscribe to their source immediately and stay subscribed even with zero listeners.
200+
Operator chains (like `source.map(...).where(...)`) use **lazy initialization** - they only subscribe to their source when the first listener is added, then stay subscribed for efficiency.
201201

202202
**This can cause memory leaks if chains are created inline in build methods!**
203203

example/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ packages:
113113
path: ".."
114114
relative: true
115115
source: path
116-
version: "5.2.0"
116+
version: "5.2.1"
117117
matcher:
118118
dependency: transitive
119119
description:

lib/src/functional_value_notifiers.dart

Lines changed: 21 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,10 @@ class SelectValueNotifier<TIn, TOut>
4444
final TOut Function(TIn) selector;
4545

4646
SelectValueNotifier(
47-
TOut initialValue,
48-
ValueListenable<TIn> previousInChain,
47+
super.initialValue,
48+
super.previousInChain,
4949
this.selector,
50-
) : super(initialValue, previousInChain) {
51-
init(previousInChain);
52-
}
50+
);
5351

5452
@override
5553
void init(ValueListenable<TIn> previousInChain) {
@@ -67,12 +65,10 @@ class MapValueNotifier<TIn, TOut> extends FunctionalValueNotifier<TIn, TOut> {
6765
final TOut Function(TIn) transformation;
6866

6967
MapValueNotifier(
70-
TOut initialValue,
71-
ValueListenable<TIn> previousInChain,
68+
super.initialValue,
69+
super.previousInChain,
7270
this.transformation,
73-
) : super(initialValue, previousInChain) {
74-
init(previousInChain);
75-
}
71+
);
7672

7773
@override
7874
void init(ValueListenable<TIn> previousInChain) {
@@ -87,12 +83,10 @@ class WhereValueNotifier<T> extends FunctionalValueNotifier<T, T> {
8783
final bool Function(T) selector;
8884

8985
WhereValueNotifier(
90-
T initialValue,
91-
ValueListenable<T> previousInChain,
86+
super.initialValue,
87+
super.previousInChain,
9288
this.selector,
93-
) : super(initialValue, previousInChain) {
94-
init(previousInChain);
95-
}
89+
);
9690

9791
@override
9892
void init(ValueListenable<T> previousInChain) {
@@ -110,12 +104,10 @@ class DebouncedValueNotifier<T> extends FunctionalValueNotifier<T, T> {
110104
final Duration debounceDuration;
111105

112106
DebouncedValueNotifier(
113-
T initialValue,
114-
ValueListenable<T> previousInChain,
107+
super.initialValue,
108+
super.previousInChain,
115109
this.debounceDuration,
116-
) : super(initialValue, previousInChain) {
117-
init(previousInChain);
118-
}
110+
);
119111

120112
@override
121113
void init(ValueListenable<T> previousInChain) {
@@ -130,11 +122,9 @@ class DebouncedValueNotifier<T> extends FunctionalValueNotifier<T, T> {
130122

131123
class AsyncValueNotifier<T> extends FunctionalValueNotifier<T, T> {
132124
AsyncValueNotifier(
133-
T initialValue,
134-
ValueListenable<T> previousInChain,
135-
) : super(initialValue, previousInChain) {
136-
init(previousInChain);
137-
}
125+
super.initialValue,
126+
super.previousInChain,
127+
);
138128

139129
@override
140130
void init(ValueListenable<T> previousInChain) {
@@ -159,11 +149,7 @@ class CombiningValueNotifier<TIn1, TIn2, TOut> extends ValueNotifier<TOut> {
159149
this.previousInChain1,
160150
this.previousInChain2,
161151
this.combiner,
162-
) {
163-
internalHandler =
164-
() => value = combiner(previousInChain1.value, previousInChain2.value);
165-
init(previousInChain1, previousInChain2);
166-
}
152+
);
167153

168154
void init(
169155
ValueListenable<TIn1> previousInChain1,
@@ -215,9 +201,7 @@ class CombiningValueNotifier3<TIn1, TIn2, TIn3, TOut>
215201
this.previousInChain2,
216202
this.previousInChain3,
217203
this.combiner,
218-
) {
219-
init(previousInChain1, previousInChain2, previousInChain3);
220-
}
204+
);
221205

222206
void init(
223207
ValueListenable<TIn1> previousInChain1,
@@ -278,14 +262,7 @@ class CombiningValueNotifier4<TIn1, TIn2, TIn3, TIn4, TOut>
278262
this.previousInChain3,
279263
this.previousInChain4,
280264
this.combiner,
281-
) {
282-
init(
283-
previousInChain1,
284-
previousInChain2,
285-
previousInChain3,
286-
previousInChain4,
287-
);
288-
}
265+
);
289266

290267
void init(
291268
ValueListenable<TIn1> previousInChain1,
@@ -358,15 +335,7 @@ class CombiningValueNotifier5<TIn1, TIn2, TIn3, TIn4, TIn5, TOut>
358335
this.previousInChain4,
359336
this.previousInChain5,
360337
this.combiner,
361-
) {
362-
init(
363-
previousInChain1,
364-
previousInChain2,
365-
previousInChain3,
366-
previousInChain4,
367-
previousInChain5,
368-
);
369-
}
338+
);
370339

371340
void init(
372341
ValueListenable<TIn1> previousInChain1,
@@ -441,16 +410,7 @@ class CombiningValueNotifier6<TIn1, TIn2, TIn3, TIn4, TIn5, TIn6, TOut>
441410
this.previousInChain5,
442411
this.previousInChain6,
443412
this.combiner,
444-
) {
445-
init(
446-
previousInChain1,
447-
previousInChain2,
448-
previousInChain3,
449-
previousInChain4,
450-
previousInChain5,
451-
previousInChain6,
452-
);
453-
}
413+
);
454414

455415
void init(
456416
ValueListenable<TIn1> previousInChain1,
@@ -514,9 +474,7 @@ class MergingValueNotifiers<T> extends FunctionalValueNotifier<T, T> {
514474
ValueListenable<T> previousInChain,
515475
this.mergeWith,
516476
T initialValue,
517-
) : super(initialValue, previousInChain) {
518-
init(previousInChain);
519-
}
477+
) : super(initialValue, previousInChain);
520478

521479
@override
522480
void init(ValueListenable<T> previousInChain) {

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: listen_it
22
description: Reactive primitives for Flutter - observable collections and powerful operators. Work with ValueNotifiers like Streams. Includes ListNotifier, MapNotifier, SetNotifier. Previously published as functional_listener.
3-
version: 5.2.0
3+
version: 5.2.1
44
maintainer: Thomas Burkhart (@escamoteur)
55
homepage: https://github.com/flutter-it/listen_it
66
repository: https://github.com/flutter-it/listen_it

test/chain_memory_test.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ void main() {
7272
final sourceWeakRef = WeakReference(service.source);
7373
final chainWeakRef = WeakReference(service.chain);
7474

75+
// Add a listener to trigger lazy initialization
76+
service.chain.addListener(() {});
77+
7578
// Verify it works
7679
service.source.value = 5;
7780
expect(service.chain.value, 10);

0 commit comments

Comments
 (0)