form_ui_builder 0.1.0
form_ui_builder: ^0.1.0 copied to clipboard
A Flutter package that helps you build dynamic and customizable user interfaces quickly and easily.
example/lib/main.dart
import 'dart:convert';
import 'package:example/bloc/main_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:form_ui_builder/form_ui_builder.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize the form_ui_builder package dependencies. buildFormLogicBundle
// fails with a Failure until this has run.
await setupServiceLocator();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'form_ui_builder example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: BlocProvider(
create: (_) => MainBloc(),
child: const MyHomePage(title: 'form_ui_builder example'),
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({required this.title, super.key});
final String title;
@override
Widget build(BuildContext context) {
return BlocConsumer<MainBloc, MainState>(
listener: (context, state) {
if (state.status == MainStatus.failure) {
showSnackBar(context, state.errorMessage);
}
},
builder: (context, state) {
final isLoaded = state.formLogicBundle.parameterMap.isNotEmpty;
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () =>
context.read<MainBloc>().add(const GetParameterEvent()),
child: Text(
isLoaded ? 'Reload schema' : 'Load schema',
),
),
const SizedBox(height: 16.0),
ElevatedButton(
onPressed: isLoaded ? () => _openFullPage(context) : null,
child: const Text('Open as a full page'),
),
const SizedBox(height: 16.0),
ElevatedButton(
onPressed: isLoaded ? () => _openEmbedded(context) : null,
child: const Text('Open embedded (desktop layout)'),
),
const SizedBox(height: 32.0),
_SubmissionPreview(submission: state.submission),
],
),
),
);
},
);
}
/// The ready-made full-page form.
void _openFullPage(BuildContext context) {
final mainBloc = context.read<MainBloc>();
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => BlocProvider(
create: (_) => FormUiBuilderBloc(
initialFormLogicBundle: mainBloc.state.formLogicBundle,
),
child: Builder(
builder: (innerContext) => FormUiBuilderScreen(
title: 'Household survey',
onSubmit: (submission) {
mainBloc.add(FormSubmittedEvent(submission));
Navigator.of(innerContext).pop();
},
),
),
),
),
);
}
/// The chrome-free view, embedded beside the host app's own panel.
void _openEmbedded(BuildContext context) {
final mainBloc = context.read<MainBloc>();
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => BlocProvider(
create: (_) => FormUiBuilderBloc(
initialFormLogicBundle: mainBloc.state.formLogicBundle,
),
child: EmbeddedFormPage(mainBloc: mainBloc),
),
),
);
}
}
/// Shows [FormUiBuilderView] inside a host-owned desktop layout: the form on
/// the left, the host app's own panel and submit button on the right.
class EmbeddedFormPage extends StatelessWidget {
const EmbeddedFormPage({required this.mainBloc, super.key});
final MainBloc mainBloc;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Embedded in the host layout')),
body: Row(
children: [
Expanded(
flex: 2,
child: FormUiBuilderView(
// The host presents validation errors its own way rather than
// letting the package throw up a SnackBar.
onValidationError: (message) => showSnackBar(context, message),
onSubmit: (submission) {
mainBloc.add(FormSubmittedEvent(submission));
Navigator.of(context).pop();
},
),
),
const VerticalDivider(width: 1.0),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Host app panel',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8.0),
const Text(
'The form on the left is FormUiBuilderView — no Scaffold, '
'no AppBar, no submit button of its own.',
),
const Spacer(),
FilledButton(
onPressed: () => context.read<FormUiBuilderBloc>().add(
const SubmitForm(),
),
child: const Text('Submit from the host'),
),
],
),
),
),
],
),
);
}
}
/// Renders the payload the package handed back.
class _SubmissionPreview extends StatelessWidget {
const _SubmissionPreview({required this.submission});
final FormSubmission? submission;
@override
Widget build(BuildContext context) {
final submission = this.submission;
if (submission == null) {
return const Text('No form submitted yet.');
}
const encoder = JsonEncoder.withIndent(' ');
final hiddenCount = submission.fields.where((f) => !f.isVisible).length;
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 520.0, maxHeight: 260.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${submission.values.length} answers keyed by database_param '
'($hiddenCount hidden fields excluded):',
style: Theme.of(context).textTheme.labelLarge,
),
const SizedBox(height: 8.0),
Expanded(
child: SingleChildScrollView(
child: SelectableText(encoder.convert(submission.toJson())),
),
),
],
),
);
}
}