decodeLibraryBlob function

RemoteWidgetLibrary decodeLibraryBlob(
  1. Uint8List bytes
)

Decode a Remote Flutter Widgets binary library blob.

Remote widget libraries are usually used in conjunction with a Runtime.

Format

This format is a depth-first serialization of the in-memory data structures, using a one-byte tag to identify types when necessary, and using 64 bit integers to encode lengths when necessary.

The first four bytes of the file (in hex) are FE 52 46 57; see libraryBlobSignature.

Primitives in this format are as follows:

  • Integers are encoded as little-endian two's complement 64 bit integers. For example, the number 513 (0x0000000000000201) is encoded as a 0x01 byte, a 0x02 byte, and six 0x00 bytes, in that order.

  • Doubles are encoded as little-endian IEEE binary64 numbers.

  • Strings are encoded as an integer length followed by that many UTF-8 encoded bytes.

    For example, the string "Hello" would be encoded as:

    05 00 00 00 00 00 00 00  48 65 6C 6C 6F
    
  • Lists are encoded as an integer length, followed by that many values back to back. When lists are of specific types (e.g. lists of imports), each value in the list is encoded directly (untagged lists); when the list can have multiple types, each value is prefixed by a tag giving the type, followed by the value (tagged lists). For example, a list of integers with the values 1 and 2 in that order would be encoded as:

    02 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00
    02 00 00 00 00 00 00 00
    

    A list of arbitrary values that happens to contain one string "Hello" would be encoded as follows; 0x04 is the tag for "String" (the full list of tags is described below):

    01 00 00 00 00 00 00 00  04 05 00 00 00 00 00 00
    00 48 65 6C 6C 6F
    

    A list of length zero is eight zero bytes with no additional payload.

  • Maps are encoded as an integer length, followed by key/value pairs. For maps where all the keys are strings (e.g. when encoding a DynamicMap), the keys are given without tags (an untagged map). For maps where the keys are of arbitrary values, the keys are prefixed by a tag byte (a tagged map; this is only used when encoding Switches). The values are always prefixed by a tag byte (all maps are over values of arbitrary types).

    For example, the map { a: 15 } (when the keys are known to always be strings, so they are untagged) is encoded as follows (0x02 is the tag for integers):

    01 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00
    61 02 0F 00 00 00 00 00  00 00
    

Objects are encoded as follows:

  • RemoteWidgetLibrary objects are encoded as an untagged list of imports and an untagged list of widget declarations.

  • Imports are encoded as an untagged list of strings, each of which is one of the subparts of the imported library name. For example, import a.b is encoded as:

    02 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00
    61 01 00 00 00 00 00 00  00 62
    
  • Widget declarations are encoded as a string giving the declaration name, an untagged map for the initial state, and finally the value that represents the root of the widget declaration (WidgetDeclaration.root, which is always either a Switch or a ConstructorCall).

    When the widget's initial state is null, it is encoded as an empty map. By extension, this means no distinction is made between a "stateless" remote widget and a "stateful" remote widget whose initial state is empty. (This is reasonable since if the initial state is empty, no state can ever be changed, so the widget is in fact de facto stateless.)

Values are encoded as a tag byte followed by their data, as follows:

  • Booleans are encoded as just a tag, with the tag being 0x00 for false and 0x01 for true.

  • Integers have the tag 0x02, and are encoded as described above (two's complement, little-endian, 64 bit).

  • Doubles have the tag 0x03, and are encoded as described above (little-endian binary64).

  • Strings have the tag 0x04, and are encoded as described above (length followed by UTF-8 bytes).

  • Lists (DynamicList) have the tag 0x05, are encoded as described above (length followed by tagged values). (Lists of untagged values are never found in a "value" context.)

  • Maps (DynamicMap) have the tag 0x07, are encoded as described above (length followed by pairs of strings and tagged values). (Tagged maps, i.e. those with tagged keys, are never found in a "value" context.)

  • Loops (Loop) have the tag 0x08. They are encoded as two tagged values, the Loop.input and the Loop.output.

  • Constructor calls (ConstructorCall) have the tag 0x09. They are encoded as a string for the ConstructorCall.name followed by an untagged map describing the ConstructorCall.arguments.

  • Argument, data, and state references (ArgsReference, DataReference, and StateReference respectively) have tags 0x0A, 0x0B, and 0x0D respectively, and are encoded as tagged lists of strings or integers giving the Reference.parts of the reference.

  • Loop references (LoopReference) have the tag 0x0C, and are encoded as an integer giving the number of Loop objects between the reference and the loop being referenced (this is similar to a De Bruijn index), followed by a tagged list of strings or integers giving the Reference.parts of the reference.

  • Switches (Switch) have the tag 0x0F. They are encoded as a tagged value describing the control value (Switch.input), followed by a tagged map for the various case values (Switch.outputs). The default case is represented by a value with tag 0x10 (and no data).

    For example, this switch:

    switch (args.a) {
     0: 'z',
     1: 'o',
     default: 'd',
    }
    

    ...is encoded as follows (including the tag for the switch itself):

    0F 0A 01 00 00 00 00 00  00 00 61 03 00 00 00 00
    00 00 00 02 00 00 00 00  00 00 00 00 04 01 00 00
    00 00 00 00 00 7A 02 01  00 00 00 00 00 00 00 04
    01 00 00 00 00 00 00 00  6F 10 04 01 00 00 00 00
    00 00 00 64
    
  • Event handlers have the tag 0x0E, and are encoded as a string (EventHandler.eventName) and an untagged map (EventHandler.eventArguments).

  • State-setting handlers have the tag 0x11, and are encoded as a tagged list of strings or integers giving the Reference.parts of the state reference (SetStateHandler.stateReference), followed by the tagged value to which to set that state entry (SetStateHandler.value).

Limitations

JavaScript does not have a native integer type; all numbers are stored as doubles. Data loss may therefore occur when handling integers that cannot be completely represented as a binary64 floating point number.

Integers are used for two purposes in this format; as a length, for which it is extremely unlikely that numbers above 2^53 would be practical anyway, and for representing integer literals. Thus, when using RFW with JavaScript environments, it is recommended to use doubles instead of ints whenever possible, to avoid accidental data loss.

See also:

Implementation

RemoteWidgetLibrary decodeLibraryBlob(Uint8List bytes) {
  final _BlobDecoder decoder = _BlobDecoder(bytes.buffer.asByteData(bytes.offsetInBytes, bytes.lengthInBytes));
  decoder.expectSignature(libraryBlobSignature);
  final RemoteWidgetLibrary result = decoder.readLibrary();
  if (!decoder.finished) {
    throw const FormatException('Unexpected trailing bytes after constructors.');
  }
  return result;
}