• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

juancastillo0 / json_form / 14147619926

29 Mar 2025 04:54PM UTC coverage: 81.034% (+0.4%) from 80.605%
14147619926

push

github

juancastillo0
fix example rendering test

1739 of 2146 relevant lines covered (81.03%)

1.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.74
/lib/src/builder/widget_builder.dart
1
import 'dart:convert';
2
import 'dart:developer';
3

4
import 'package:cross_file/cross_file.dart';
5
import 'package:flutter/material.dart';
6
import 'package:json_form/src/builder/array_schema_builder.dart';
7
import 'package:json_form/src/builder/logic/widget_builder_logic.dart';
8
import 'package:json_form/src/builder/object_schema_builder.dart';
9
import 'package:json_form/src/builder/property_schema_builder.dart';
10
import 'package:json_form/src/fields/shared.dart';
11
import 'package:json_form/src/models/models.dart';
12

13
/// For each field, gives a function that returns the files selected by the user.
14
/// The inner function is called when adding a file for input fields with format "data-url".
15
typedef JsonFormFilePickerHandler = Future<List<XFile>?> Function() Function(
16
  JsonFormField<Object?> field,
17
);
18

19
/// For each field, gives a function that receives a map of values (items) to strings (labels)
20
/// and returns the selected value, or null if none was selected.
21
typedef JsonFormSelectPickerHandler
22
    = Future<Object?> Function(Map<Object?, String> options)? Function(
23
  JsonFormField<Object?> field,
24
);
25

26
/// For each field, gives a function that receives the new value and returns an error,
27
/// or null if the validation was successful and no error was found.
28
/// If the error is an empty String, it will be considered an error in validation,
29
/// but no error message will be shown.
30
typedef JsonFormValidatorHandler = String? Function(Object? newValue)? Function(
31
  JsonFormField<Object?> field,
32
);
33

34
/// Builds a form with [jsonSchema] and configurations from [uiSchema] and [uiConfig].
35
/// You may use [controller] for added functionalities.
36
class JsonForm extends StatefulWidget {
37
  /// Builds a form with [jsonSchema] and configurations from [uiSchema] and [uiConfig].
38
  /// You may use [controller] for added functionalities.
39
  const JsonForm({
1✔
40
    super.key,
41
    required this.jsonSchema,
42
    required this.onFormDataSaved,
43
    this.controller,
44
    this.uiSchema,
45
    this.uiConfig,
46
    this.fieldValidator,
47
    this.fieldSelectPicker,
48
    this.fieldFilePicker,
49
  });
50

51
  /// The JSON schema to build the form from
52
  final String jsonSchema;
53

54
  /// Callback function to be called when the form is submitted
55
  final void Function(Object) onFormDataSaved;
56

57
  /// The controller to be used for the form.
58
  /// It can be used to set the initial data, get/set the form data,
59
  /// subscribe to changes, etc.
60
  final JsonFormController? controller;
61

62
  /// The UI schema with input configurations for each field
63
  final String? uiSchema;
64

65
  /// The UI configuration with global styles, texts, builders and other Flutter configurations
66
  final JsonFormUiConfig? uiConfig;
67

68
  /// A custom validator for each field.
69
  ///
70
  /// For each field, gives a function that receives the new value and returns an error,
71
  /// or null if the validation was successful and no error was found.
72
  /// If the error is an empty String, it will be considered an error in validation,
73
  /// but no error message will be shown.
74
  final JsonFormValidatorHandler? fieldValidator;
75

76
  /// A custom select picker for enums and one-of fields.
77
  ///
78
  /// For each field, gives a function that receives a map of values (items) to strings (labels)
79
  /// and returns the selected value, or null if none was selected.
80
  final JsonFormSelectPickerHandler? fieldSelectPicker;
81

82
  /// A file picker, required when using the "data-url" string format .
83
  ///
84
  /// For each field, gives a function that returns the files selected by the user.
85
  /// The inner function is called when adding a file for input fields with format "data-url".
86
  final JsonFormFilePickerHandler? fieldFilePicker;
87

88
  @override
1✔
89
  State<JsonForm> createState() => _JsonFormState();
1✔
90
}
91

92
class _JsonFormState extends State<JsonForm> {
93
  late JsonFormController controller;
94
  late Schema mainSchema;
95
  GlobalKey<FormState> get _formKey => controller.formKey!;
3✔
96

97
  @override
1✔
98
  void initState() {
99
    super.initState();
1✔
100
    initMainSchema(controllerChanged: true, schemaChanged: true);
1✔
101
  }
102

103
  void initMainSchema({
1✔
104
    required bool controllerChanged,
105
    required bool schemaChanged,
106
  }) {
107
    if (controllerChanged) {
108
      controller = widget.controller ?? JsonFormController(initialData: {});
5✔
109
      controller.formKey ??= GlobalKey<FormState>();
3✔
110
      if (controller.mainSchema != null &&
2✔
111
          (!schemaChanged || widget.jsonSchema.isEmpty)) {
3✔
112
        return;
113
      }
114
    }
115
    final mainSchema = Schema.fromJson(
1✔
116
      json.decode(widget.jsonSchema) as Map<String, Object?>,
3✔
117
      id: kGenesisIdKey,
118
    );
119
    final map = widget.uiSchema != null
2✔
120
        ? json.decode(widget.uiSchema!) as Map<String, Object?>
3✔
121
        : null;
122
    if (map != null) {
123
      mainSchema.setUiSchema(map, fromOptions: false);
1✔
124
    }
125
    this.mainSchema = mainSchema;
1✔
126
  }
127

128
  @override
1✔
129
  void didUpdateWidget(covariant JsonForm oldWidget) {
130
    super.didUpdateWidget(oldWidget);
1✔
131
    final controllerChanged = oldWidget.controller != widget.controller;
4✔
132
    final schemaChanged = oldWidget.jsonSchema != widget.jsonSchema ||
4✔
133
        oldWidget.uiSchema != widget.uiSchema;
4✔
134
    if (schemaChanged || controllerChanged) {
135
      initMainSchema(
1✔
136
        controllerChanged: controllerChanged,
137
        schemaChanged: schemaChanged,
138
      );
139
    }
140
  }
141

142
  @override
1✔
143
  Widget build(BuildContext context) {
144
    return WidgetBuilderInherited(
1✔
145
      controller: controller,
1✔
146
      jsonForm: widget,
1✔
147
      context: context,
148
      baseConfig: widget.uiConfig,
2✔
149
      child: Builder(
1✔
150
        builder: (context) {
1✔
151
          final widgetBuilderInherited = WidgetBuilderInherited.of(context);
1✔
152
          final uiConfig = widgetBuilderInherited.uiConfig;
1✔
153

154
          final formChild = Column(
1✔
155
            children: <Widget>[
1✔
156
              if (uiConfig.debugMode)
1✔
157
                TextButton(
×
158
                  onPressed: () {
×
159
                    inspect(mainSchema);
×
160
                  },
161
                  child: const Text('INSPECT'),
162
                ),
163
              _buildHeaderTitle(context),
1✔
164
              FormFromSchemaBuilder(
1✔
165
                mainSchema: mainSchema,
1✔
166
                formValue: null,
167
              ),
168
              uiConfig.submitButtonBuilder?.call(onSubmit) ??
2✔
169
                  Padding(
1✔
170
                    padding: const EdgeInsets.symmetric(vertical: 10),
171
                    child: ElevatedButton(
1✔
172
                      key: JsonFormKeys.submitButton,
173
                      onPressed: onSubmit,
1✔
174
                      child: Text(
1✔
175
                        uiConfig.localizedTexts.submit(),
2✔
176
                      ),
177
                    ),
178
                  ),
179
            ],
180
          );
181

182
          return SingleChildScrollView(
1✔
183
            key: JsonFormKeys.scrollView,
184
            child: uiConfig.formBuilder?.call(_formKey, formChild) ??
1✔
185
                Form(
1✔
186
                  key: _formKey,
1✔
187
                  autovalidateMode: uiConfig.autovalidateMode,
1✔
188
                  child: Padding(
1✔
189
                    padding: const EdgeInsets.all(12.0),
190
                    child: formChild,
191
                  ),
192
                ),
193
          );
194
        },
195
      ),
196
    );
197
  }
198

199
  Widget _buildHeaderTitle(BuildContext context) {
1✔
200
    final uiConfig = WidgetBuilderInherited.of(context).uiConfig;
2✔
201
    final custom = uiConfig.titleAndDescriptionBuilder?.call(mainSchema);
1✔
202
    if (custom != null) return custom;
203
    if (mainSchema.title == null && mainSchema.description == null) {
4✔
204
      return const SizedBox();
205
    }
206
    return Column(
1✔
207
      crossAxisAlignment: CrossAxisAlignment.start,
208
      children: <Widget>[
1✔
209
        if (mainSchema.title != null)
2✔
210
          SizedBox(
1✔
211
            width: double.infinity,
212
            child: Text(
1✔
213
              mainSchema.title!,
2✔
214
              style: uiConfig.title,
1✔
215
              textAlign: uiConfig.titleAlign,
1✔
216
            ),
217
          ),
218
        const Divider(),
1✔
219
        if (mainSchema.description != null)
2✔
220
          SizedBox(
1✔
221
            width: double.infinity,
222
            child: Text(
1✔
223
              mainSchema.description!,
2✔
224
              style: uiConfig.description,
1✔
225
              textAlign: uiConfig.titleAlign,
1✔
226
            ),
227
          ),
228
      ],
229
    );
230
  }
231

232
  //  Form methods
233
  void onSubmit() {
1✔
234
    final data = controller.submit();
2✔
235
    if (data != null) {
236
      widget.onFormDataSaved(data);
3✔
237
    }
238
  }
239
}
240

241
class FormFromSchemaBuilder extends StatelessWidget {
242
  const FormFromSchemaBuilder({
1✔
243
    super.key,
244
    required this.mainSchema,
245
    required this.formValue,
246
    this.schemaObject,
247
  });
248
  final Schema mainSchema;
249
  final JsonFormValue? formValue;
250
  final SchemaObject? schemaObject;
251

252
  @override
1✔
253
  Widget build(BuildContext context) {
254
    final schema = formValue?.schema ?? mainSchema;
3✔
255
    return JsonFormKeyPath(
1✔
256
      context: context,
257
      id: formValue?.id ?? schema.id,
3✔
258
      child: Builder(
1✔
259
        builder: (context) {
1✔
260
          if (schema.uiSchema.hidden) {
2✔
261
            return const SizedBox.shrink();
262
          }
263
          if (schema is SchemaProperty) {
1✔
264
            return PropertySchemaBuilder(
1✔
265
              mainSchema: mainSchema,
1✔
266
              formValue: formValue!,
1✔
267
            );
268
          }
269
          if (schema is SchemaArray) {
1✔
270
            return ArraySchemaBuilder(
1✔
271
              mainSchema: mainSchema,
1✔
272
              schemaArray: schema,
273
            );
274
          }
275

276
          if (schema is SchemaObject) {
1✔
277
            return ObjectSchemaBuilder(
1✔
278
              mainSchema: mainSchema,
1✔
279
              schemaObject: schema,
280
            );
281
          }
282

283
          return const SizedBox.shrink();
284
        },
285
      ),
286
    );
287
  }
288
}
289

290
class JsonFormKeyPath extends InheritedWidget {
291
  JsonFormKeyPath({
1✔
292
    super.key,
293
    required BuildContext context,
294
    required this.id,
295
    required super.child,
296
  }) : parent = maybeGet(context);
1✔
297

298
  final String id;
299
  final JsonFormKeyPath? parent;
300

301
  String get path => appendId(parent?.path, id);
5✔
302

303
  static String getPath(BuildContext context, {String id = ''}) {
1✔
304
    return JsonFormKeyPath(
1✔
305
      id: id,
306
      context: context,
307
      child: const SizedBox(),
308
    ).path;
1✔
309
  }
310

311
  static String appendId(String? path, String id) {
1✔
312
    return path == null || path.isEmpty || path == kGenesisIdKey
2✔
313
        ? id
314
        : id.isEmpty
1✔
315
            ? path
316
            : '$path.$id';
1✔
317
  }
318

319
  @override
1✔
320
  bool updateShouldNotify(JsonFormKeyPath oldWidget) {
321
    return id != oldWidget.id;
3✔
322
  }
323

324
  static JsonFormKeyPath? maybeOf(BuildContext context) =>
×
325
      context.dependOnInheritedWidgetOfExactType<JsonFormKeyPath>();
×
326

327
  static JsonFormKeyPath of(BuildContext context) {
×
328
    final result = maybeOf(context);
×
329
    assert(result != null, 'No JsonFormKeyPath found in context');
×
330
    return result!;
331
  }
332

333
  static JsonFormKeyPath? maybeGet(BuildContext context) =>
1✔
334
      context.getElementForInheritedWidgetOfExactType<JsonFormKeyPath>()?.widget
2✔
335
          as JsonFormKeyPath?;
336

337
  static JsonFormKeyPath get(BuildContext context) {
×
338
    final result =
339
        context.getElementForInheritedWidgetOfExactType<JsonFormKeyPath>();
×
340
    assert(result != null, 'No JsonFormKeyPath found in context');
×
341
    return result!.widget as JsonFormKeyPath;
×
342
  }
343
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc