Flutter Json Widgets

github pages

This goal of this project is to provide a way to create Flutter widgets with JSON.

This includes reading and writing to JSON from classes that represent widgets or other helper classes and enums.

This does not depend on the flutter sdk for the core classes and can be used in places like the server, command line and dart2js.

Online Editor

Documentation

Implementation

This package relies on another package called freezed to generate the classes, enums and especially the unions that make it very helpful for parsing.

While it is possible to match the Flutter SDK api for widget creation in most cases, there are times where this will differ.

Named Constructors

Since the widget class is a sealed union things like ElevatedButton.icon will be ElevatedButtonIcon instead.

The class will still map to the correct widget at runtime.

Functions

Since this is JSON ultimatly, then logic will not work here. However, there are multiple types of intents that can be used with a class Callback and various actions (navigation, messages, empty, ...).

Also planning on expanding it to include form submission and http requests too.

Builder Methods

Since there is no logic things like LayoutBuilder are difficult to achieve. The MaterialApp has a routes property that you can provide a static map to without depending on context.

Supported Widgets

There are a lot, and more coming soon. List of widgets here.

SSR Example

Server (dart)

// ignore_for_file: depend_on_referenced_packages

import 'dart:convert';
import 'dart:async';

import 'package:flutter_json_widgets/flutter_json_widgets.dart';

import 'package:shelf_router/shelf_router.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_cors_headers/shelf_cors_headers.dart';

int _counter = 0;

Future main() async {
  final app = Router();

  const host = 'localhost';
  const port = 8080;
  const url = 'http://$host:$port';

  app.post('/api/counter', (Request request) async {
    final content = await request.readAsString();
    final map = jsonDecode(content) as Map<String, Object?>;
    _counter = map['counter'] as int;
    return Response.ok(
      jsonEncode({'counter': _counter}),
      headers: {
        'Content-Type': 'application/json',
      },
    );
  });

  app.get(
    '/',
    (Request request) => _ui(const MaterialApp(
      debugShowCheckedModeBanner: false,
      initialRoute: '/counter',
      routes: {
        '/counter': NetworkWidget(
          request: NetworkHttpRequest(
            url: '$url/counter',
          ),
        ),
      },
    )),
  );

  app.get(
    '/counter',
    (Request request) => _ui(Scaffold(
      appBar: const AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: const TextStyle.headlineMedium(),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: Callback.networkRequest(
          NetworkHttpRequest(
            url: '$url/api/counter',
            method: 'POST',
            bodyMap: {'counter': _counter + 1},
          ),
          callback: const Callback.reload(),
        ),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    )),
  );

  // Set CORS headers with every request
  final handler = const Pipeline().addMiddleware(corsHeaders()).addHandler(app);

  // ignore: avoid_print
  print('Starting server on $url');
  await io.serve(handler, host, port);
}

Response _ui(Widget widget) {
  const encoder = JsonEncoder.withIndent('  ');
  final jsonString = encoder.convert(widget.toJson());
  return Response.ok(
    jsonString,
    headers: {
      'Content-Type': 'application/json',
    },
  );
}

Client (flutter)

import 'package:flutter/material.dart';
import 'package:flutter_json_widgets/flutter.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
   return FlutterWidget.network(
     url: Uri.parse('http://localhost:8080/'),
    );
  }
}

JSON

Here is an example for the dart API:

import 'package:flutter_json_widgets/material.dart';

class Example {
  int _counter = 0;

  Widget build() {
    return Scaffold(
      appBar: const AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: const TextStyle.headlineMedium(),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: Callback.networkRequest(
          NetworkHttpRequest(
            url: '$url/api/counter',
            method: 'POST',
            bodyMap: {'counter': _counter + 1},
          ),
          callback: const Callback.reload(),
        ),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

And the backing JSON:

{
  "appBar": {
    "automaticallyImplyLeading": true,
    "title": {
      "data": "Flutter Demo Home Page",
      "runtimeType": "text"
    },
    "primary": true,
    "excludeHeaderSemantics": false,
    "toolbarOpacity": 1.0,
    "bottomOpacity": 1.0,
    "forceMaterialTransparency": false,
    "runtimeType": "appBar"
  },
  "body": {
    "child": {
      "mainAxisAlignment": "center",
      "mainAxisSize": "max",
      "crossAxisAlignment": "center",
      "verticalDirection": "down",
      "children": [
        {
          "data": "You have pushed the button this many times:",
          "runtimeType": "text"
        },
        {
          "data": "0",
          "style": {
            "runtimeType": "headlineMedium"
          },
          "runtimeType": "text"
        }
      ],
      "runtimeType": "column"
    },
    "runtimeType": "center"
  },
  "floatingActionButton": {
    "child": {
      "icon": {
        "codePoint": 57415,
        "fontFamily": "MaterialIcons",
        "matchTextDirection": false
      },
      "runtimeType": "icon"
    },
    "tooltip": "Increment",
    "onPressed": {
      "request": {
        "url": "http://localhost:8080/api/counter",
        "headers": {},
        "method": "POST",
        "bodyMap": {
          "counter": 1
        }
      },
      "callback": {
        "runtimeType": "reload"
      },
      "runtimeType": "networkRequest"
    },
    "mini": false,
    "clipBehavior": "none",
    "autofocus": false,
    "isExtended": false,
    "runtimeType": "floatingActionButton"
  },
  "persistentFooterAlignment": {
    "x": 1.0,
    "y": 0.0
  },
  "primary": true,
  "extendBody": false,
  "extendBodyBehindAppBar": false,
  "drawerEnableOpenDragGesture": true,
  "endDrawerEnableOpenDragGesture": true,
  "runtimeType": "scaffold"
}

Troubleshooting

For web you need to pass the flag --no-tree-shake-icons to keep the icons.

Libraries

alignment
Based on the Alignment class in the Flutter SDK.
border_radius
Based on the BorderRadius class in the Flutter SDK.
border_side
Based on the BorderSide class in the Flutter SDK.
box_constraints
Based on the BoxConstraints class in the Flutter SDK.
callback
Since JSON does not support functions, we need to use a custom class to map intents to callbacks.
color
Based on the Color class in the Flutter SDK.
color_filter
Based on the ColorFilter class in the Flutter SDK.
color_scheme
Based on the ColorScheme class in the Flutter SDK.
colors
Based on the Colors class in the Flutter SDK.
curves
Based on the Curve class in the Flutter SDK.
data_cell
Based on the DataCell class in the Flutter SDK.
data_column
Based on the DataColumn class in the Flutter SDK.
data_row
Based on the DataRow class in the Flutter SDK.
decoration
Based on the Decoration class in the Flutter SDK.
decoration_image
Based on the DecorationImage class in the Flutter SDK.
Based on the DropdownMenuItem class in the Flutter SDK.
edge_insets
Based on the EdgeInsets class in the Flutter SDK.
enums
floating_action_button_location
Based on the FloatingActionButtonLocation class in the Flutter SDK.
floating_label_alignment
Based on the FloatingLabelAlignment class in the Flutter SDK.
flutter
Flutter compatible import.
flutter_json_widgets
flutter_widget
A transformer that converts a JSON string into a Flutter widget.
font_feature
Based on the FontFeature class in the Flutter SDK.
font_variation
Based on the FontVariation class in the Flutter SDK.
font_weight
Based on the FontWeight class in the Flutter SDK.
form_data
Since this is JSON we need to store an abstract type for form data.
gradient
Based on the Gradient class in the Flutter SDK.
gradient_transform
Based on the GradientTransform class in the Flutter SDK.
icon_data
Based on the IconData class in the Flutter SDK.
icon_theme_data
Based on the IconThemeData class in the Flutter SDK.
icons
This file is auto generated. Do not edit. See bin/icons.dart for more information. /// Based on the Icons class in the Flutter SDK.
image_provider
Based on the ImageProvider class in the Flutter SDK.
inline_span
Based on the InlineSpan class in the Flutter SDK.
input_decoration
Based on the InputDecoration class in the Flutter SDK.
key
Based on the Key class in the Flutter SDK.
locale
Based on the Locale class in the Flutter SDK.
material_banner
Based on the MaterialBanner class in the Flutter SDK.
material_state_property
Based on the MaterialStateProperty class in the Flutter SDK.
matrix_4
Based on the Matrix4 class in the Flutter SDK.
mouse_cursor
Based on the MouseCursor class in the Flutter SDK.
Based on the NavigationRailDestination class in the Flutter SDK.
network_request
Since this is JSON we need to describe the network requests that will be made.
offset
Based on the Offset class in the Flutter SDK.
paint
Based on the Paint class in the Flutter SDK.
popup_menu_entry
Based on the PopupMenuEntry class in the Flutter SDK.
preferred_size_widget
Based on the PreferredSizeWidget class in the Flutter SDK.
radius
Based on the Radius class in the Flutter SDK.
rect
Based on the Rect class in the Flutter SDK.
scroll_physics
Based on the ScrollPhysics class in the Flutter SDK.
shadow
Based on the Shadow class in the Flutter SDK.
shape_border
Based on the ShapeBorder class in the Flutter SDK.
size
Based on the Size class in the Flutter SDK.
sliver
Class for creating Sliver widgets. The Flutter SDK uses the Widget class but can lead to improper usage.
sliver_child_delegate
Based on the SliverChildDelegate class in the Flutter SDK.
sliver_grid_delegate
Based on the SliverGridDelegate class in the Flutter SDK.
snack_bar
Based on the SnackBar class in the Flutter SDK.
snack_bar_action
Based on the SnackBarAction class in the Flutter SDK.
strut_style
Based on the StrutStyle class in the Flutter SDK.
table_border
Based on the TableBorder class in the Flutter SDK.
table_column_width
Based on the TableColumnWidth class in the Flutter SDK.
table_row
Based on the TableRow class in the Flutter SDK.
text_align_vertical
Based on the TextAlignVertical class in the Flutter SDK.
text_decoration
Based on the TextDecoration class in the Flutter SDK.
text_height_behavior
Based on the TextHeightBehavior class in the Flutter SDK.
text_input_formatter
Based on the TextInputFormatter class in the Flutter SDK.
text_input_type
Based on the TextInputType class in the Flutter SDK.
text_style
Based on the TextStyle class in the Flutter SDK.
text_theme
Based on the TextTheme class in the Flutter SDK.
theme_data
Based on the ThemeData class in the Flutter SDK.
visual_density
Based on the VisualDensity class in the Flutter SDK.
widget
Based on the Widget library in the Flutter SDK.