[rfw] Add some more documentation for RFW (#4349)

This commit is contained in:
Ian Hickson
2023-07-12 00:12:01 -07:00
committed by GitHub
parent cbec816300
commit aaebc6ee03
10 changed files with 1178 additions and 123 deletions

View File

@ -1,6 +1,11 @@
## 1.0.11
* Adds more documentation in the README.md file!
* Adds automated verification of the sample code in the README.
## 1.0.10
* Fixes stale ignore: prefer_const_constructors.
* Fixes stale ignore: `prefer_const_constructors`.
* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
* Changes package internals to avoid explicit `as Uint8List` downcast.

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,10 @@ void main() {
runApp(const Example());
}
// The "#docregion" comment helps us keep this code in sync with the
// excerpt in the rfw package's README.md file.
//
// #docregion Example
class Example extends StatefulWidget {
const Example({super.key});
@ -54,8 +58,11 @@ class _ExampleState extends State<Example> {
@override
void initState() {
super.initState();
// Local widget library:
_runtime.update(coreName, createCoreWidgets());
// Remote widget library:
_runtime.update(mainName, _remoteWidgets);
// Configuration data:
_data.update('greet', <String, Object>{'name': 'World'});
}
@ -73,3 +80,4 @@ class _ExampleState extends State<Example> {
);
}
}
// #enddocregion Example

View File

@ -16,6 +16,10 @@ void main() {
runApp(const Example());
}
// The "#docregion" comment helps us keep this code in sync with the
// excerpt in the rfw package's README.md file.
//
// #docregion Example
class Example extends StatefulWidget {
const Example({super.key});
@ -35,6 +39,9 @@ class _ExampleState extends State<Example> {
@override
void reassemble() {
// This function causes the Runtime to be updated any time the app is
// hot reloaded, so that changes to _createLocalWidgets can be seen
// during development. This function has no effect in production.
super.reassemble();
_update();
}
@ -87,3 +94,4 @@ class _ExampleState extends State<Example> {
);
}
}
// #enddocregion Example

View File

@ -25,7 +25,12 @@ widget CalculatorPad = Column(
children: [
Row(
children: [
// The "#docregion" pragma here allows us to inline this as an example
// in the "rfw" package's README.md file. It is unrelated to what this
// example is otherwise trying to show and can be disregarded.
// #docregion button7
CalculatorButton(label: "7", onPressed: event "digit" { arguments: [7] }),
// #enddocregion button7
CalculatorButton(label: "8", onPressed: event "digit" { arguments: [8] }),
CalculatorButton(label: "9", onPressed: event "digit" { arguments: [9] }),
SizedBox(width: 116.0, height: 116.0),
@ -58,6 +63,7 @@ widget CalculatorPad = Column(
],
);
// #docregion CalculatorButton
widget CalculatorButton = Padding(
padding: [8.0],
child: SizedBox(
@ -69,7 +75,9 @@ widget CalculatorButton = Padding(
),
),
);
// #enddocregion CalculatorButton
// #docregion Button
widget Button { down: false } = GestureDetector(
onTap: args.onPressed,
onTapDown: set state.down = true,
@ -110,6 +118,7 @@ widget Button { down: false } = GestureDetector(
),
),
);
// #enddocregion Button
widget Display = Container(
height: 80.0,

View File

@ -533,7 +533,7 @@ DynamicMap parseDataFile(String file) {
/// event "..." { }
/// ```
///
/// Tthe string is the name of the event, and the arguments map is the data to
/// The string is the name of the event, and the arguments map is the data to
/// send with the event.
///
/// For example, the event handler in the following sequence sends the event

View File

@ -537,6 +537,8 @@ Map<String, LocalWidgetBuilder> get _coreWidgetsDefinitions => <String, LocalWid
);
},
// The "#docregion" pragma below makes this accessible from the README.md file.
// #docregion Row
'Row': (BuildContext context, DataSource source) {
return Row(
mainAxisAlignment: ArgumentDecoders.enumValue<MainAxisAlignment>(MainAxisAlignment.values, source, ['mainAxisAlignment']) ?? MainAxisAlignment.start,
@ -548,6 +550,7 @@ Map<String, LocalWidgetBuilder> get _coreWidgetsDefinitions => <String, LocalWid
children: source.childList(['children']),
);
},
// #enddocregion Row
'SafeArea': (BuildContext context, DataSource source) {
return SafeArea(

View File

@ -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.10
version: 1.0.11
environment:
sdk: ">=3.0.0 <4.0.0"

View File

@ -0,0 +1,308 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is hand-formatted.
// This file contains and briefly tests the snippets used in the README.md file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rfw/formats.dart';
import 'package:rfw/rfw.dart';
const Map<String, String> rawRemoteWidgetSnippets = <String, String>{
'root': '''
// #docregion root
import local;
widget root = GreenBox(
child: Hello(name: "World"),
);
// #enddocregion root
''',
'fruit': '''
import local;
// #docregion fruit
widget fruit = Foo(
bar: { quux: [ 'apple', 'banana', 'cherry' ] },
);
// #enddocregion fruit
''',
'example1': '''
import local;
// #docregion example1
widget example1 = GreenBox(
child: Foo(
bar: 'Jean',
),
);
// #enddocregion example1
''',
'example2': '''
import local;
// #docregion example2
widget example2 = GreenBox(
child: Foo(
bar: { name: 'Jean' },
),
);
// #enddocregion example2
''',
'example3': '''
import local;
// #docregion example3
widget example3 = GreenBox(
child: Foo(
text: ['apple', 'banana']
),
);
// #enddocregion example3
''',
'tap': '''
import local;
import core;
widget tap = GestureDetector(
onTap: event 'test' { },
child: SizedBox(),
);
''',
'tapDown': '''
import local;
import core;
widget tapDown = GestureDetector(
onTapDown: event 'test' { },
child: SizedBox(),
);
''',
'Shop': '''
// #docregion Shop
import core;
widget Shop = ListView(
children: [
Text(text: "Products:"),
...for product in data.server.games:
Product(product: product)
],
);
widget Product = Text(text: args.product.name, softWrap: false, overflow: "fade");
// #enddocregion Shop
''',
'MaterialShop': '''
// #docregion MaterialShop
import core;
import material;
widget MaterialShop = Scaffold(
appBar: AppBar(
title: Text(text: ['Products']),
),
body: ListView(
children: [
...for product in data.server.games:
Product(product: product)
],
),
);
widget Product = ListTile(
title: Text(text: args.product.name),
onTap: event 'shop.productSelect' { name: args.product.name, path: args.product.link },
);
// #enddocregion MaterialShop
''',
};
// The empty docregion at the end of the following causes the snippet to end with "// ...".
const String gameData =
'''
// #docregion game-data
{ "games": [
{"rating": 8.219, "users-rated": 16860, "name": "Twilight Struggle", "rank": 1, "link": "/boardgame/12333/twilight-struggle", "id": 12333},
{"rating": 8.093, "users-rated": 11750, "name": "Through the Ages: A Story of Civilization", "rank": 2, "link": "/boardgame/25613/through-ages-story-civilization", "id": 25613},
{"rating": 8.088, "users-rated": 34745, "name": "Agricola", "rank": 3, "link": "/boardgame/31260/agricola", "id": 31260},
{"rating": 8.082, "users-rated": 8913, "name": "Terra Mystica", "rank": 4, "link": "/boardgame/120677/terra-mystica", "id": 120677},
// #enddocregion game-data
// #docregion game-data
// #enddocregion game-data
] }
''';
List<WidgetLibrary> _createLocalWidgets(String region) {
switch (region) {
case 'root':
return <WidgetLibrary>[LocalWidgetLibrary(<String, LocalWidgetBuilder>{
// #docregion defaultLocalWidgets
'GreenBox': (BuildContext context, DataSource source) {
return Container(color: const Color(0xFF002211), child: source.child(<Object>['child']));
},
'Hello': (BuildContext context, DataSource source) {
return Center(child: Text('Hello, ${source.v<String>(<Object>["name"])}!', textDirection: TextDirection.ltr));
},
// #enddocregion defaultLocalWidgets
})];
case 'fruit':
return <WidgetLibrary>[
LocalWidgetLibrary(<String, LocalWidgetBuilder>{
// #docregion v
'Foo': (BuildContext context, DataSource source) {
return Text(source.v<String>(<Object>['bar', 'quux', 2])!);
},
// #enddocregion v
}),
LocalWidgetLibrary(<String, LocalWidgetBuilder>{
// #docregion isList
'Foo': (BuildContext context, DataSource source) {
if (source.isList(<Object>['bar', 'quux'])) {
return Text('${source.v<String>(<Object>['bar', 'quux', 2])}', textDirection: TextDirection.ltr);
}
return Text('${source.v<String>(<Object>['baz'])}', textDirection: TextDirection.ltr);
},
// #enddocregion isList
}),
];
case 'example1':
case 'example2':
return <WidgetLibrary>[LocalWidgetLibrary(<String, LocalWidgetBuilder>{
// #docregion child
'GreenBox': (BuildContext context, DataSource source) {
return Container(color: const Color(0xFF002211), child: source.child(<Object>['child']));
},
// #enddocregion child
// #docregion isMap
'Foo': (BuildContext context, DataSource source) {
if (source.isMap(<Object>['bar'])) {
return Text('${source.v<String>(<Object>['bar', 'name'])}', textDirection: TextDirection.ltr);
}
return Text('${source.v<String>(<Object>['bar'])}', textDirection: TextDirection.ltr);
},
// #enddocregion isMap
})];
case 'example3':
return <WidgetLibrary>[LocalWidgetLibrary(<String, LocalWidgetBuilder>{
// #docregion optionalChild
'GreenBox': (BuildContext context, DataSource source) {
return Container(color: const Color(0xFF002211), child: source.optionalChild(<Object>['child']));
},
// #enddocregion optionalChild
// #docregion length
'Foo': (BuildContext context, DataSource source) {
final int length = source.length(<Object>['text']);
if (length > 0) {
final StringBuffer text = StringBuffer();
for (int index = 0; index < length; index += 1) {
text.write(source.v<String>(<Object>['text', index]));
}
return Text(text.toString(), textDirection: TextDirection.ltr);
}
return const Text('<empty>', textDirection: TextDirection.ltr);
},
// #enddocregion length
})];
case 'tap':
// #docregion onTap
return <WidgetLibrary>[
LocalWidgetLibrary(<String, LocalWidgetBuilder>{
// The local widget is called `GestureDetector`...
'GestureDetector': (BuildContext context, DataSource source) {
// The local widget is implemented using the `GestureDetector`
// widget from the Flutter framework.
return GestureDetector(
onTap: source.voidHandler(<Object>['onTap']),
// A full implementation of a `GestureDetector` local widget
// would have more arguments here, like `onTapDown`, etc.
child: source.optionalChild(<Object>['child']),
);
},
}),
];
// #enddocregion onTap
case 'tapDown':
return <WidgetLibrary>[
LocalWidgetLibrary(<String, LocalWidgetBuilder>{
'GestureDetector': (BuildContext context, DataSource source) {
// #docregion onTapDown
return GestureDetector(
onTapDown: source.handler(<Object>['onTapDown'], (HandlerTrigger trigger) => (TapDownDetails details) => trigger()),
child: source.optionalChild(<Object>['child']),
);
// #enddocregion onTapDown
},
}),
LocalWidgetLibrary(<String, LocalWidgetBuilder>{
'GestureDetector': (BuildContext context, DataSource source) {
// #docregion onTapDown-long
return GestureDetector(
// onTapDown expects a function that takes a TapDownDetails
onTapDown: source.handler<GestureTapDownCallback>( // this returns a function that takes a TapDownDetails
<Object>['onTapDown'],
(HandlerTrigger trigger) { // "trigger" is the function that will send the event to RemoteWidget.onEvent
return (TapDownDetails details) { // this is the function that is returned by handler() above
trigger(); // the function calls "trigger"
};
},
),
child: source.optionalChild(<Object>['child']),
);
// #enddocregion onTapDown-long
},
}),
LocalWidgetLibrary(<String, LocalWidgetBuilder>{
'GestureDetector': (BuildContext context, DataSource source) {
// #docregion onTapDown-position
return GestureDetector(
onTapDown: source.handler(<Object>['onTapDown'], (HandlerTrigger trigger) {
return (TapDownDetails details) => trigger(<String, Object>{
'x': details.globalPosition.dx,
'y': details.globalPosition.dy,
});
}),
child: source.optionalChild(<Object>['child']),
);
// #enddocregion onTapDown-position
},
}),
];
case 'Shop':
case 'MaterialShop':
return <WidgetLibrary>[];
default:
fail('test has no defined local widgets for root widget "$region"');
}
}
void main() {
testWidgets('readme snippets', (WidgetTester tester) async {
final Runtime runtime = Runtime()
..update(const LibraryName(<String>['core']), createCoreWidgets())
..update(const LibraryName(<String>['material']), createMaterialWidgets());
final DynamicContent data = DynamicContent(parseDataFile(gameData));
for (final String region in rawRemoteWidgetSnippets.keys) {
final String body = rawRemoteWidgetSnippets[region]!;
runtime.update(LibraryName(<String>[region]), parseLibraryFile(body));
}
for (final String region in rawRemoteWidgetSnippets.keys) {
for (final WidgetLibrary localWidgets in _createLocalWidgets(region)) {
await tester.pumpWidget(
MaterialApp(
home: RemoteWidget(
runtime: runtime
..update(const LibraryName(<String>['local']), localWidgets),
data: data,
widget: FullyQualifiedWidgetName(LibraryName(<String>[region]), region),
),
),
);
}
}
});
}

View File

@ -26,6 +26,5 @@
- plugin_platform_interface
- pointer_interceptor
- quick_actions/quick_actions
- rfw
- webview_flutter_android
- webview_flutter_web