json_dynamic_widget

Last updated:

0 purchases

json_dynamic_widget Image
json_dynamic_widget Images
Add to Cart

Description:

json dynamic widget

json_dynamic_widget #


Table of Contents

Live Example
First Party Plugins
Migration to 7.X.X version

Code Generation

>= 7.0.0
< 7.0.0


Code Generation Annotations
Widget composition
Migration CLI


Usage
Understanding the Registry
Built In Widgets

Passing the nulls to the args


Using Expressions

Using Variables
Dynamic Functions

Basic function usage
Named args in functions
Complex function calls
Built functions




Creating Custom Widgets
Creating Custom Arg Processor


Live Example #

Web

First Party Plugins #
Here's a list of first party plugins that exist for this library.

json_dynamic_widget_plugin_components
json_dynamic_widget_plugin_font_awesome
json_dynamic_widget_plugin_ionicons
json_dynamic_widget_plugin_js
json_dynamic_widget_plugin_lottie
json_dynamic_widget_plugin_material_icons
json_dynamic_widget_plugin_markdown
json_dynamic_widget_plugin_rive
json_dynamic_widget_plugin_svg

Migration to 7.X.X version #
NOTE: There are several breaking changes in this release from the JSON Schema perspective. Almost all of them can be automatically migrated from v6 to v7 using the Migration CLI.

Code Generation #
As of 7.0.0 a code generator exists to simplify the creation of the dynamic widgets. The code generator can generate the Dart / Flutter code to build widgets or it can be used in reverse to generate the JSON / YAML from the Dart / Flutter code. For more advanced information on the code generator, see the Code Generator document. For more information on the JSON / YAML generator see Reverse Encoding.
The code that is now required to build a custom widget with the release of 7.0.0 is followed by the code that used to be required prior to 7.0.0.
>= 7.0.0
import 'package:json_dynamic_widget/json_dynamic_widget.dart';

part 'json_column_builder.g.dart';

@jsonWidget
abstract class _JsonColumnBuilder extends JsonWidgetBuilder {
const _JsonColumnBuilder({
required super.args,
});

@override
Column buildCustom({
ChildWidgetBuilder? childBuilder,
required BuildContext context,
required JsonWidgetData data,
Key? key,
});
}
copied to clipboard
< 7.0.0
import 'package:child_builder/child_builder.dart';
import 'package:flutter/material.dart';
import 'package:json_dynamic_widget/json_dynamic_widget.dart';
import 'package:json_theme/json_theme.dart';
import 'package:json_theme/json_theme_schemas.dart';

class JsonColumnBuilder extends JsonWidgetBuilder {
const JsonColumnBuilder({
required this.crossAxisAlignment,
required this.mainAxisAlignment,
required this.mainAxisSize,
this.textBaseline,
this.textDirection,
required this.verticalDirection,
}) : super(numSupportedChildren: kNumSupportedChildren);

static const kNumSupportedChildren = -1;

static const type = 'column';

final CrossAxisAlignment crossAxisAlignment;
final MainAxisAlignment mainAxisAlignment;
final MainAxisSize mainAxisSize;
final TextBaseline? textBaseline;
final TextDirection? textDirection;
final VerticalDirection verticalDirection;

static JsonColumnBuilder? fromDynamic(
dynamic map, {
JsonWidgetRegistry? registry,
}) {
JsonColumnBuilder? result;

if (map != null) {
result = JsonColumnBuilder(
crossAxisAlignment: ThemeDecoder.decodeCrossAxisAlignment(
map['crossAxisAlignment'],
validate: false,
) ??
CrossAxisAlignment.center,
mainAxisAlignment: ThemeDecoder.decodeMainAxisAlignment(
map['mainAxisAlignment'],
validate: false,
) ??
MainAxisAlignment.start,
mainAxisSize: ThemeDecoder.decodeMainAxisSize(
map['mainAxisSize'],
validate: false,
) ??
MainAxisSize.max,
textBaseline: ThemeDecoder.decodeTextBaseline(
map['textBaseline'],
validate: false,
),
textDirection: ThemeDecoder.decodeTextDirection(
map['textDirection'],
validate: false,
),
verticalDirection: ThemeDecoder.decodeVerticalDirection(
map['verticalDirection'],
validate: false,
) ??
VerticalDirection.down,
);
}

return result;
}

@override
Widget buildCustom({
ChildWidgetBuilder? childBuilder,
required BuildContext context,
required JsonWidgetData data,
Key? key,
}) {
return Column(
crossAxisAlignment: crossAxisAlignment,
key: key,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
textBaseline: textBaseline,
textDirection: textDirection,
verticalDirection: verticalDirection,
children: [
for (var child in data.children ?? <JsonWidgetData>[])
child.build(
context: context,
childBuilder: childBuilder,
),
],
);
}
}

class ColumnSchema {
static const id =
'https://peiffer-innovations.github.io/flutter_json_schemas'
'/schemas/json_dynamic_widget/column.json';

static final schema = {
r'$schema': 'http://json-schema.org/draft-06/schema#',
r'$id': id,
r'$children': -1,
r'$comment': 'https://api.flutter.dev/flutter/widgets/Column-class.html',
'title': 'Column',
'oneOf': [
{
'type': 'null',
},
{
'type': 'object',
'additionalProperties': false,
'properties': {
'crossAxisAlignment':
SchemaHelper.objectSchema(CrossAxisAlignmentSchema.id),
'mainAxisAlignment':
SchemaHelper.objectSchema(MainAxisAlignmentSchema.id),
'mainAxisSize': SchemaHelper.objectSchema(MainAxisSizeSchema.id),
'textBaseline': SchemaHelper.objectSchema(TextBaselineSchema.id),
'textDirection': SchemaHelper.objectSchema(TextDirectionSchema.id),
'verticalDirection':
SchemaHelper.objectSchema(VerticalDirectionSchema.id),
},
},
],
};
}
copied to clipboard

Code Generation Annotations #
See the Annotations guide for information on all of the code generation annotations available for use.

Widget composition #
To share the same arguments/annotations between multiple builders you can create mixins with the values you need.
In the following example, _ColumnBuilder and _RowBuilder shares the same properties (encode/decode/schema) of children when genreated.
mixin ChildrenArguments {
@JsonArgDecoder('children')
List<Widget> _decodeChildren({required value}) { ... };

@JsonArgEncoder('children')
static String _encodeChildren(List<Widget> value) { ... };

@JsonArgSchema('children')
static Map<String, dynamic> _childrenSchema() => { ... };
}


@jsonWidget
abstract class _ColumnBuilder extends JsonWidgetBuilder with ChildrenArguments { ... }

@jsonWidget
abstract class _RowBuilder extends JsonWidgetBuilder with ChildrenArguments { ... }

copied to clipboard

Migration CLI #
This version comes with a script that can migrate existing JSON / YAML files from v6 to v7 automatically. To run the script, first add the package as a dependency:
dependencies:
json_dynamic_widget: <version>
copied to clipboard
Then run:
dart run json_dynamic_widget:migrate_7 [path/to/files]
copied to clipboard
The script will automatically migrate the files it finds and make a backup using the original name + .bak. If you are satisfied with the output from the migration script, feel free to delete those backup files. For more information, see the Migration CLI documentation

Usage #
Important Note: Because this library allows for dynamic building of Icons, Flutter's built in tree shaker for icons no longer has the ability to guarantee what icons are referenced vs not. Once you include this as a dependency, you must add the --no-tree-shake-icons as a build flag or your builds will fail.
Example:
flutter build [apk | web | ios | ...] --no-tree-shake-icons
copied to clipboard
This library provides Widgets that are capable of building themselves from JSON or YAML structures. The general structure follows:
{
"type": "<lower_case_type>",
"id": "<optional-id>",
"listen": [
"var1",
"var2",
"..."
],
"args": {
"...": "..."
},
}
copied to clipboard
---
type: <lower_case_type>
id: <optional-id>
listen:
- var1
- var2
- ...
args:
...: ...

copied to clipboard
The listen array is used to define variable names that specified JsonWidgetData listen to. Thanks to that JsonWidgetData will be rebuilt with every change of such a variables.
In case of not defining such a array the JsonWidgetRegistry will try to built such a array dynamically and use any met variable. Good practice is to define it by a hand to reduce amount of rebuilds.
See the documentation and / or example app for the currently supported widgets. All built types are encoded using a lower-case and underscore separator as opposed to a camel case strategy. For instance, a ClipRect will have a type of clip_rect.
Once you have the JSON for a widget, you will use the JsonWidgetData to build the resulting Widget that can be added to the tree. For performance reasons, the JsonWidgetData should be instantiated once and then cached rather than created in each build cycle.
Example
import 'package:flutter/material.dart';
import 'package:json_dynamic_widget/json_dynamic_widget.dart';

class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({
@required this.jsonData,
this.registry,
Key key,
}): assert(jsonData != null),
super(key: key)

final Map<String, dynamic> jsonData;
final JsonWidgetRegistry registry;

@override
_MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyStatefulWidget> {
@override
void initState() {
super.initState();

_data = JsonWidgetData.fromDynamic(widget.jsonData);
}

@override
Wiget build(BuildContext context) => _data.build(
context,
registry: widget.registry ?? JsonWidgetRegistry.instance,
);
}
copied to clipboard
Understanding the Registry #
The JsonWidgetRegistry is the centralized processing warehouse for building and using the JSON Dynamic Widgets. Widgets must be registered to the registry to be available for building. The registry also supports providing dynamic variables and dynamic functions to the widgets that it builds.
The Registry is also repsonsible for processing JsonWidgetData args to their
real values via arg processors. Users can define their own arg processors which is giving the possibility to define the custom syntax.
When a value changes on the registry, it posts a notification to the valueStream so any potential processing logic can be executed. The dynamic widgets that use variable values also listen to this stream so they can update their widget state when a value they use for rendering change.
The registry always has a default instance that will be used when a substitute registry is not given. Substitute registeries can be created and used to isolate variables and functions within the app as needed. For instance, you may want a separate registry per page if each page may set dynamic values on the registry. This can prevent the values from one page being overwritten by another.
Built In Widgets #
The structure for all the args is defined in each widget builder, which are defined below:



Widget Builders
Example Location




align
align.json


animated_align
animated_align.json


animated_container
animated_container.json


animated_cross_fade
animated_cross_fade.json


animated_default_text_style
animated_default_text_style.json


animated_opacity
animated_opacity.json


animated_padding
animated_padding.json


animated_physical_model
animated_physical_model.json


animated_positioned
animated_positioned.json


animated_positioned_directional
animated_positioned_directional.json


animated_size
animated_size.json


animated_switcher
animated_switcher.json


animated_theme
animated_theme.json


app_bar
align.json


aspect_ratio
aspect_ratio.json


asset_image
asset_images.json


baseline
baseline.json


button_bar
card.json


card
card.json


center
center.json


checkbox
checkbox.json


circular_progress_indicator
circular_progress_indicator.json


clip_oval
clips.json


clip_path
clips.json


clip_rect
clips.json


clip_rrect
bank_example.json


column
bank_example.json


comment
scroll_view.json


conditional
conditional.json


container
bank_example.json


cupertino_switch
cupertino_switch.yaml


custom_scroll_view
slivers.json


directionality
directionality.json


dropdown_button_form_field
form.json


dynamic
dynamic.json


elevated_button
buttons.json


expanded
conditional.json


fitted_box
fitted_box.json


flexible
form.json


floating_action_button
buttons.json


form
form.json


fractional_translation
fractional_translation.json


fractionally_sized
fractionally_sized.json


gesture_detector
gestures.json


grid_view
for_each.json


hero
asset_images.json


icon
card.json


icon_button
buttons.json


ignore_pointer
gestures.json


indexed_stack
indexed_stack.json


ink_well
asset_images.json


input_error
input_error.json


interactive_viewer
interactive_viewer.json


intrinsic_height
intrinsic_height.json


intrinsic_width
intrinsic_width.json


limited_box
limited_box.json


linear_progress_indicator
linear_progress_indicator.json


list_tile
card.json


list_view
list_view.json


material
bank_example.json


memory_image
images.json


network_image
images.json


offstage
offstage.json


opacity
opacity.json


outlined_button
buttons.json


overflow_box
overflow_box.json


padding
bank_example.json


placeholder
placeholder.json


popup_menu_button
popup_menu_button.json


positioned
bank_example.json


primary_scroll_controller
scroll_view.json


radio
radio.json


rich_text
rich_text.json


row
bank_example.json


safe_area
form.json


save_context
form.json


scaffold
form.json


scroll_configuration_
scroll_view.json


scrollbar
scroll_view.json


set_default_value
set_default_value.json


set_scroll_controller
scroll_view.json


set_value
set_default_value.json


single_child_scroll_view
bank_example.json


sized_box
bank_example.json


sliver_grid
slivers.json


sliver_padding
slivers.json


sliver_list
slivers.json


sliver_to_box_adapter
slivers.json


stack
align.json


switch
switch.json


text
bank_example.json


text_button
buttons.json


text_form_field
form.json


theme
theme.json


tooltip
tooltip.json


tween_animation
tween_animation.json


wrap
wrap.json



All the internal builders are added to the registry by default.
It is possible to omit that behavior by using overrideInternalBuilders flag.
To select manually the internal functions it is recommended to use JsonWidgetInternalBuildersBuilder.
const JsonWidgetBuilderContainer(
builder: JsonBottomNavigationBarBuilder.fromDynamic,
schemaId: BottomNavigationBarSchema.id
)
JsonWidgetRegistry(
overrideInternalBuilders: true,
builders: {
...JsonWidgetInternalBuildersBuilder().withColumn().build(),
...<String, JsonWidgetBuilderContainer>{
JsonCustomBuilder.kType: JsonWidgetBuilderContainer(
builder: JsonCustomBuilder.fromDynamic,
schemaId: JsonCustomBuilderSchema.id)
}
});
copied to clipboard
Passing the nulls to the args #
All explicit nulls like {"key" : null} are removed from the args on parsing level.
Sometimes null value and lack of value are two separate pieces of information and there is a need to pass it up to builder level.
A special syntax must be used to fulfill that need:
{
"maxLines": "${null}"
}
copied to clipboard
Example:
null_value_passing.json

Using Expressions #
The library since version 4.0.0 has a tight integration with expressions library. By integrating the JsonWidgetRegistry variables and functions with that library there is possible to define different kind of simple expressions placed between ${}.

Using Variables #
Variables can be defined on the JsonWidgetRegistry that is used to render the dynamic widgets.
A variable can be used in any of the child / children / args values and for certain types of properties, a variable reference is the only way to actually assign that value.
Widgets that accept user input will assign that user input to a variable named the value inside of the id option, if an id exists. This allows widgets the ability to listen to input value updates.
There is a possibility to use them in JSON definition thanks to expressions library. Few examples:
${dynamicVariable}
${dynamic['persons'][0]}
${'Hello ' + name}
copied to clipboard
More examples are available at variables.json.
The built in variables are defined below:



Variable Name
Example
Description




${curveName}_curve
${linear_curve}${bounce_in_curve}
Provides a const instance of any of the Curves const values. The name of the Curve constant should be transformed into snake_case.




Dynamic Functions #
Basic function usage
Like any other expression functions defined in JsonWidgetRegistry can be used in JSON by placing their name and params between ${}. For example:
${sayHello('Hello,' + firstName)}
copied to clipboard
Assuming the function sayHello is implemented as, and the firstName variable is "Ted":
print(args[0]);
copied to clipboard
... then the output of that could would be:
Hello, Ted!
copied to clipboard
Named args in functions
Additionally we can pass a map to the function:
${myFunction({'key':'keyName', 'value':value})}
copied to clipboard
Now, in your function, the args will be passed as such:
[
{"key":"keyName", "value": <<value of the variable from the registry>>}
]
copied to clipboard
This allows function that take multiple, optional, values to be more easily created and called vs having to do something like...
${myFunction(value, null, null, null, '#ff0000')}
copied to clipboard
Complex function calls
This is possible to construct really complex function calls:
${func1(func2(func3()+' text'+var1), func4(1+2))}
copied to clipboard
Built functions
The built in functions are defined below:



Function Name
Example
Args
Description




dynamic
${dynamic('operationVar1', 'operationVar2'...)}
The variable names which contains values convertable into DynamicOperation.
Executes every DynamicOperation passed as args.


for_each
${for_each(items['data']['items'], 'templateName', 'value', 'key')}
The variable containing the items to iterate overThe variable containing the template to use when iterating.Optional: the name of the variable to put the value inOptional: the name of the variable to put the index or key in
Iterates over the list or map defined by the first arg and builds the widget defined in the template / second argument. The value will be placed in either the variable named value or the passed in third argument. Finally, the index or key will be placed in key or the fourth arg's name.


length
${length(myVar)}
The variable or value to return the length from.
Returns the length of the first argument. If the argument is a JSON encoded String, this will first decode it to the native representation. Next, the return value depends on the type of argument. If the arg is a String, a Map, a List, a Set, or an Iterable, the result of calling .length on it will be returned. Otherwise if the arg is an int or a double, the int value of the arg will be returned. Other types will result in an exception.


log
${log('my message', 'info')}
The message to write to the loggerOptional: level to log the message at; defaults to finest
Logs the given message out to the logger using the optional level or finest if not set.


navigate_named
${navigate_named('home', someValue)}
The route nameOptional: an arguments object to provide
Navigates to the named route. The GlobalKey<NavigatorState> must be provided to the registry before this will work.


navigate_pop
${navigate_pop(false)}
Optional: the value to pop with
Pop's the navigator stack. The GlobalKey<NavigatorState> must be provided to the registry before this will work.


noop
${noop()}
n/a
Simple no-arg no-op function that can be used to enable buttons for UI testing.


remove_value
${remove_value('varName')}
The variable name
Removes the variable named in the first argument from the registry.


set_value
${set_value('varName', 'some value')}
The variable nameThe variable value
Sets the value of the variable in the registry.



All the internal functions are added to the registry by default.
It is possible to omit that behavior by using overrideInternalFunctions flag.
To select manually the internal functions it is recommended to use JsonWidgetInternalFunctionsBuilder.
JsonWidgetRegistry(
overrideInternalFunctions: true,
functions: {
...JsonWidgetInternalFunctionsBuilder().withSetValue().build(),
...<String, JsonWidgetFunction>{
'customFunction': ({args, required registry}) {
print("This is a custom registry function.");
},
}
});
copied to clipboard

Creating Custom Widgets #
Creating a custom widget requires first creating a JsonWidgetBuilder for the widget you would like to add.
For example, if you would like to create a new widget that can render a SVG, you would create a SvgBuilder like the following:
import 'package:flutter_svg/flutter_svg.dart';
import 'package:json_dynamic_widget/json_dynamic_widget.dart';

part 'svg_builder.g.dart';

@jsonWidget
abstract class _SvgBuilder extends JsonWidgetBuilder {
const _SvgBuilder({
required super.args,
});

@override
_Svg buildCustom({
ChildWidgetBuilder? childBuilder,
required BuildContext context,
required JsonWidgetData data,
Key? key,
});
}

class _Svg extends StatelessWidget {
const _Svg({
this.asset,
this.color,
this.height,
this.url,
this.width,
}) : assert(asset == null || url == null),
assert(asset != null || url != null);

final String? asset;
final Color? color;
final double? height;
final String? url;
final double? width;

@override
Widget build(BuildContext context) {
return asset != null
? SvgPicture.asset(
asset!,
height: height,
width: width,
)
: SvgPicture.network(
url!,
height: height,
width: width,
);
}
}
copied to clipboard
Next, you will need to run the code generator command to generate the glue / binding code. To run the code generator, execute:
dart run build_runner build --delete-conflicting-outputs
copied to clipboard
Once the code is generated, you can safely use the registry to build the widget from JSON. For this example widget, the following JSON would construct an instance:
{
"type": "svg",
"args": {
"asset": "assets/images/visa.svg",
"color": "#fff",
"height": 40,
"width": 56
}
}
copied to clipboard

Creating Custom Arg Processor #
Custom arg processors are allowing to extend JSON syntax with custom one.
For example let's create the arg processor which will convert "TRUE" and "FALSE" into booleans as a result of JsonWidgetRegistry args processing.
First the ArgProcessor interface has to be implemented.
import 'package:json_dynamic_widget/json_dynamic_widget.dart';

class BooleanStringArgProcessor implements ArgProcessor {
final _matchRegexp = RegExp(r'^TRUE|FALSE$');

@override
bool support(dynamic arg) {
return arg != null && arg is String && _matchRegexp.hasMatch(arg);
}

@override
ProcessedArg process(
JsonWidgetRegistry registry, dynamic arg, Set<String>? listenVariables) {
var resultListenVariables = listenVariables ?? <String>{};
var boolStr = _matchRegexp.firstMatch(arg)!.toString();
return ProcessedArg(
listenVariables: resultListenVariables,
value: boolStr == 'TRUE',
);
}
}
copied to clipboard
Then such a processor has to be placed into JsonWidgetRegistry.
By default ArgProcessors.defaults are used but there is a possibility to change that via
JsonWidgetRegistry.registerArgProcessors.
var registry = JsonWidgetRegistry.instance;

registry.registerArgProcessors(
<ArgProcessor>[BooleanStringArgProcessor()].addAll(ArgProcessors.defaults)
);
copied to clipboard
The arg processors are executed from the first to the last one in the list.
To make sure that BooleanStringArgProcessor will be used the best is to add it as a first element of the list.

License:

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Customer Reviews

There are no reviews.