Basics

Forms



Introduction to Forms

Forms are fundamental for any modern mobile or web application.

In Nylo you can use the NyForm class to manage, validate and submit data all in one place.

It's extremely easy and customizable, let's take a look at how to create a form.


Creating a form

First, run the below metro command from your terminal.

dart run nylo_framework:main make:form LoginForm
# or with Metro alias
metro make:form LoginForm

This will create a new form class lib/app/forms/login_form.dart

E.g. The newly created LoginForm

import 'package:nylo_framework/nylo_framework.dart';

class LoginForm extends NyFormData {

  LoginForm({String? name}) : super(name ?? "login");

  // Add your fields here
  @override
  fields() => [
    Field("Email", 
        cast: FormCast.email(),
        validator: FormValidator.email()
    ),
    Field("Password",
        cast: FormCast.password(),
        validator: FormValidator.password()
    ),
  ];
}

Displaying a form

To display a form, you can use the NyForm widget.

import 'package:nylo_framework/nylo_framework.dart';
import '/app/forms/login_form.dart';
...

// Create your form
LoginForm form = LoginForm();

// add the form using the NyForm widget
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ListView(
        shrinkWrap: true,
        children: [
          NyForm(form: form),

          ElevatedButton(child: Text("Submit"),
            onPressed: () {
              form.submit(onSuccess: (data) {
                // data = { "Email": "example@mail.com", "Password": "password" }
              });
            },
         )
        ],
    ),
  );
}

This is all you need to create and display a form in Nylo.

The UI of this page will now contain two fields, Email and Password, and a submit button.

Submitting a form

To submit a form, you can call the submit method on a form.

LoginForm form = LoginForm();

@override
Widget build(BuildContext context) {
    return Scaffold(
        body: ListView(
            shrinkWrap: true,
            children: [
                NyForm(form: form),
            ],
        ),
        floatingActionButton: FloatingActionButton(
            onPressed: () {
                form.submit(onSuccess: (data) {
                    // Do something with the data
                });
            },
            child: Icon(Icons.save),
        ),
    );
}

When you call form.submit(), Nylo will validate the form and if the form is valid, it will call the onSuccess callback with the form data.

That's a quick overview of how to create, display and submit a form in Nylo.

This is just scratching the surface, you can customize your forms even further by adding casts, validation rules, dummy data and global styles.


Creating a form

The easiest way to create a form is with Metro.

You can create a form by running the following command:

dart run nylo_framework:main make:form AdvertForm
# or with Metro alias
metro make:form AdvertForm

This will create a new form class in the lib/app/forms directory.

import 'package:nylo_framework/nylo_framework.dart';

class AdvertForm extends NyFormData {

  AdvertForm({String? name}) : super(name ?? "advert");

  // Add your fields here
  @override
  fields() => [
    Field("Name", 
      validator: FormValidator.rule("not_empty|max:20")
    ),
    Field("Price", 
      cast: FormCast.currency("usd"),
      validator: FormValidator.rule("not_empty")
    ),
    Field("Favourite Color", 
      value: "Blue",
      cast: FormCast.picker(
        options: ["Red", "Green", "Blue", "Yellow"]
      ),
      validator: FormValidator.rule("not_empty")
    ),
  ];
}


Displaying a form

To display a form, you can use the NyForm widget.

Once you have created a form, you can display it anywhere in your application.

AdvertForm form = AdvertForm();

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ListView(
        children: [
            NyForm(form: form),
        ],
    ),
  );
}

// or like this
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ListView(
        children: [
            form.create()
        ],
    ),
  );
}

This will display the fields associated with the form.

You'll need to provide an additional widget to submit the form using form.submit().


Submitting a Form

To submit a form, you can call the submit method on a form.

AdvertForm form = AdvertForm();

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: NyForm(form: form),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        form.submit(onSuccess: (data) {
          // Do something with the data
        });
      },
      child: Icon(Icons.save),
    ),
  );
}

When you call form.submit(), Nylo will validate the form and if the form is valid, it will call the onSuccess callback with the form data.

You can also use the onFailure: (error) {} callback to handle form validation errors.

AdvertForm form = AdvertForm();

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: NyForm(form: form),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        form.submit(
          onSuccess: (data) {
            // Do something with the data
          },
          onFailure: (error) {
            // Do something with the error
          }
        );
      },
      child: Icon(Icons.save),
    ),
  );
}


Setting data

You can set data using the initialData parameter.

CarAdvertForm form = CarAdvertForm();

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ListView(
      children: [
        NyForm(form: form, initialData: {
            "Model": "BMW",
            "Price": 45000,
            "Wheel Size": "18",
        }),
      ],
    )
  );
}

// or like this
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ListView(
      children: [
        form.create(initialData: {
            "Model": "BMW",
            "Price": 45000,
            "Wheel Size": "18",
        }),
      ],
    )
  );
}

If you need to set data after it's been created, use the setData method or setField method.

  • setData will set all the data at once.
  • setField will set the data for a specific field.
CarAdvertForm form = CarAdvertForm();

init() async {
    form.setData({
        "Model": "BMW",
        "Price": 45000,
        "Wheel Size": "18",
    });

    // or by field name
    form.setField("Model", "Mercedes");
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ListView(
      children: [
        NyForm(form: form),

        ElevatedButton(
          onPressed: () {
            form.submit(onSuccess: (data) {
              // Do something with the data
              // data = { "Model": "Mercedes", "Price": 45000, "Wheel Size": "18" }
            });
          },
          child: Text("Submit"),
        ),
      ],
    )
  );
}


Clearing Data

If you need to clear all the data, you can use the clear method.

JobForm form = JobForm();

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ListView(
      children: [
        NyForm(form: form),
        
        ElevatedButton(child: Text("Clear Data"),
          onPressed: () {
            form.clear();
          },
        ),
      ],
    )
  );
}

If you need to clear a specific field, you can use the clearField method.

form.clearField("Name");

Fields

The Field class in Nylo is important because it defines the fields in your form.

Let's imagine we run a social media app and we have a page where users can upload a 'post'.

The data we need to collect is the title, category, description, and hash tags.

class PostForm extends NyFormData {

  PostForm({String? name}) : super(name ?? "post");

  @override
  fields() => [
    Field("Title"),
    Field("Category"),
    Field("Description"),
    Field("Hash Tags"),
  ];
}

The above code will create a form with three fields: Title, Category, Description, and Hash Tags.

We can improve it by adding casts and validation rules.

class PostForm extends NyFormData {

  PostForm({String? name}) : super(name ?? "post");

  @override
  fields() => [
    Field("Title",
        cast: FormCast.capitalizeWords(),
        validate: FormValidator()
                    .notEmpty()
                    .maxLength(50),
    ),
    Field("Category",
        cast: FormCast.picker(
            options: ["Technology", "Fashion", "Food", "Travel"],
        ),
        validate: FormValidator()
                    .contains(["Technology", "Fashion", "Food", "Travel"]),
    ),
    Field("Description",
        cast: FormCast.textArea(),
        validate: FormValidator(message: "Please write a description")
                    .notEmpty(),
    ),
    Field("Hash Tags", 
        cast: FormCast.textArea(textAreaSize: TextAreaSize.md),
        validate: FormValidator.rule("not_empty")
    ),
  ];
}

This new form now has casts and validation rules.

When the form is submitted, Nylo will validate the form and if the form is valid, it will call the onSuccess callback with the form data.

Grouping Fields

If you want to group fields next to each other, you can use a List.

It's easy, here's an example:

class PostForm extends NyFormData {

  PostForm({String? name}) : super(name ?? "post");

  @override
  fields() => [
    [
        Field("Title",
            cast: FormCast.capitalizeWords(),
        ),
        Field("Category",
            cast: FormCast.picker(
                options: ["Technology", "Fashion", "Food", "Travel"],
            ),
        ),
    ],
    Field("Description",
        cast: FormCast.textArea(),
    ),
    Field("Hash Tags", 
        cast: FormCast.textArea(textAreaSize: TextAreaSize.md),
    ),
  ];
}

If you try this, you will see that the Title and Category fields are grouped together.

Behind the scenes, Nylo will create a Row widget to display the fields.

You can also adjust the mainAxisSpacing and crossAxisSpacing using the mainAxisSpacing and crossAxisSpacing parameters on the NyForm widget.


Validation

Nylo is able to validate your form fields using the FormValidator class.

Here's all the available validation rules:

Method Description
FormValidator.email() Validates an email address
FormValidator.password() Validates a password
FormValidator().notEmpty() Validates that the field is not empty
FormValidator().minLength(5) Validates that the field has a minimum length of 5
FormValidator().maxLength(10) Validates that the field has a maximum length of 10
FormValidator().contains(["developer", "manager"]) Validates that the field contains one of the values in the array
FormValidator().numeric() Validates that the field is a number
FormValidator().date() Validates that the field is a date
FormValidator().uppercase() Validates that the field is uppercase
FormValidator().lowercase() Validates that the field is lowercase
FormValidator().regex(r"^[a-zA-Z0-9]*$") Validates that the field matches a regex pattern
FormValidator().dateInPast() Validates that the date is in the past
FormValidator().dateInFuture() Validates that the date is in the future
FormValidator().isTrue() Validates that the value is true
FormValidator().isFalse() Validates that the value is false
FormValidator().dateAgeIsYounger(35) Validates that the date is younger than 35
FormValidator().dateAgeIsOlder(35) Validates that the date is older than 35
FormValidator().zipcodeUs() Validates that the value is a valid US zipcode
FormValidator().postcodeUk() Validates that the value is a valid UK postcode

If you need to create a custom validation rule, you can do so by calling FormValidator.custom like in the example below.

class JobForm extends NyFormData {
  
  @override
  fields() => [
    Field("Name", validation: FormValidator().notEmpty().maxLength(20)),
    Field("Age", validation: FormValidator.rule("not_empty|numeric|min:18")),
    Field("Salary", validation: FormValidator.custom((value) {
      if (value < 10000) {
        return false;
      }
      return true;
    }, message: "Salary must be greater than 10000")),
  ];
}

In the above example, we have defined validation rules for the fields Name, Age and Salary. If the form is invalid, the onFailure callback will be called with the error message.

You can find all the available validation rules here.

Validate on focus change

This is feature is disabled by default. If you want to enable it, you can do so by setting the validateOnFocusChange parameter to true.

NyForm(form: accountForm, validateOnFocusChange: true),

Now, the form will only validate the field when the focus changes.

Custom Error Messages

You can also provide a custom error message for each field.

class JobForm extends NyFormData {

  @override
  fields() => [
    Field("Name", 
        validation: FormValidator(message: "Name is required").notEmpty().maxLength(20)
    ),
    Field("Age", 
        validation: FormValidator.rule("not_empty|numeric|min:25", message: "Age must be a number and greater than 25")
    ),
    Field("Salary", validation: FormValidator.custom((value) {
      if (value < 10000) {
        return false;
      }
      return true;
    }, message: "Salary must be greater than 10000")),
  ];
}

Casts

Casts are used to cast the fields to their respective types.

class JobForm extends NyFormData {

  @override
  fields() => [
    Field("Age", 
        cast: FormCast.number()
    ),
    Field("Salary", 
        cast: FormCast.currency("usd"),
    ),
    Field("Position", 
        cast: FormCast.picker(
            options: ["Developer", "Manager", "Designer", "Tester"]
        ),
    ),
  ];
}

Available Casts

Method Description
FormCast() Default cast
FormCast.email() Cast to email
FormCast.password() Cast to password
FormCast.password(viewable: true) Cast to viewable password
FormCast.currency("usd") Cast to currency (supports multiple currencies)
FormCast.picker() Cast to picker
FormCast.textArea() Cast to textarea
FormCast.textArea(textAreaSize: TextAreaSize.md) Cast to medium textarea
FormCast.capitalizeWords() Cast to capitalize words
FormCast.capitalizeSentences() Cast to capitalize sentences
FormCast.number() Cast to number
FormCast.phoneNumber() Cast to phone number
FormCast.datetime() Cast to datetime
FormCast.uppercase() Cast to uppercase
FormCast.lowercase() Cast to lowercase

You can use any of the above casts in your form.

If you'd like to create your own custom cast, you can do so by creating a new class in config/form_casts.dart.

FormCast.currency - all currencies

The FormCast.currency cast supports multiple currencies, here's a list of all the available currencies:

Currency Code Currency Name
usd US Dollar
eur Euro
gbp British Pound Sterling
jpy Japanese Yen
cny Chinese Yuan
cad Canadian Dollar
aud Australian Dollar
inr Indian Rupee
idr Indonesian Rupiah
sgd Singapore Dollar
myr Malaysian Ringgit
thb Thai Baht
twd New Taiwan Dollar
vnd Vietnamese Dong
zar South African Rand
pkr Pakistani Rupee


Dummy Data

You can add dummy data to your form when you are developing your application.

If your .env file is set to production, the dummy data will be ignored.

Example

APP_ENV="developing" # or "production"
class FitnessLevelForm extends NyFormData {

  @override
  fields() => [
    Field("Name", 
        cast: FormCast.number(),
        dummyData: "John Doe"
    ),
    Field("Weight", 
        dummyData: "75 kg"
    ),
    Field("Height", 
        dummyData: "180 cm"
    ),
    Field("Hash Tags", 
        dummyData: "#fitness, #health, #gym"
    ),
  ];
}

In the above example, the fields Name, Weight, Height, and Hash Tags will have dummy data when the form is displayed.

Note: Remove to update your .env file to APP_ENV="production" to disable dummy data.

Style

Nylo provides two ways to style your form fields.

The first is by defining a global style for all fields in the form. The second is by styling individual fields.

Global Style

Global styles are the easiest way to customize your form fields. You can define the style for any field except for the Picker and DateTime field.

To get started, first navigate to your /app/forms/style/form_style.dart file.

This file will contain a class called FormStyle, you'll be able to define all your global styles here.

To update the TextField's, override the textField method. This function is responsible for styling the TextField's in your form.

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

class FormStyle extends NyFormStyle {

  /// TextField styles for the form
  @override
  FormStyleTextField textField(BuildContext context, Field field) {
    return {
     'default': (NyTextField textField) => textField.copyWith(
        decoration: InputDecoration(
          border: InputBorder.none,
          filled: true,
          fillColor: Colors.blue.shade100,
          labelText: field.name,
        ),
      ),
    };
  }
  ...

In the above example, we have defined a global style for all the TextField's in the form.

Note that the default key is required to define the default style for all the TextField's.

You can also define more styles, like in the example below:

``` dart
import 'package:flutter/material.dart';
import 'package:nylo_framework/nylo_framework.dart';

class FormStyle extends NyFormStyle {

  /// TextField styles for the form
  @override
  FormStyleTextField textField(BuildContext context, Field field) {
    return {
     'default': (NyTextField textField) => textField.copyWith(
        decoration: InputDecoration(
          border: InputBorder.none,
          filled: true,
          fillColor: Colors.blue.shade100,
          labelText: field.name,
        ),
      ),
      'minimal': (NyTextField textField) => textField.copyWith(
        decoration: InputDecoration(
          ...
        ),
      ),
    };
  }
  ...

Now, you can use the minimal style in your form.

class ProfileForm extends NyFormData {
  
  fields() => [
    Field("Name", style: "minimal"),
    Field("Username"),
  ];
}

You can also use the style parameter, this will override the global style for the field.

Nylo contains a compact style that you can use in your form.

class RegisterForm extends NyFormData {

    @override
    fields() => [
        Field("Name", 
            style: "compact"
        ),
        Field("Email", 
            style: "compact"
        ),
        Field("Password", 
            style: "compact"
        ),
        Field("Confirm Password", 
            style: "compact"
        ),
    ];
}

You can also use the style parameter to perform an inline style override.

class RegisterForm extends NyFormData {

    @override
    fields() => [
        Field("Name", 
            style: (NyTextField nyTextField) => nyTextField.copyWith(
                decoration: InputDecoration(
                    labelText: "Name",
                    hintText: "Enter your name",
                ),
            ),
        ),
        Field("Email", 
            style: (NyTextField nyTextField) => nyTextField.copyWith(
                decoration: InputDecoration(
                    labelText: "Email",
                    hintText: "Enter your email",
                ),
            ),
        ),
        Field("Password", 
            style: (NyTextField nyTextField) => nyTextField.copyWith(
                decoration: InputDecoration(
                    labelText: "Password",
                    hintText: "Enter your password",
                ),
            ),
        ),
        Field("Confirm Password", 
            style: (NyTextField nyTextField) => nyTextField.copyWith(
                decoration: InputDecoration(
                    labelText: "Confirm Password",
                    hintText: "Confirm your password",
                ),
            ),
        ),
    ];
}


Pre-built Forms

Nylo comes with pre-built forms that you can use out of the box.

This list will be updated as more forms are added.

NyLoginForm

The NyLoginForm is a pre-built login form that you can use in your application.

Fields

  • Email - FormCast.email()
  • Password - FormCast.password()
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: NyForm.login(name: "login"),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        NyForm.submit("login", onSuccess: (data) {
          // Do something with the data
        });
      },
      child: Icon(Icons.save),
    ),
  );
}


NyForm Parameters

  • key: The key of the form
  • form: The form to display
  • initialData: The initial data for the form
  • crossAxisSpacing: The cross axis spacing (default: 10)
  • mainAxisSpacing: The main axis spacing (default: 10)
  • onChanged: The callback when the form changes
  • validateOnFocusChange: Validate the form on focus change (default: false)
  • locked: Lock the form (default: false)


Listening to Form Changes

You can listen to form changes using the onChanged callback.

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: NyForm(
      form: form,
      onChanged: (data) {
        // Do something with the data
        // data = { "Name": "John Doe", "Age": 25, "Salary": 45000, "Position": "Developer" }
      },
    ),
  );
}


Custom Form Casts

If you want to use custom form casts, you can create them and reuse them in your forms.

First, make sure your AppProvider is set up correctly.

class AppProvider implements NyProvider {
  @override
  boot(Nylo nylo) async {
    ...
    nylo.addFormCasts(formCasts); // Add the form casts
  }

Inside config/form_casts.dart, you can add your custom form casts.

import 'package:nylo_framework/nylo_framework.dart';

final Map<String, dynamic> formCasts = {
  /// Example
  "age_picker": (Field field, Function(dynamic value)? onChanged) {
    return MyCustomField(field, onChanged);
  },
};

Your custom form cast needs to accept the following parameters:

  • Field field: The field to cast
  • Function(dynamic value)? onChanged: The callback when the field changes

Nylo will automatically register the form casts for you.

Now, you can use your custom form cast in your form.

class JobForm extends NyFormData {

  @override
  fields() => [
    Field("Age", 
        cast: "age_picker"
    ),
    Field("Salary", 
        cast: FormCast.currency("usd"),
    ),
    Field("Position", 
        cast: FormCast.picker(
            options: ["Developer", "Manager", "Designer", "Tester"]
        ),
    ),
  ];
}