Skip to content

Commit

Permalink
[rfw] Enable subscribing to the root of a DynamicContent (#5848)
Browse files Browse the repository at this point in the history
fixes #141069
  • Loading branch information
Hixie authored Jan 30, 2024
1 parent c0cb0ee commit 5b48c44
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 14 deletions.
8 changes: 6 additions & 2 deletions packages/rfw/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
## 1.0.21

* Adds support for subscribing to the root of a `DynamicContent` object.

## 1.0.20

* Adds OverflowBox material widget.
* Updates ButtonBar material widget implementation.
* Adds `OverflowBox` material widget.
* Updates `ButtonBar` material widget implementation.

## 1.0.19

Expand Down
39 changes: 32 additions & 7 deletions packages/rfw/lib/src/flutter/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import 'package:flutter/foundation.dart' show objectRuntimeType;
import '../dart/model.dart';

/// Signature for the callback passed to [DynamicContent.subscribe].
///
/// Do not modify the provided value (e.g. if it is a map or list). Doing so
/// would leave the [DynamicContent] in an inconsistent state.
typedef SubscriptionCallback = void Function(Object value);

/// Returns a copy of a data structure if it consists of only [DynamicMap]s,
Expand Down Expand Up @@ -116,9 +119,12 @@ Object? deepClone(Object? template) {
/// [missing] as the new value. It is not an error to subscribe to missing data.
/// It _is_ an error to add [missing] values to the data model, however.
///
/// To subscribe to the root of the [DynamicContent], use an empty list as the
/// key when subscribing.
///
/// The [LocalWidgetBuilder]s passed to a [LocalWidgetLibrary] use a
/// [DataSource] as their interface into the [DynamicContent]. To ensure the
/// integrity of the update mechanism, that interface only allows access to
/// integrity of the update mechanism, _that_ interface only allows access to
/// leaves, not intermediate nodes (maps and lists).
///
/// It is an error to subscribe to the same key multiple times with the same
Expand All @@ -143,6 +149,13 @@ class DynamicContent {
/// key.
///
/// Existing keys that are not present in the given map are left unmodified.
///
/// If the root node has subscribers (see [subscribe]), they are called once
/// per key in `initialData`, not just a single time.
///
/// Collections (maps and lists) in `initialData` must not be mutated after
/// calling this method; doing so would leave the [DynamicContent] in an
/// inconsistent state.
void updateAll(DynamicMap initialData) {
for (final String key in initialData.keys) {
final Object value = initialData[key] ?? missing;
Expand All @@ -156,6 +169,10 @@ class DynamicContent {
///
/// The `value` must consist exclusively of [DynamicMap], [DynamicList], [int],
/// [double], [bool], and [String] objects.
///
/// Collections (maps and lists) in `value` must not be mutated after calling
/// this method; doing so would leave the [DynamicContent] in an inconsistent
/// state.
void update(String rootKey, Object value) {
_root.updateKey(rootKey, deepClone(value)!);
_scheduleCleanup();
Expand All @@ -167,7 +184,14 @@ class DynamicContent {
/// The value is always non-null; if the value is missing, the [missing]
/// object is used instead.
///
/// The empty key refers to the root of the [DynamicContent] object (i.e.
/// the map manipulated by [updateAll] and [update]).
///
/// Use [unsubscribe] when the subscription is no longer needed.
///
/// Do not modify the value returned by this method or passed to the given
/// `callback` (e.g. if it is a map or list). Changes made in this manner will
/// leave the [DynamicContent] in an inconsistent state.
Object subscribe(List<Object> key, SubscriptionCallback callback) {
return _root.subscribe(key, 0, callback);
}
Expand Down Expand Up @@ -329,12 +353,6 @@ class _DynamicNode {
_sendUpdates(value);
}

void _sendUpdates(Object value) {
for (final SubscriptionCallback callback in _callbacks) {
callback(value);
}
}

void updateKey(String rootKey, Object value) {
assert(_value is DynamicMap);
assert(_hasValidType(value));
Expand All @@ -345,6 +363,13 @@ class _DynamicNode {
if (_children.containsKey(rootKey)) {
_children[rootKey]!.update(value);
}
_sendUpdates(_value);
}

void _sendUpdates(Object value) {
for (final SubscriptionCallback callback in _callbacks) {
callback(value);
}
}

@override
Expand Down
2 changes: 1 addition & 1 deletion packages/rfw/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: rfw
description: "Remote Flutter widgets: a library for rendering declarative widget description files at runtime."
repository: https://github.com/flutter/packages/tree/main/packages/rfw
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+rfw%22
version: 1.0.20
version: 1.0.21

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
14 changes: 14 additions & 0 deletions packages/rfw/test/runtime_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1074,4 +1074,18 @@ void main() {
});
expect(tested, isTrue);
});

testWidgets('DynamicContent subscriptions', (WidgetTester tester) async {
final List<String> log = <String>[];
final DynamicContent data = DynamicContent(<String, Object?>{
'a': <Object>[0, 1],
'b': <Object>['q', 'r'],
});
data.subscribe(<Object>[], (Object value) { log.add('root: $value'); });
data.subscribe(<Object>['a', 0], (Object value) { log.add('leaf: $value'); });
data.update('a', <Object>[2, 3]);
expect(log, <String>['leaf: 2', 'root: {a: [2, 3], b: [q, r]}']);
data.update('c', 'test');
expect(log, <String>['leaf: 2', 'root: {a: [2, 3], b: [q, r]}', 'root: {a: [2, 3], b: [q, r], c: test}']);
});
}
9 changes: 5 additions & 4 deletions packages/rfw/test_coverage/bin/test_coverage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import 'package:meta/meta.dart';

// Please update these targets when you update this package.
// Please ensure that test coverage continues to be 100%.
const int targetLines = 3223;
// Don't forget to update the lastUpdate date too!
const int targetLines = 3273;
const String targetPercent = '100';
const String lastUpdate = '2023-06-29';
const String lastUpdate = '2024-01-30';

@immutable
/* final */ class LcovLine {
Expand Down Expand Up @@ -196,14 +197,14 @@ Future<void> main(List<String> arguments) async {
print(
'Total lines of covered code has increased, and coverage script is now out of date.\n'
'Coverage is now $coveredPercent%, $coveredLines/$totalLines lines, whereas previously there were only $targetLines lines.\n'
'Update the "\$targetLines" constant at the top of rfw/test_coverage/bin/test_coverage.dart (to $coveredLines).',
'Update the "targetLines" constant at the top of rfw/test_coverage/bin/test_coverage.dart (to $coveredLines).',
);
}
if (targetLines > totalLines) {
print(
'Total lines of code has reduced, and coverage script is now out of date.\n'
'Coverage is now $coveredPercent%, $coveredLines/$totalLines lines, but previously there were $targetLines lines.\n'
'Update the "\$targetLines" constant at the top of rfw/test_coverage/bin/test_coverage.dart (to $totalLines).',
'Update the "targetLines" constant at the top of rfw/test_coverage/bin/test_coverage.dart (to $totalLines).',
);
exit(1);
}
Expand Down

0 comments on commit 5b48c44

Please sign in to comment.