rfw 1.1.0
rfw: ^1.1.0 copied to clipboard
Metadata
Remote Flutter widgets: a library for rendering declarative widget description files at runtime.
Remote Flutter Widgets#
This package provides a mechanism for rendering widgets based ondeclarative UI descriptions that can be obtained at runtime.
Status#
This package is relatively stable.
We plan to keep the format and supported widget set backwards compatible,so that once a file works, it will keep working.However, this is best-effortonly. To guarantee that files keep working as you expect, submittests to this package (e.g. the binary file and the corresponding screenshot,as a golden test).
The set of widgets supported by this package is somewhat arbitrary.PRs that add new widgets from Flutter's default widget libraries(widgets,material, and'cupertino) are welcome.
There are some known theoretical performance limitations with thepackage's current implementation, but so far nobody has reportedexperiencing them in production. Pleasefileissuesif you run into them.
Feedback#
We would love to hear your experiences using this package, whetherpositive or negative. In particular, stories of uses of this packagein production would be very interesting. Please add comments toissue90218.
Limitations#
Once you realize that you can ship UI (and maybe logic, e.g. usingWasm; see the example below) you will slowly be tempted to move yourwhole application to this model.
This won't work.
Flutter proper lets you create widgets for compelling UIs withgestures and animations and so forth. With RFW you can use thosewidgets, but it doesn't let youcreate those widgets.
For example, you don't want to use RFW to create a UI that involvespage transitions. You don't want to use RFW to create new widgets thatinvolve drag and drop. You don't want to use RFW to create widgetsthat involve custom painters.
Rather, RFW is best suited for interfaces made out of prebuiltcomponents. For example, a database front-end could use this todescribe bespoke UIs for editing different types of objects in thedatabase. Message-of-the-day announcements could be built using thismechanism. Search interfaces could use this mechanism for rich resultcards.
RFW is well-suited for describing custom UIs from a potentiallyinfinite set of UIs that could not possibly have been known when theapplication was created. On the other hand, updating the application'slook and feel, changing how navigation works in an application, oradding new features, are all changes that are best made in Flutteritself, creating a new application and shipping that through normalupdate channels.
Using Remote Flutter Widgets#
Introduction#
The Remote Flutter Widgets (RFW) package combines widget descriptionsobtained at runtime, data obtained at runtime, some predefined widgetsprovided at compile time, and some app logic provided at compile time(possibly combined with other packages to enable new logic to beobtained at runtime), to generate arbitrary widget trees at runtime.
The widget descriptions obtained at runtime (e.g. over the network)are calledremote widget libraries. These are normally transportedin a binary format with the file extension.rfw. They can be writtenin a text format (file extension.rfwtxt), and either used directlyor converted into the binary format for transport. Therfw packageprovides APIs for parsing and encoding these formats. Theparts of the packagethat only deal with these formats can be imported directly and have nodependency on Flutter'sdart:ui library, which means they can beused on the server or in command-line applications.
The data obtained at runtime is known asconfiguration data and isrepresented byDynamicContent objects. It uses a data structuresimilar to JSON (but it distinguishesint anddouble and does notsupportnull). Therfw package provides both binary and textformats to carry this data; JSON can also be used directly (with somecaution), and the data can be created directly in Dart. This isdiscussed in more detail in theDynamicContentAPI documentation.
Remote widget libraries can use the configuration data to define howthe widgets are built.
Remote widget libraries all eventually bottom out in the predefinedwidgets that are compiled into the application. These are calledlocal widget libraries. Therfw package ships with two localwidget libraries, thecorewidgetsfrom thewidgets library (such asText,Center,Row, etc), andsome of thematerialwidgets.
Programs can define their own local widget libraries, to provide morewidgets for remote widget libraries to use.
These components are combined using aRemoteWidgetwidget and aRuntimeobject.
The remote widget libraries can specifyevents that trigger inresponse to callbacks. For example, theOutlinedButton widgetdefined in theMateriallocal widget library has anonPressed property which the remotewidget library can define as triggering an event. Events can containdata (either hardcoded or obtained from the configuration data).
These events result in a callback on theRemoteWidget being invoked.Events can either have hardcoded results, or therfw package can becombined with other packages such aswasm_run_flutter sothat events trigger code obtained at runtime. That code typicallychanges the configuration data, resulting in an update to the renderedwidgets.
See also:API documentation
Getting started#
A Flutter application can render remote widgets using theRemoteWidget widget, as in the following snippet:
class Example extends StatefulWidget { const Example({super.key}); @override State<Example> createState() => _ExampleState();}class _ExampleState extends State<Example> { final Runtime _runtime = Runtime(); final DynamicContent _data = DynamicContent(); // Normally this would be obtained dynamically, but for this example // we hard-code the "remote" widgets into the app. // // Also, normally we would decode this with [decodeLibraryBlob] rather than // parsing the text version using [parseLibraryFile]. However, to make it // easier to demo, this uses the slower text format. static final RemoteWidgetLibrary _remoteWidgets = parseLibraryFile(''' // The "import" keyword is used to specify dependencies, in this case, // the built-in widgets that are added by initState below. import core.widgets; // The "widget" keyword is used to define a new widget constructor. // The "root" widget is specified as the one to render in the build // method below. widget root = Container( color: 0xFF002211, child: Center( child: Text(text: ["Hello, ", data.greet.name, "!"], textDirection: "ltr"), ), ); '''); static const LibraryName coreName = LibraryName(<String>['core', 'widgets']); static const LibraryName mainName = LibraryName(<String>['main']); @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'}); } @override Widget build(BuildContext context) { return RemoteWidget( runtime: _runtime, data: _data, widget: const FullyQualifiedWidgetName(mainName, 'root'), onEvent: (String name, DynamicMap arguments) { // The example above does not have any way to trigger events, but if it // did, they would result in this callback being invoked. debugPrint('user triggered event "$name" with data: $arguments'); }, ); }}In this example, the "remote" widgets are hardcoded into theapplication (_remoteWidgets), the configuration data is hardcodedand unchanging (_data), and the event handler merely prints amessage to the console.
In typical usage, the remote widgets come from a server at runtime,either through HTTP or some other network transport. Separately, theDynamicContent data would be updated, either from the server orbased on local data.
Similarly, events that are signalled by the user's interactions withthe remote widgets (RemoteWidget.onEvent) would typically be sent tothe server for the server to update the data, or would cause the datato be updated directly, on the user's device, according to somepredefined logic.
It is recommended that servers send binary data, decoded usingdecodeLibraryBlob anddecodeDataBlob, when providing updates forthe remote widget libraries and data.
Applying these concepts to typical use cases#
Message of the day, advertising, announcements
Whenrfw is used for displaying content that is largely static inpresentation and updated only occasionally, the simplest approach isto encode everything into the remote widget library, download that tothe client, and render it, with only minimal data provided in theconfiguration data (e.g. the user's dark mode preference, theirusername, the current date or time) and with a few predefined events(such as one to signal the message should be closed and another tosignal the user checking a "do not show this again" checkbox, orsimilar).
Dynamic data editors
A more elaborate use case might involve remote widget libraries beingused to describe the UI for editing structured data in a database. Inthis case, the data may be more important, containing the current databeing edited, and the events may signal to the application how toupdate the data on the backend.
Search results
A general search engine could have dedicated remote widgets definedfor different kinds of results, allowing the data to be formatted andmade interactive in ways that are specific to the query and in waysthat could not have been predicted when the application was created.For example, new kinds of search results for current events could becreated on the fly and sent to the client without needing to updatethe client application.
Developing new local widget libraries#
A "local" widget library is one that describes the built-in widgetsthat your "remote" widgets are built out of. The RFW package comeswith some preprepared libraries, available throughcreateCoreWidgetsandcreateMaterialWidgets.You can also create your own.
When developing new local widget libraries, it is convenient to hookinto thereassemble method to update the local widgets. That way,changes can be seen in real time when hot reloading.
class Example extends StatefulWidget { const Example({super.key}); @override State<Example> createState() => _ExampleState();}class _ExampleState extends State<Example> { final Runtime _runtime = Runtime(); final DynamicContent _data = DynamicContent(); @override void initState() { super.initState(); _update(); } @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(); } static WidgetLibrary _createLocalWidgets() { return LocalWidgetLibrary(<String, LocalWidgetBuilder>{ 'GreenBox': (BuildContext context, DataSource source) { return ColoredBox( 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, ), ); }, }); } static const LibraryName localName = LibraryName(<String>['local']); static const LibraryName remoteName = LibraryName(<String>['remote']); void _update() { _runtime.update(localName, _createLocalWidgets()); // Normally we would obtain the remote widget library in binary form from a // server, and decode it with [decodeLibraryBlob] rather than parsing the // text version using [parseLibraryFile]. However, to make it easier to // play with this sample, this uses the slower text format. _runtime.update( remoteName, parseLibraryFile(''' import local; widget root = GreenBox( child: Hello(name: "World"), ); '''), ); } @override Widget build(BuildContext context) { return RemoteWidget( runtime: _runtime, data: _data, widget: const FullyQualifiedWidgetName(remoteName, 'root'), onEvent: (String name, DynamicMap arguments) { debugPrint('user triggered event "$name" with data: $arguments'); }, ); }}Widgets in local widget libraries are represented by closures that areinvoked by the runtime whenever a local widget is referenced.
The closure uses theLocalWidgetBuildersignature. Like any builder in Flutter, it takes aBuildContext,which can be used to look up inherited widgets.
For example, widgets that need the current text direction mightdefer to
Directionality.of(context), with the givenBuildContextas the context argument.
The other argument is aDataSource.This gives access to the arguments that were provided to the widget inthe remote widget library.
For example, consider the example above, where the remote widget library is:
import local;widget root = GreenBox( child: Hello(name: "World"),);TheGreenBox widget is invoked with one argument (child), and theHello widget is invoked with one argument (name).
In the definitions ofGreenBox andHello, the data source is usedto pull out these arguments.
Obtaining arguments from theDataSource#
The arguments are a tree of maps and lists with leaves that are Dartscalar values (int,double,bool, orString), further widgets,or event handlers.
Scalars
Here is an example of a more elaborate widget argument:
widget fruit = Foo( bar: { quux: [ 'apple', 'banana', 'cherry' ] },);To obtain a scalar value from the arguments, theDataSource.vmethod is used. This method takes a list of keys (strings or integers)that denote the path to scalar in question. For instance, to obtain"cherry" from the example above, the keys would bebar,quux, and2, as in:
'Foo': (BuildContext context, DataSource source) { return Text(source.v<String>(<Object>['bar', 'quux', 2])!);},Thev method is generic, with a type argument that specifies theexpected type (one ofint,double,bool, orString). When thevalue of the argument in the remote widget library does not match thespecified (or inferred) type given tov, or if the specified keysdon't lead to a value at all, it returns null instead.
Maps and lists
TheLocalWidgetBuilder callback can inspect keys to see if they aremaps or lists before attempting to use them. For example, beforeaccessing a dozen fields from a map, one might useisMap to check ifthe map is present at all. If it is not, then all the fields will benull, and it is inefficient to fetch each one individually.
TheDataSource.isMapmethod is takes a list of keys (likev) and reports if the keyidentifies a map.
For example, in this case thebar argument can be treated either asa map with aname subkey, or a scalar String:
'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);},Thus either of the following would have the same result:
widget example1 = GreenBox( child: Foo( bar: 'Jean', ),);widget example2 = GreenBox( child: Foo( bar: { name: 'Jean' }, ),);TheDataSource.isListmethod is similar but reports on whether the specified key identifies a list:
'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);},For lists, aLocalWidgetBuilder callback can iterate over the itemsin the list using thelengthmethod, which returns the length of the list (or zero if the key doesnot identify a list):
'Foo': (BuildContext context, DataSource source) { final int length = source.length(<Object>['text']); if (length > 0) { final text = StringBuffer(); for (var 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);},This could be used like this:
widget example3 = GreenBox( child: Foo( text: ['apple', 'banana'] ),);Widgets
TheGreenBox widget has a child widget, which is itself specified bythe remote widget. This is common, for example,Row andColumnwidgets have children,Center has a child, and so on. Indeed, mostwidgets have children, except for those likeText,Image, andSpacer.
TheGreenBox definition usesDataSource.childto obtain the widget, in a manner similar to thev method:
'GreenBox': (BuildContext context, DataSource source) { return ColoredBox(color: const Color(0xFF002211), child: source.child(<Object>['child']));},Rather than returningnull when the specified key points to anargument that isn't a widget, thechild method returns anErrorWidget. For cases where havingnull is acceptable, theoptionalChild method can be used:
'GreenBox': (BuildContext context, DataSource source) { return ColoredBox(color: const Color(0xFF002211), child: source.optionalChild(<Object>['child']));},It returnsnull when the specified key does not point to a widget.
For widgets that take lists of children, thechildListmethod can be used. For example, this is howRow is defined increateCoreWidgets (see in particular thechildren line):
'Row': (BuildContext context, DataSource source) { return Row( mainAxisAlignment: ArgumentDecoders.enumValue<MainAxisAlignment>(MainAxisAlignment.values, source, ['mainAxisAlignment']) ?? MainAxisAlignment.start, mainAxisSize: ArgumentDecoders.enumValue<MainAxisSize>(MainAxisSize.values, source, ['mainAxisSize']) ?? MainAxisSize.max, crossAxisAlignment: ArgumentDecoders.enumValue<CrossAxisAlignment>(CrossAxisAlignment.values, source, ['crossAxisAlignment']) ?? CrossAxisAlignment.center, textDirection: ArgumentDecoders.enumValue<TextDirection>(TextDirection.values, source, ['textDirection']), verticalDirection: ArgumentDecoders.enumValue<VerticalDirection>(VerticalDirection.values, source, ['verticalDirection']) ?? VerticalDirection.down, textBaseline: ArgumentDecoders.enumValue<TextBaseline>(TextBaseline.values, source, ['textBaseline']), children: source.childList(['children']), );},ArgumentDecoders
It is common to need to decode types that are more structured thanmerelyint,double,bool, orString scalars, for example,enums,Colors, orPaints.
TheArgumentDecodersnamespace offers some utility functions to make the decoding of suchvalues consistent.
For example, theRow definition above has some cases of enums. Todecode them, it uses theArgumentDecoders.enumValuemethod.
Handlers
The last kind of argument that widgets can have is callbacks.
Since remote widget libraries are declarative and not code, theycannot represent executable closures. Instead, they are represented asevents. For example:
CalculatorButton(label: "7", onPressed: event "digit" { arguments: [7] }),This creates aCalculatorButton widget with two arguments,label,a string, andonPressed, an event, whose name is "digit" and whosearguments are a map with one key, "arguments", whose value is a listwith one value 7.
In that example,CalculatorButton is itself a remote widget that isdefined in terms of aButton, and theonPressed argumentis passed to theonPressed of theButton, like this:
widget CalculatorButton = Padding( padding: [8.0], child: SizedBox( width: 100.0, height: 100.0, child: Button( child: FittedBox(child: Text(text: args.label)), onPressed: args.onPressed, ), ),);Subsequently,Button is defined in terms of aGestureDetectorlocal widget (which is defined in terms of theGestureDetectorwidget from the Flutter framework), and theargs.onPressed is passedto theonTap argument of thatGestureDetector local widget (andfrom there subsequently to the Flutter framework widget).
When all is said and done, and the button is pressed, an event withthe name "digit" and the given arguments is reported to theRemoteWidget'sonEvent callback. That callback takes twoarguments, the event name and the event arguments.
On the implementation side, in local widget libraries, arguments liketheonTap of theGestureDetector local widget must be turned intoa Dart closure that is passed to the actual Flutter widget calledGestureDetector as the value of itsonTap callback.
The simplest kind of callback closure is aVoidCallback (noarguments, no return value). To turn anevent value in a localwidget's arguments in the local widget library into aVoidCallbackin Dart that reports the event as described above, theDataSource.voidHandler method is used. For example, here is asimplifiedGestureDetector local widget that just implementsonTap(when implementing similar local widgets, you may use a similartechnique):
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']), ); }, }),];Sometimes, a callback has a different signature, in particular, it mayprovide arguments. To convert theevent value into a Dart callbackclosure that reports an event as described above, theDataSource.handler method is used.
In addition to the list of keys that identify theevent value, themethod itself takes a callback closure. That callback's purpose is toconvert the giventrigger (a function which, when called, reportsthe event) into the kind of callback closure theWidget expects.This is usually written something like the following:
return GestureDetector( onTapDown: source.handler(<Object>['onTapDown'], (HandlerTrigger trigger) => (TapDownDetails details) => trigger()), child: source.optionalChild(<Object>['child']),);To break this down more clearly:
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']),);In some cases, the arguments sent to the callback (theTapDownDetails in this case) are useful and should be passed to theRemoteWidget.onEvent as part of its arguments. This can be done bypassing some values to thetrigger method, as in:
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']),);Any arguments in theevent get merged with the arguments passed tothe trigger.
Animations
Therfw package introduces a new Flutter widget calledAnimationDefaults.
This widget is exposed bycreateCoreWidgets under the same name, andcan be exposed in other local widget libraries as desired. This allowsremote widget libraries to configure the animation speed and curves ofentire subtrees more conveniently than repeating the details for eachwidget.
To support this widget, implement curve arguments usingArgumentDecoders.curveand duration arguments usingArgumentDecoders.duration.This automatically defers to the defaults provided byAnimationDefaults. Alternatively, theAnimationDefaults.curveOfandAnimationDefaults.durationOfmethods can be used with aBuildContext directly to get curve andduration settings for animations.
The settings default to 200ms and theCurves.fastOutSlowIncurve.
Developing remote widget libraries#
Remote widget libraries are usually defined using a Remote FlutterWidgets text library file (rfwtxt extension), which is then compiledinto a binary library file (rfw extension) on the server beforebeing sent to the client.
The format of text library files is defined in detail in the APIdocumentation of theparseLibraryFilefunction.
Compiling a textrfwtxt file to the binaryrfw format can be doneby callingencodeLibraryBlobon the results of callingparseLibraryFile.
The example inexample/remote has someelaborate remotewidgets,including some that manipulate state (Button).
State
The canonical example of a state-manipulating widget is a button.Buttons must react immediately (in milliseconds) and cannot wait forlogic that's possibly running on a remote server (maybe many hundredsof milliseconds away).
The aforementionedButton widget in theremote_widget_libraries exampletracks a local "down" state, manipulates it in reaction toonTapDown/onTapUp events, and changes the shadow and margins ofthe button based on its state:
widget Button { down: false } = GestureDetector( onTap: args.onPressed, onTapDown: set state.down = true, onTapUp: set state.down = false, onTapCancel: set state.down = false, child: Container( duration: 50, margin: switch state.down { false: [ 0.0, 0.0, 2.0, 2.0 ], true: [ 2.0, 2.0, 0.0, 0.0 ], }, padding: [ 12.0, 8.0 ], decoration: { type: "shape", shape: { type: "stadium", side: { width: 1.0 }, }, gradient: { type: "linear", begin: { x: -0.5, y: -0.25 }, end: { x: 0.0, y: 0.5 }, colors: [ 0xFFFFFF99, 0xFFEEDD00 ], stops: [ 0.0, 1.0 ], tileMode: "mirror", }, shadows: switch state.down { false: [ { blurRadius: 4.0, spreadRadius: 0.5, offset: { x: 1.0, y: 1.0, } } ], default: [], }, }, child: DefaultTextStyle( style: { color: 0xFF000000, fontSize: 32.0, }, child: args.child, ), ),);BecauseContainer is implemented increateCoreWidgets using theAnimatedContainer widget, changing the fields causes the button toanimate. Theduration: 50 argument sets the animation speed to 50ms.
Lists
Let us consider a remote widget library that is used to render data inthis form:
{ "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},// ···For the sake of this example, let us assume this data is registeredwith theDynamicContent under the nameserver.
This configuration data is both valid JSON and a valid RFW data file,which shows how similar the two syntaxes are.
This data is parsed by calling
parseDataFile,which turns it intoDynamicMap.That object is then passed to aDynamicContent,usingDynamicContent.update(this is where the nameserverwould be specified) which is passedto aRemoteWidgetvia thedataproperty.Ideally, rather than dealing with this text form on the client, thedata would be turned into a binary form using
encodeDataBlobon the server, and then parsed on the client usingdecodeDataBlob.
First, let's render a plain FlutterListView with the name of eachproduct. TheShop widget below achieves this:
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");TheProduct widget here is not strictly necessary, it could beinlined into theShop. However, as with Flutter itself, it can beeasier to develop widgets when logically separate components areseparated into separate widgets.
We can elaborate on this example, introducing a MaterialAppBar,using aListTile for the list items, and making them interactive (atleast in principle; the logic in the app would need to know how tohandle the "shop.productSelect" event):
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 },);Fetching remote widget libraries remotely#
The example inexample/remote shows how a program could fetchdifferent user interfaces at runtime. In this example, the interfaceused on startup is the one last cached locally. Each time the programis run, after displaying the currently-cached interface, theapplication fetches a new interface over the network, overwriting theone in the cache, so that a different interface is used the next timethe app is run.
This example also shows how an application can implement custom localcode for events; in this case, incrementing a counter (both of the"remote" widgets are just different ways of implementing a counter).
Integrating with scripting language runtimes#
The example inexample/wasm shows how a program could fetch logic inaddition to UI, in this case using Wasm compiled from C (and let usbriefly appreciate the absurdity of using C as a scripting languagefor an application written in Dart).
In this example, as written, the Dart client could support anyapplication whose data model consisted of a single integer and whoselogic could be expressed in C without external dependencies.
This example could be extended to have the C program export data inthe Remote Flutter Widgets binary data blob format which could beparsed usingdecodeDataBlob and passed toDynamicContent.update(thus allowing any structured data supported by RFW), and similarlyarguments could be passed to the Wasm code using the same format(encoding usingencodeDataBlob) to allow arbitrary structured datato be communicated from the interface to the Wasm logic. In addition,the Wasm logic could be provided with WASI interface bindings or withcustom bindings that expose platform capabilities (e.g. from Flutterplugins), greatly extending the scope of what could be implemented inthe Wasm logic.
As of the time of writing,package:wasm does not support Android,iOS, or web, so this demo is limited to desktop environments. Theunderlying Wasmer runtime supports Android and iOS already, andobviously Wasm in general is supported by web browsers, so it isexpected that these limitations are only temporary (modulo policyconcerns on iOS, anyway).
Contributing#
Publisher
Weekly Downloads
Metadata
Remote Flutter widgets: a library for rendering declarative widget description files at runtime.
Repository (GitHub)
View/report issues
Contributing
Topics
Documentation
License
BSD-3-Clause (license)
Dependencies
More
← Metadata
Publisher
Weekly Downloads
Metadata
Remote Flutter widgets: a library for rendering declarative widget description files at runtime.
Repository (GitHub)
View/report issues
Contributing
Topics
Documentation
License
BSD-3-Clause (license)