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

juancastillo0 / json_form / 10909757877

17 Sep 2024 07:29PM UTC coverage: 78.271% (+3.9%) from 74.365%
10909757877

Pull #6

github

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

380 of 453 new or added lines in 27 files covered. (83.89%)

22 existing lines in 5 files now uncovered.

1639 of 2094 relevant lines covered (78.27%)

1.27 hits per line

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

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

13
/// Returns a map of paths keys to functions that return the files selected by the user.
14
typedef FileHandler = Map<String, Future<List<XFile>?> Function()?> Function();
15

16
/// Returns a map of paths keys to functions that receives a map values to strings
17
/// and returns the selected value, or null if none was selected.
18
typedef CustomPickerHandler
19
    = Map<String, Future<Object?> Function(Map<Object?, Object?> data)>
20
        Function();
21

22
typedef CustomValidatorHandler = Map<String, String? Function(Object?)?>
23
    Function();
24

25
/// Builds a form with [jsonSchema] and configurations from [uiSchema] and [uiConfig].
26
/// You may use [controller] for added functionalities.
27
class JsonForm extends StatefulWidget {
28
  /// Builds a form with [jsonSchema] and configurations from [uiSchema] and [uiConfig].
29
  /// You may use [controller] for added functionalities.
30
  const JsonForm({
1✔
31
    super.key,
32
    required this.jsonSchema,
33
    required this.onFormDataSaved,
34
    this.controller,
35
    this.uiSchema,
36
    this.uiConfig,
37
    this.fileHandler,
38
    this.customPickerHandler,
39
    this.customValidatorHandler,
40
  });
41

42
  /// The JSON schema to build the form from
43
  final String jsonSchema;
44

45
  /// Callback function to be called when the form is submitted
46
  final void Function(Object) onFormDataSaved;
47

48
  /// The controller to be used for the form.
49
  /// It can be used to set the initial data, get/set the form data,
50
  /// subscribe to changes, etc.
51
  final JsonFormController? controller;
52

53
  /// The UI schema with input configurations for each field
54
  final String? uiSchema;
55

56
  /// The UI configuration with global styles, texts, builders and other Flutter configurations
57
  final JsonFormSchemaUiConfig? uiConfig;
58

59
  /// The file handler to be used for the form
60
  final FileHandler? fileHandler;
61

62
  /// Custom handlers for dropdown buttons
63
  final CustomPickerHandler? customPickerHandler;
64

65
  /// Validators executed when for each field
66
  final CustomValidatorHandler? customValidatorHandler;
67

68
  @override
1✔
69
  State<JsonForm> createState() => _JsonFormState();
1✔
70
}
71

72
class _JsonFormState extends State<JsonForm> {
73
  late JsonFormController controller;
74
  Schema get mainSchema => controller.mainSchema!;
3✔
75
  GlobalKey<FormState> get _formKey => controller.formKey!;
3✔
76

77
  @override
1✔
78
  void initState() {
79
    super.initState();
1✔
80
    initMainSchema(controllerChanged: true, schemaChanged: true);
1✔
81
  }
82

83
  void initMainSchema({
1✔
84
    required bool controllerChanged,
85
    required bool schemaChanged,
86
  }) {
87
    if (controllerChanged) {
88
      controller = widget.controller ?? JsonFormController(data: {});
5✔
89
      controller.formKey ??= GlobalKey<FormState>();
3✔
90
      if (controller.mainSchema != null &&
2✔
91
          (!schemaChanged || widget.jsonSchema.isEmpty)) {
3✔
92
        return;
93
      }
94
    }
95
    final mainSchema = Schema.fromJson(
1✔
96
      json.decode(widget.jsonSchema) as Map<String, Object?>,
3✔
97
      id: kGenesisIdKey,
98
    );
99
    final map = widget.uiSchema != null
2✔
100
        ? json.decode(widget.uiSchema!) as Map<String, Object?>
3✔
101
        : null;
102
    if (map != null) {
103
      mainSchema.setUiSchema(map, fromOptions: false);
1✔
104
    }
105
    controller.mainSchema = mainSchema;
2✔
106
  }
107

108
  @override
1✔
109
  void didUpdateWidget(covariant JsonForm oldWidget) {
110
    super.didUpdateWidget(oldWidget);
1✔
111
    final controllerChanged = oldWidget.controller != widget.controller;
4✔
112
    final schemaChanged = oldWidget.jsonSchema != widget.jsonSchema ||
4✔
113
        oldWidget.uiSchema != widget.uiSchema;
4✔
114
    if (schemaChanged || controllerChanged) {
115
      initMainSchema(
1✔
116
        controllerChanged: controllerChanged,
117
        schemaChanged: schemaChanged,
118
      );
119
    }
120
  }
121

122
  @override
1✔
123
  Widget build(BuildContext context) {
124
    return WidgetBuilderInherited(
1✔
125
      controller: controller,
1✔
126
      fileHandler: widget.fileHandler,
2✔
127
      customPickerHandler: widget.customPickerHandler,
2✔
128
      customValidatorHandler: widget.customValidatorHandler,
2✔
129
      context: context,
130
      baseConfig: widget.uiConfig,
2✔
131
      child: Builder(
1✔
132
        builder: (context) {
1✔
133
          final widgetBuilderInherited = WidgetBuilderInherited.of(context);
1✔
134
          final uiConfig = widgetBuilderInherited.uiConfig;
1✔
135

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

164
          return SingleChildScrollView(
1✔
165
            key: const Key('JsonForm_scrollView'),
166
            child: uiConfig.formBuilder?.call(_formKey, formChild) ??
1✔
167
                Form(
1✔
168
                  key: _formKey,
1✔
169
                  autovalidateMode: uiConfig.autovalidateMode,
1✔
170
                  child: Padding(
1✔
171
                    padding: const EdgeInsets.all(12.0),
172
                    child: formChild,
173
                  ),
174
                ),
175
          );
176
        },
177
      ),
178
    );
179
  }
180

181
  Widget _buildHeaderTitle(BuildContext context) {
1✔
182
    final uiConfig = WidgetBuilderInherited.of(context).uiConfig;
2✔
183
    final custom = uiConfig.titleAndDescriptionBuilder?.call(mainSchema);
1✔
184
    if (custom != null) return custom;
185

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

212
  //  Form methods
213
  void onSubmit() {
1✔
214
    final data = controller.submit();
2✔
215
    if (data != null) {
216
      widget.onFormDataSaved(data);
3✔
217
    }
218
  }
219
}
220

221
class FormFromSchemaBuilder extends StatelessWidget {
222
  const FormFromSchemaBuilder({
1✔
223
    super.key,
224
    required this.mainSchema,
225
    required this.formValue,
226
    this.schemaObject,
227
  });
228
  final Schema mainSchema;
229
  final JsonFormValue? formValue;
230
  final SchemaObject? schemaObject;
231

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

256
          if (schema is SchemaObject) {
1✔
257
            return ObjectSchemaBuilder(
1✔
258
              mainSchema: mainSchema,
1✔
259
              schemaObject: schema,
260
            );
261
          }
262

263
          return const SizedBox.shrink();
264
        },
265
      ),
266
    );
267
  }
268
}
269

270
class JsonFormKeyPath extends InheritedWidget {
271
  JsonFormKeyPath({
1✔
272
    super.key,
273
    required BuildContext context,
274
    required this.id,
275
    required super.child,
276
  }) : parent = maybeGet(context);
1✔
277

278
  final String id;
279
  final JsonFormKeyPath? parent;
280

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

283
  static String ofPath(BuildContext context, {String id = ''}) {
1✔
284
    return JsonFormKeyPath(
1✔
285
      id: id,
286
      context: context,
287
      child: const SizedBox(),
288
    ).path;
1✔
289
  }
290

291
  static String appendId(String? path, String id) {
1✔
292
    return path == null || path.isEmpty || path == kGenesisIdKey
2✔
293
        ? id
294
        : id.isEmpty
1✔
295
            ? path
296
            : '$path.$id';
1✔
297
  }
298

299
  @override
1✔
300
  bool updateShouldNotify(JsonFormKeyPath oldWidget) {
301
    return id != oldWidget.id;
3✔
302
  }
303

NEW
304
  static JsonFormKeyPath? maybeOf(BuildContext context) =>
×
NEW
305
      context.dependOnInheritedWidgetOfExactType<JsonFormKeyPath>();
×
306

NEW
307
  static JsonFormKeyPath of(BuildContext context) {
×
NEW
308
    final result = maybeOf(context);
×
NEW
309
    assert(result != null, 'No JsonFormKeyPath found in context');
×
310
    return result!;
311
  }
312

313
  static JsonFormKeyPath? maybeGet(BuildContext context) =>
1✔
314
      context.getElementForInheritedWidgetOfExactType<JsonFormKeyPath>()?.widget
2✔
315
          as JsonFormKeyPath?;
316

NEW
317
  static JsonFormKeyPath get(BuildContext context) {
×
318
    final result =
NEW
319
        context.getElementForInheritedWidgetOfExactType<JsonFormKeyPath>();
×
NEW
320
    assert(result != null, 'No JsonFormKeyPath found in context');
×
NEW
321
    return result!.widget as JsonFormKeyPath;
×
322
  }
323
}
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