Skip to content

Commit

Permalink
Merge pull request #1 from roughike/bugfix-first-listener-privileges
Browse files Browse the repository at this point in the history
Fix a bug where simultaneously listening to a reused Preference would not propagate changes to other listeners
  • Loading branch information
roughike authored May 25, 2019
2 parents aa2457e + edec0d4 commit 610d919
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 36 deletions.
34 changes: 15 additions & 19 deletions lib/src/preference/preference.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,6 @@ class _EmitValueChanges<T> extends StreamTransformerBase<String, T> {
final PreferenceAdapter<T> valueAdapter;
final SharedPreferences preferences;

T _lastValue;

void _addIfChanged(StreamController<T> controller, T value) {
// If the value changed from the last one, emit
// it and update "lastValue" with the newest one.
if (value != _lastValue) {
controller.add(value);
_lastValue = value;
}
}

T _getValueFromPersistentStorage() {
// Return the latest value from preferences,
// If null, returns the default value.
Expand All @@ -139,24 +128,31 @@ class _EmitValueChanges<T> extends StreamTransformerBase<String, T> {
onListen: () {
// When the stream is listened to, start with the current persisted
// value.
_addIfChanged(controller, _getValueFromPersistentStorage());
final value = _getValueFromPersistentStorage();
controller.add(value);

// Cache the last value. Caching is specific for each listener, so the
// cached value exists inside the onListen() callback for a reason.
T lastValue = value;

// Whenever a key has been updated, fetch the current persisted value
// and emit it.
subscription = input
.transform(_EmitOnlyMatchingKeys(key))
.map((_) => _getValueFromPersistentStorage())
.listen(
(value) => _addIfChanged(controller, value),
onDone: controller.close,
);
(value) {
if (value != lastValue) {
controller.add(value);
lastValue = value;
}
},
onDone: () => controller.close(),
);
},
onPause: ([resumeSignal]) => subscription.pause(resumeSignal),
onResume: () => subscription.resume(),
onCancel: () {
_lastValue = null;
return subscription.cancel();
},
onCancel: () => subscription.cancel(),
);

return controller.stream.listen(null);
Expand Down
65 changes: 48 additions & 17 deletions test/preference/preference_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ void main() {
);
});

Future<void> _updateValue(String newValue) async {
when(preferences.getString('key')).thenReturn(newValue);

// The value passed to setValue does not matter in tests - it just merely
// tells the preference that something just changed.
await preference.setValue(null);
}

test('calling setValue() calls the correct key and emits key updates', () {
preference.setValue('value1');
preference.setValue('value2');
Expand Down Expand Up @@ -75,34 +83,57 @@ void main() {
expect(preference, emits('3'));
});

test('does not emit same value more than once in a row', () async {
test('does not emit same value more than once in a row for one listener',
() async {
int updateCount = 0;
preference.listen((_) => updateCount++);

when(preferences.getString('key')).thenReturn('new value');
await preference.setValue(null);

when(preferences.getString('key')).thenReturn('new value');
await preference.setValue(null);

when(preferences.getString('key')).thenReturn('new value');
await preference.setValue(null);
await _updateValue('new value');
await _updateValue('new value');
await _updateValue('new value');

// Changed from "default value" to "new value"
expect(updateCount, 2);

when(preferences.getString('key')).thenReturn('another value 1');
await preference.setValue(null);

when(preferences.getString('key')).thenReturn('another value 2');
await preference.setValue(null);

when(preferences.getString('key')).thenReturn('another value 3');
await preference.setValue(null);
await _updateValue('another value 1');
await _updateValue('another value 2');
await _updateValue('another value 3');

// Changed from "new value" to "another value" 3 times
expect(updateCount, 5);
});

test('emits each value change to all listeners', () async {
String value1;
String value2;
String value3;

preference.listen((value) => value1 = value);
preference.listen((value) => value2 = value);
preference.listen((value) => value3 = value);

await _updateValue(null);

expect(value1, 'default value');
expect(value2, 'default value');
expect(value3, 'default value');

// The value passed to setValue does not matter in tests - it just merely
// triggers the preference that something just changed.
await _updateValue('first change');

expect(value1, 'first change');
expect(value2, 'first change');
expect(value3, 'first change');

// The value passed to setValue does not matter in tests - it just merely
// triggers the preference that something just changed.
await _updateValue('second change');

expect(value1, 'second change');
expect(value2, 'second change');
expect(value3, 'second change');
});
});
}

Expand Down

0 comments on commit 610d919

Please sign in to comment.