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

juancastillo0 / json_form / 11535878107

26 Oct 2024 11:55PM UTC coverage: 80.642% (+6.3%) from 74.365%
11535878107

Pull #6

github

web-flow
Merge 39d1345dc into 419da37b9
Pull Request #6: Form value in controller recursive

537 of 640 new or added lines in 27 files covered. (83.91%)

26 existing lines in 6 files now uncovered.

1658 of 2056 relevant lines covered (80.64%)

1.3 hits per line

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

89.66
/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/json_form_schema_style.dart';
12
import 'package:json_form/src/models/models.dart';
13

14
/// Returns a map of paths keys to functions that return the files selected by the user.
15
typedef FileHandler = Future<List<XFile>?> Function() Function(
16
  JsonFormField<Object?> field,
17
);
18

19
/// Returns a map of paths keys to functions that receives a map values to strings
20
/// and returns the selected value, or null if none was selected.
21
typedef CustomPickerHandler
22
    = Future<Object?> Function(Map<Object?, String> options)? Function(
23
  JsonFormField<Object?> field,
24
);
25

26
typedef CustomValidatorHandler = String? Function(dynamic newValue)? Function(
27
  JsonFormField<Object?> field,
28
);
29

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

47
  /// The JSON schema to build the form from
48
  final String jsonSchema;
49

50
  /// Callback function to be called when the form is submitted
51
  final void Function(Object) onFormDataSaved;
52

53
  /// The controller to be used for the form.
54
  /// It can be used to set the initial data, get/set the form data,
55
  /// subscribe to changes, etc.
56
  final JsonFormController? controller;
57

58
  /// The UI schema with input configurations for each field
59
  final String? uiSchema;
60

61
  /// The UI configuration with global styles, texts, builders and other Flutter configurations
62
  final JsonFormUiConfig? uiConfig;
63

64
  /// A custom validator that receives the field and returns an error message
65
  /// or null if the field is valid. If the error is an empty String, it will
66
  /// be considered an error in validation, but no error message will be shown.
67
  final CustomValidatorHandler? fieldValidator;
68

69
  /// A custom picker that receives the field and returns a Future with the selected value.
70
  /// If no Future is returned, the default selector will be shown.
71
  /// If the Future completes with a null value, the default field will be considered as not selected.
72
  final CustomPickerHandler? fieldDropdownPicker;
73

74
  /// A file picker that receives the field and returns a Function that selects the files.
75
  final FileHandler? fieldFilePicker;
76

77
  @override
1✔
78
  State<JsonForm> createState() => _JsonFormState();
1✔
79
}
80

81
class _JsonFormState extends State<JsonForm> {
82
  late JsonFormController controller;
83
  late Schema mainSchema;
84
  GlobalKey<FormState> get _formKey => controller.formKey!;
3✔
85

86
  @override
1✔
87
  void initState() {
88
    super.initState();
1✔
89
    initMainSchema(controllerChanged: true, schemaChanged: true);
1✔
90
  }
91

92
  void initMainSchema({
1✔
93
    required bool controllerChanged,
94
    required bool schemaChanged,
95
  }) {
96
    if (controllerChanged) {
97
      controller = widget.controller ?? JsonFormController(initialData: {});
5✔
98
      controller.formKey ??= GlobalKey<FormState>();
3✔
99
      if (controller.mainSchema != null &&
2✔
100
          (!schemaChanged || widget.jsonSchema.isEmpty)) {
3✔
101
        return;
102
      }
103
    }
104
    final mainSchema = Schema.fromJson(
1✔
105
      json.decode(widget.jsonSchema) as Map<String, Object?>,
3✔
106
      id: kGenesisIdKey,
107
    );
108
    final map = widget.uiSchema != null
2✔
109
        ? json.decode(widget.uiSchema!) as Map<String, Object?>
3✔
110
        : null;
111
    if (map != null) {
112
      mainSchema.setUiSchema(map, fromOptions: false);
1✔
113
    }
114
    this.mainSchema = mainSchema;
1✔
115
  }
116

117
  @override
1✔
118
  void didUpdateWidget(covariant JsonForm oldWidget) {
119
    super.didUpdateWidget(oldWidget);
1✔
120
    final controllerChanged = oldWidget.controller != widget.controller;
4✔
121
    final schemaChanged = oldWidget.jsonSchema != widget.jsonSchema ||
4✔
122
        oldWidget.uiSchema != widget.uiSchema;
4✔
123
    if (schemaChanged || controllerChanged) {
124
      initMainSchema(
1✔
125
        controllerChanged: controllerChanged,
126
        schemaChanged: schemaChanged,
127
      );
128
    }
129
  }
130

131
  @override
1✔
132
  Widget build(BuildContext context) {
133
    return WidgetBuilderInherited(
1✔
134
      controller: controller,
1✔
135
      jsonForm: widget,
1✔
136
      context: context,
137
      baseConfig: widget.uiConfig,
2✔
138
      child: Builder(
1✔
139
        builder: (context) {
1✔
140
          final widgetBuilderInherited = WidgetBuilderInherited.of(context);
1✔
141
          final uiConfig = widgetBuilderInherited.uiConfig;
1✔
142

143
          final formChild = Column(
1✔
144
            children: <Widget>[
1✔
145
              if (uiConfig.debugMode)
1✔
146
                TextButton(
×
147
                  onPressed: () {
×
148
                    inspect(mainSchema);
×
149
                  },
150
                  child: const Text('INSPECT'),
151
                ),
152
              _buildHeaderTitle(context),
1✔
153
              FormFromSchemaBuilder(
1✔
154
                mainSchema: mainSchema,
1✔
155
                formValue: null,
156
              ),
157
              uiConfig.submitButtonBuilder?.call(onSubmit) ??
2✔
158
                  Padding(
1✔
159
                    padding: const EdgeInsets.symmetric(vertical: 10),
160
                    child: ElevatedButton(
1✔
161
                      key: JsonFormKeys.submitButton,
162
                      onPressed: onSubmit,
1✔
163
                      child: Text(
1✔
164
                        uiConfig.localizedTexts.submit(),
2✔
165
                      ),
166
                    ),
167
                  ),
168
            ],
169
          );
170

171
          return SingleChildScrollView(
1✔
172
            key: JsonFormKeys.scrollView,
173
            child: uiConfig.formBuilder?.call(_formKey, formChild) ??
1✔
174
                Form(
1✔
175
                  key: _formKey,
1✔
176
                  autovalidateMode: uiConfig.autovalidateMode,
1✔
177
                  child: Padding(
1✔
178
                    padding: const EdgeInsets.all(12.0),
179
                    child: formChild,
180
                  ),
181
                ),
182
          );
183
        },
184
      ),
185
    );
186
  }
187

188
  Widget _buildHeaderTitle(BuildContext context) {
1✔
189
    final uiConfig = WidgetBuilderInherited.of(context).uiConfig;
2✔
190
    final custom = uiConfig.titleAndDescriptionBuilder?.call(mainSchema);
1✔
191
    if (custom != null) return custom;
192

193
    return Column(
1✔
194
      crossAxisAlignment: CrossAxisAlignment.start,
195
      children: <Widget>[
1✔
196
        if (mainSchema.title != null)
2✔
197
          SizedBox(
1✔
198
            width: double.infinity,
199
            child: Text(
1✔
200
              mainSchema.title!,
2✔
201
              style: uiConfig.title,
1✔
202
              textAlign: uiConfig.titleAlign,
1✔
203
            ),
204
          ),
205
        const Divider(),
1✔
206
        if (mainSchema.description != null)
2✔
207
          SizedBox(
1✔
208
            width: double.infinity,
209
            child: Text(
1✔
210
              mainSchema.description!,
2✔
211
              style: uiConfig.description,
1✔
212
              textAlign: uiConfig.titleAlign,
1✔
213
            ),
214
          ),
215
      ],
216
    );
217
  }
218

219
  //  Form methods
220
  void onSubmit() {
1✔
221
    final data = controller.submit();
2✔
222
    if (data != null) {
223
      widget.onFormDataSaved(data);
3✔
224
    }
225
  }
226
}
227

228
class FormFromSchemaBuilder extends StatelessWidget {
229
  const FormFromSchemaBuilder({
1✔
230
    super.key,
231
    required this.mainSchema,
232
    required this.formValue,
233
    this.schemaObject,
234
  });
235
  final Schema mainSchema;
236
  final JsonFormValue? formValue;
237
  final SchemaObject? schemaObject;
238

239
  @override
1✔
240
  Widget build(BuildContext context) {
241
    final schema = formValue?.schema ?? mainSchema;
3✔
242
    return JsonFormKeyPath(
1✔
243
      context: context,
244
      id: formValue?.id ?? schema.id,
3✔
245
      child: Builder(
1✔
246
        builder: (context) {
1✔
247
          if (schema.uiSchema.hidden) {
2✔
248
            return const SizedBox.shrink();
249
          }
250
          if (schema is SchemaProperty) {
1✔
251
            return PropertySchemaBuilder(
1✔
252
              mainSchema: mainSchema,
1✔
253
              formValue: formValue!,
1✔
254
            );
255
          }
256
          if (schema is SchemaArray) {
1✔
257
            return ArraySchemaBuilder(
1✔
258
              mainSchema: mainSchema,
1✔
259
              schemaArray: schema,
260
            );
261
          }
262

263
          if (schema is SchemaObject) {
1✔
264
            return ObjectSchemaBuilder(
1✔
265
              mainSchema: mainSchema,
1✔
266
              schemaObject: schema,
267
            );
268
          }
269

270
          return const SizedBox.shrink();
271
        },
272
      ),
273
    );
274
  }
275
}
276

277
class JsonFormKeyPath extends InheritedWidget {
278
  JsonFormKeyPath({
1✔
279
    super.key,
280
    required BuildContext context,
281
    required this.id,
282
    required super.child,
283
  }) : parent = maybeGet(context);
1✔
284

285
  final String id;
286
  final JsonFormKeyPath? parent;
287

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

290
  static String getPath(BuildContext context, {String id = ''}) {
1✔
291
    return JsonFormKeyPath(
1✔
292
      id: id,
293
      context: context,
294
      child: const SizedBox(),
295
    ).path;
1✔
296
  }
297

298
  static String appendId(String? path, String id) {
1✔
299
    return path == null || path.isEmpty || path == kGenesisIdKey
2✔
300
        ? id
301
        : id.isEmpty
1✔
302
            ? path
303
            : '$path.$id';
1✔
304
  }
305

306
  @override
1✔
307
  bool updateShouldNotify(JsonFormKeyPath oldWidget) {
308
    return id != oldWidget.id;
3✔
309
  }
310

NEW
311
  static JsonFormKeyPath? maybeOf(BuildContext context) =>
×
NEW
312
      context.dependOnInheritedWidgetOfExactType<JsonFormKeyPath>();
×
313

NEW
314
  static JsonFormKeyPath of(BuildContext context) {
×
NEW
315
    final result = maybeOf(context);
×
NEW
316
    assert(result != null, 'No JsonFormKeyPath found in context');
×
317
    return result!;
318
  }
319

320
  static JsonFormKeyPath? maybeGet(BuildContext context) =>
1✔
321
      context.getElementForInheritedWidgetOfExactType<JsonFormKeyPath>()?.widget
2✔
322
          as JsonFormKeyPath?;
323

NEW
324
  static JsonFormKeyPath get(BuildContext context) {
×
325
    final result =
NEW
326
        context.getElementForInheritedWidgetOfExactType<JsonFormKeyPath>();
×
NEW
327
    assert(result != null, 'No JsonFormKeyPath found in context');
×
NEW
328
    return result!.widget as JsonFormKeyPath;
×
329
  }
330
}
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