Easy form building in Flutter with a fluent schema API.

Overview

former - Easy form building in Flutter

Motivation

Formik is one of my favorite React libraries. It is a form library that drastically reduces boilerplate for keeping track of field values, validation, and form submission.

Form building in Flutter suffers from similar issues:

  • Developers have to manually keep track of field values, for example using TextEditingControllers.
  • Validation and error handling requires imperative logic.

This is where former comes in.

Installation

Latest version: 0.2.0-dev.3

NOTE: THIS PACKAGE IS AT A PRE-RELEASE STAGE - API CAN CHANGE DRASTICALLY IN THE FUTURE.

Add former to the dependencies section of your pubspec.yaml:

dependencies:
  # ...your other dependencies
  former: # optionally lock-in a version

and add former_gen to the dev_dependencies section:

dev_dependencies:
  # ...your other dependencies
  former_gen: # optionally lock-in a version

Finally, run flutter pub get.

Features

former provides the following features:

  • Enabling/disabling form globally
  • Declarative form validation
  • Automatic value tracking via Former widgets
  • Easy error handling with FormerError widget.
  • Type-safe access of form.

Usage

Creating the form

former works by inspecting your form class and generating the corresponding code that makes it work with the former API.

First, lets create our form class in my_form.dart:

import 'package:flutter_gen/flutter_gen.dart';

@Formable()
abstract class _MyForm extends FormerForm {
}

A couple of things to note:

  • The form class is abstract and private. This is because some logic has to be mixed in before it is usable by former.
  • The form class extends FormerForm. It interfaces our form class with former so that it can be used by former internals.

The FormerForm requires subclasses to implement the bracket operators. This is not needed in our abstract class because that burden will be handled by former's code generation. We only need to implement the submit method. For example, it can include submitting your form to some API for further processing.

For simplicity's sake, our implementation of submit only returns an empty future value.

Notice that the submit method accepts a BuildContext. This is the same BuildContext used by the Former widget that we will use later to provide this form. It can come in handy when you want to access other Providers in the context. Just make sure that the Providers you want to access are parents of the Former widget that is providing the form.

@Formable()
abstract class _MyForm extends FormerForm {
  @override
  Future<void> submit(BuildContext context) {
    // TODO: implement submit()
    return Future.value();
  }
}

Let's also add some fields to our form:

@Formable()
abstract class _MyForm extends FormerForm {
  String username = '';
  String email = '';

  @override
  Future<void> submit(BuildContext context) {
    // TODO: implement submit()
    return Future.value();
  }
}

Our form class is not usable until we mix in the generated mixin which makes the form "indexable" with the bracket operator, and also contain type information of the fields in the form. Add the following before the class declaration:

class MyForm = _MyForm with _$MyForm;

...and add this:

part 'my_form.g.dart';

to import the generated code.

The Dart analyzer will complain about unrecognized symbols and imports. To fix it, start the code generation via build_runner:

flutter pub run build_runner build

Specifying the requirements

Imagine that our form has the following requirements:

  • the username should be at least 10 characters long, but not longer than 50 characters.
  • the email field, well, should contain a valid email.

Without former, this has to be done in an imperative way by, for example, checking the length of the string. former's super declarative API for specifying requirements makes everything easy and readable. All you have to do is to create the schema class that is generated for you. In my_form.dart,

final schema = MyFormSchema(
  username: StringMust()
    ..hasMinLength(10)
    ..hasMaxLength(50),
  email: StringMust()
    ..beAnEmail(),
);

As you can see, the API is very self-explanatory. Note the use of the cascade operator .. - in Dart, instead of returning this for method chaining, the cascade operator .. is preferred.

Building form controls

former exports various widgets that interacts with the given form. To start, let's first create our form widget:

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

import 'my_form.dart';

class Form extends StatelessWidget {
  @override
  Widget build() {
    return Column(
      children: [
        FormerTextField<MyForm>(field: MyFormField.username),
        FormerTextField<MyForm>(field: MyFormField.email),
        ElevatedButton(
            onPressed: () {
              Former.of<MyForm>(context, listen: false).submit();
            },
            child: Text('Submit form')
        )
      ],
    );
  }
}

Our form contains two text fields that control the username and the email field respectively. The MyFormField class is automatically generated for you, so you don't have to create one yourself.

When the button is clicked, MyForm's submit method is called to submit the form. Beside submitting the form, Former.of(context) gives you access to:

  • the current form with .form. For example, you can access the current value of the username field with Former.of<MyForm>(context).form.username
  • enabling/disabling the form with .isFormEnabled getter/setter. When a form is disabled, all the former controls controlling the form is automatically disabled as well.
  • the error of a given field with .errorOf(field) which returns the error message as a result of a failed validation. It returns an empty string when the field is valid, or when no validation is performed yet.

This is an extremely simplified version of a form to showcase the widgets. Realistically, each Former control should have a label describing what they do. In the future, there may be a widget that attaches a label to a Former control. For now, it has to be done manually.

Wrapping it all up

Finally, all we have to do is to wrap our form widget with the Former widget:

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

import 'my_form.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build() {
    return MaterialApp(
      home: Scaffold(
        body: Former(
          form: () => MyForm(),
          schema: () => schema, // exported from my_form.dart
          child: _MyForm(),
        ),
      ),
    );
  }
}

Source code

The full source code is available in the example folder.

API

Available widgets

The following widgets are available for use with former:

  • FormerTextField
  • FormerCheckbox
  • FormerSwitch
  • FormerSlider

In development:

  • FormerRadio
  • FormerDropdownButton

Schema

The following validators can be used to validate form fields. Each validator has various methods that imposes extra requirements on a given value (called requirement methods).

Every requirement method accepts an optional error message param that is returned when the value does not meet that requirement.

  • StringMust
  • NumberMust
  • BoolMust

Implement the Validator class to create custom validation logic.

You might also like...

Easy Form State Management using BLoC pattern

🔥 Dart and Flutter Package 🔥 Easy Form State Management using BLoC pattern 🔥 Wizard/stepper forms, asynchronous validation, dynamic and conditional fields, submission progress, serialization and more! 🔥

Jan 8, 2023

With cupertino_setting_control you can create a settings page or a simple form very easy.

With cupertino_setting_control you can create a settings page or a simple form very easy.

Flutter Cupertino Setting Control With cupertino_setting_control you can create a settings page or a simple form very easy. Therefore, cupertino_setti

Mar 28, 2022

Bhagavad Gita app using flutter & Bhagavad-Gita-API is A lightweight Node.js based Bhagavad Gita API [An open source rest api on indian Vedic Scripture Shrimad Bhagavad Gita].

Bhagavad Gita app using flutter & Bhagavad-Gita-API is A lightweight Node.js based Bhagavad Gita API [An open source rest api on indian Vedic Scripture Shrimad Bhagavad Gita].

Gita Bhagavad Gita flutter app. Download App - Playstore Web Application About Bhagavad Gita app using flutter & Bhagavad-Gita-API is A lightweight No

Apr 5, 2022

Beautiful Weather App using API with support for dark mode. Created by Jakub Sobański ( API ) and Martin Gogołowicz (UI, API help)

Beautiful Weather App using API with support for dark mode. Created by Jakub Sobański ( API ) and Martin Gogołowicz (UI, API help)

Flutter Weather App using API with darkmode support Flutter 2.8.1 Null Safety Beautiful Weather App using https://github.com/MonsieurZbanowanYY/Weathe

Nov 29, 2022

A flutter widget to indicate loading progress. Easy to use, easy to extend

💙 👾 💫 A flutter widget to indicate loading progress. Easy to use, easy to extend

May 30, 2022

Music-App-Flutter - This is a flutter app which has some songs displayed in the form of a list and user can play any of them by clicking on the name of the song.

Music-App-Flutter - This is a flutter app which has some songs displayed in the form of a list and user can play any of them by clicking on the name of the song.

music_player_app A music player app made by me in flutter About the App This is a music player which i made to play audio files which we have passed i

Apr 1, 2021

Flutter Insert, Update, Delete and add form view dynamic

salesapp Insert, Update, Delete and form view add dynamic. Getting Started This project is a starting point for a Flutter application. A few resources

Dec 22, 2021

CRUD Table Flutter consists of a Lazy loading function, resizable columns, and integrated CRUD Form.

CRUD Table Flutter consists of a Lazy loading function, resizable columns, and integrated CRUD Form.

CRUD Table Flutter CRUD Table Flutter is a package for crating CURD-UI for your entity/object/class easily. It consists of a Lazy loading function, re

Dec 31, 2022

Flutter page add multiple form in screen

Flutter page add multiple form in screen

Flutter Multipage Form A dynamic multi-page form implementation in Flutter Getting Started Clone this repository git clone [email protected]:putraxor/flu

Sep 12, 2022
Comments
  • Could not find the correct Provider<FormerProvider<FormerForm>> above the selector <FormerProvider<FormerForm>, String> Widget

    Could not find the correct Provider> above the selector , String> Widget

    This error happens to me when I try to add Former to my LoginPage. Could not find the correct Provider<FormerProvider<FormerForm>> above the selector <FormerProvider<FormerForm>, String> Widget

    Source code of my LoginPage below.

    login_page.dart:

    import 'package:flutter/material.dart';
    import 'package:flutter_i18n/flutter_i18n.dart';
    import 'package:former/former.dart';
    import 'package:learning_reddit_clone/pages/login_page/form_login.dart';
    import 'package:learning_reddit_clone/pages/login_page/login_form.dart';
    import 'package:learning_reddit_clone/widgets/header.dart';
    
    class LoginPage extends StatelessWidget {
      const LoginPage({
        Key? key,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: LayoutBuilder(
            builder: (context, constraints) {
              return Container(
                margin: EdgeInsets.symmetric(horizontal: constraints.maxWidth / 10),
                child: SingleChildScrollView(
                  child: SizedBox(
                    height: constraints.maxHeight,
                    child: Column(
                      children: [
                        Header(
                          title: FlutterI18n.translate(context, "pages.login.title"),
                        ),
                        Container(
                          child: Former(
                            form: () => LoginForm(),
                            schema: () => loginFormSchema,
                            child: FormLogin(),
                          ),
                        )
                      ],
                    ),
                  ),
                ),
              );
            },
          ),
        );
      }
    }
    

    login_form.dart:

    import 'package:flutter/cupertino.dart';
    import 'package:former/validators.dart';
    import 'package:former_gen/former_gen.dart';
    import 'package:former/former.dart';
    
    part 'login_form.g.dart';
    
    class LoginForm = _LoginForm with _$LoginForm;
    
    @Formable()
    abstract class _LoginForm extends FormerForm {
      String email = '';
      String password = '';
    
      @override
      Future<void> submit(BuildContext context) {
        return Future.value();
      }
    }
    
    final loginFormSchema = LoginFormSchema(
      email: StringMust()..beAnEmail(),
      password: StringMust()..exist(),
    );
    

    form_login.dart:

    import 'package:flutter/material.dart';
    import 'package:flutter_i18n/flutter_i18n.dart';
    import 'package:former/former.dart';
    import 'package:learning_reddit_clone/pages/login_page/login_form.dart';
    
    class FormLogin extends StatefulWidget {
      const FormLogin({Key? key}) : super(key: key);
    
      @override
      _FormLoginState createState() => _FormLoginState();
    }
    
    class _FormLoginState extends State<FormLogin> {
      @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            FormerTextField<LoginForm>(
              field: LoginFormField.email,
              keyboardType: TextInputType.emailAddress,
            ),
            FormerError(
              field: LoginFormField.email,
            ),
            FormerTextField<LoginForm>(
              field: LoginFormField.password,
              keyboardType: TextInputType.visiblePassword,
            ),
            FormerError(
              field: LoginFormField.password,
            ),
            ElevatedButton(
              onPressed: () => {},
              child: Text(
                FlutterI18n.translate(context, "pages.login.title"),
              ),
            ),
          ],
        );
      }
    }
    

    What can i do?

    opened by PiotrekPKP 2
  • Make provider error more descriptive

    Make provider error more descriptive

    Former widgets should show a helpful error message when the type of the form the widgets are consuming is not passed. For example:

    FormerTextField()
    

    should throw an AssertionError with a helpful error message:

    You must pass in the type of your form that this Former widget is consuming, so that the widget can locate and obtain the correct form.
    Example:
    
    FormerTextField<MyForm>(
                   ^^^^^^^^
                   Pass in the type of your form here.
    
      ...other params,
    ),
    
    This assertion is made by: FormerTextField.
    

    Closes #3

    cc @PiotrekPKP

    opened by kennethnym 0
  • Make error messages more descriptive

    Make error messages more descriptive

    Make error messages more descriptive, making it easier to debug.

    Example: #2. The error message can suggest that the error may be due to missing generic type (which I think is a pretty common mistake).

    cc @PiotrekPKP

    enhancement good first issue 
    opened by kennethnym 0
Releases(v0.2.0)
  • v0.2.0(Aug 29, 2021)

  • v0.2.0-rc.1(Jun 3, 2021)

    TIL beta releases isn't considered later than dev releases on pub.dev. Since there will unlikely be breaking API changes in the near future, I decided to do an RC release.

    Source code(tar.gz)
    Source code(zip)
  • v0.2.0-beta.1(May 30, 2021)

    After a month of internal testing (i.e. testing in my own project 😄 ), I decided to do a beta release. Note that this is a pre-release version, and things can change drastically from version to version.

    Nothing is changed from v0.2.0-dev.4 aside from more detailed docstring and new files for the doc site. For the full changelog since the initial release, refer to CHANGELOG.md.

    Any feedback is welcomed! 😄

    Source code(tar.gz)
    Source code(zip)
Owner
Kenneth
Self-taught developer for 6 years. Not quite good at it yet but I'll keep learning :)
Kenneth
A fluent API for generating valid Dart source code

A fluent, builder-based library for generating valid Dart code. Usage code_builder has a narrow and user-friendly API. See the example and test folder

Dart 330 Jan 7, 2023
Flutter form fields designed to take much of the burden of form-related coding off the programmer's back — masks, validations, keyboard type, etc.

well_formed Contents Overview Getting Started Demo application References Overview Well-Formed Widget Fields - Well-Formed - is a collection of Flutte

Dartoos 7 Nov 2, 2022
A Flutter package that provides a dropdown form field using a dropdown button inside a form field.

Dropdown form field A dropdown form field using a dropdown button inside a form field. Demo Features Can be used as regular form field. Simple to impl

Carlos Eugenio Torres 72 Jan 1, 2023
Form builder image picker - Form builder image picker for flutter

form_builder_image_picker Field for picking image(s) from Gallery or Camera for

Ferri Sutanto 0 Jan 28, 2022
Jannis 0 Jan 29, 2022
User auth form - Signup and signin user auth form with ability to stay signed in and have an option to signout.

user_auth_form SIgnup and signin user authentification form Getting Started This project is a starting point for a Flutter application. A few resource

null 0 Jan 6, 2022
Implements Microsoft's Fluent Design System in Flutter.

fluent_ui Design beautiful native windows apps using Flutter Unofficial implementation of Fluent UI for Flutter. It's written based on the official do

Bruno D'Luka 1.8k Dec 29, 2022
Launcher for the reboot project written with Dart(Flutter) and Fluent UI compliant

reboot_launcher Launcher for project reboot Getting Started This project is a starting point for a Flutter application. A few resources to get you sta

Alessandro Autiero 3 Oct 22, 2022
Fluent System Icons are a collection of familiar, friendly and modern icons from Microsoft.

Fluent UI System Icons Fluent UI System Icons are a collection of familiar, friendly and modern icons from Microsoft. Icon List View the full list of

Microsoft 4.3k Dec 29, 2022
ANSI escape sequences and styling micro-library written in fluent/modern Dart.

neoansi ANSI escape sequences and styling micro-library written in fluent/modern Dart. This library provides minimal ANSI escape sequences and helpers

Neo Dart 8 Oct 31, 2022