DEV Community

Cover image for How to improve Flutter Forms
Obum
Obum

Posted on

How to improve Flutter Forms

A Form is how we collect input from users in our apps. Forms are unique points of user interactivity. Examples of where you can find forms include:

  • Auth Screens (register or login),
  • Checkout Screens (cart contents),
  • Item Creation/Updating Screens (like todos), etc.

Most apps and websites have a form at some point. Forms are part of what defines a user's experience on our apps. We should improve forms to give users a good experience.

The forms we build are peculiar to a product or feature. There is no one-fits-all rule for forms. What should be a best practice could have exceptions. What we are saying is, wherever you are building a form, there is room for improvement.

Following are tips that you can apply to improve your forms in Flutter.

Table of Contents

1. Overall Form UI
2. Form Validation
3. Password Fields
4. Other Input Types
5. Keyboard Types
6. Autofill
7. Disabling, Loading, and Submitting.
Conclusion

1. Overall Form UI

We can't talk about User Experience without appreciating the User Interface (UI). The UI is the first point of contact with users. It defines the "love at first sight" of our apps.

You want to use/build good UIs that will retain the user. In most cases, you should already have a pre-designed UI (predesigned with Figma or other tools) that you will just implement in Flutter.

You can implement any UI in Flutter. The idea is to implement beautiful UIs, whether you created the UI yourself or it was predesigned by someone else.

The unique combination of box decorations, fonts, colors, icons, sizes, spacings, shadows, etc. all constitute what your form UI looks like. Go for the best UI implementation to give your users a good experience.

Use descriptive labels and placeholders for form fields.


Image Source


Image Source


Image Source


Image Source

2. Form Validation

Don't trust Users. Don't trust Developers.

Yes, our code should never trust anybody. "Trust" here is assuming that the user or the developers will do what we expect. The code we write has to enforce boundaries on the data it manipulates.

Many apps use data. This data has to be checked to ensure the smooth flow of the system.

For example,

  • If you are building a registration form, you want to ensure that all emails or usernames are unique.
  • If you are building a booking/reservation system, you want to ensure that users can only book slots for resources in the future.
  • If you are building an online exam, you want to be sure the user has answered all questions before submitting. And so on.

Data validation involves checking user data before accepting it into the system. We should do data validation on both client-side and server-side code.

On client-side code, we don't trust the users. Or rather, we want to help the users to give us the type of data we expect and to respect constraints.

On server-side code, we don't trust the front-end developers. Or rather we want to be sure that anyone calling our APIs respects the constraints we expect.

Flutter is a client-side tool, so we will focus on client-side data validation. You can do data validation in client code anywhere you take user input in your app (not everything is a form).

Form validation is checking user-inputted data in a form. Validate forms to ensure data constraints.

Consider validating input fields on user interaction and "onSubmit" of the form.

Use descriptive messages to explain to the user was is the issue with each field.


Image Source

Also, show validation messages next to each form field rather than showing a generic error at the top or bottom of the form.


Image Source

To take input text from users in Flutter, use TextFields. To take input text while validating the input in Flutter, use TextFormFields.

TextFormFields accept a validator function parameter that should return null if the content of the input is okay or the error message as a string if the input's content (what the user filled) has issues.

If an input is required, the validator function should equally return some text explaining that that field is required.

Asides from required, you can check for other properties on input fields like mininum or maximum length of text, email validity, only digits, or against any RegExp Pattern.

Apply any validation logic you want inside the validator function of your input fields.

For email validation, you can use Regex yourself or use packages like email_validator on pub.dev.

TextFormFields also accept a maxLength integer to help you enforce maximum length on input field values (without the validator).

They also accept an inputFormatters list of TextInputFormatters for more form validation properties.

Form widgets (like every widget in Flutter) accept a key parameter. You can use the form's key to validate form fields before submitting form data.

Flutter Forms also accept an autovalidateMode enum with values of always, disabled, and onUserInteraction.

To reset the validation state of a Flutter Form, reassign the value of the form's key and empty the contents of inputs through their controllers.

3. Password Fields

Passwords are usually in auth-related screens: either registration or login.

It is good practice to obscure the user's password against preying eyes. However, the user sometimes might want to view the password. We shouldn't deny our users this feature.

As you obscure passwords, please add a button to toggle viewing and obscuring the password. Flutter TextFormFields have an obscureText boolean property. They also permit you to specify the obscuringCharacter.

It is also common practice to double the password fields in registration forms. Doing this helps users to be sure of the password they are creating.

The controller of a TextField tells its content. We can use the controller of a password field in the validator of the confirm password field to know whether passwords match.

Still on passwords, you might want users to use complex passwords. Maybe minimum length of 8 characters, mixing alphabets, numbers, and symbols, not using common names, etc.

You can all such logic in the validator function of your password field. You can also add extra UI validation feedback yourself to tell the user the strength of their passwords.

4. Other Input Types

Text is not the only collectible data. When we want users to select one or more options, we have to use appropriate widgets for those cases.

Use radio buttons when at least one and only one option can be chosen. Use checkboxes when multiple options can be chosen.

DropdownButtons are also usable in forms. Use a dropdown if only one option is to be selected from a list of more than 3 options. If the list is long (more than 10), consider adding "search" functionality for the user to easily locate their choices.

These selector widgets can also be involved in form validation. You might have to add and style extra widgets to show their error messages next to them. Their errors will mainly be "required"-related errors.

In such case, in your submit function, when checking if the form's values are valid with formKey.currentState!.validate(), also check if the values of selector fields have been set.

There is a DropdownButtonFormField widget that presents as a dropdown inside a form field. It takes a decoration property and a validator function, just like TextFields. Use it where necessary.

If you want to show a custom widget as a field, you can wrap it in the InputDecorator widget and it will appear as you want.

You can also have fields for Date and Time. Use pickers for these fields. Flutter has the showDatePicker function to open a Date picker. We also have the showTimePicker function.

If you want more customization, there are packages on pub.dev to suit your needs. Or you can code one yourself (it's harder).

You can have a field that will display the selected date/time. But this field will not be "editable". Like the user can't type text into it. On tap of the field, open a picker, and in the callback, set the value of the field to the chosen date.

You can have validators in the field too. You can validate chosen date or time as you wish in the picker's callback or in your validator function.

5. Keyboard Types

Still in the world of optimizations, we can open specific keyboard types for each TextField in Flutter.

Set the keyboardType on the TextField to any of the available TextInputType and you are good to go. The types include emailAddress, phone, number, etc.

You can set a keyboard type for each field to match the demands of that field.

For example

  • If you set the keyboard type to TextInputType.emailAddress, the mobile operating system keyboard will have a prominent @ symbol for emails.
  • If you set it to TextInputType.phone, the user will see mainly digits on the keyboard.
  • If you set it to TextInputType.multiline, there will be an enter key, and so on.

textInputAction is also a valid setting for text fields in Flutter. It takes any value of the TextInputAction enum like done, search, send, etc.

These will show a corresponding icon or text at the end of the opened keyboard in each mobile operating system. It helps to tell the user what the field does in that context.

6. Autofill

Autofill or autocomplete is when browsers or operating systems fill forms in apps or websites on behalf of users.

If a user opts in for autofill, the system saves user data that has been filled in previous forms. In the next form, the system will help the user fill out the form (autofill).

The user can then edit what the system auto-filled to suit their purpose.

Autofill info could also be gotten from the user's device directly. Like the signed-in email addresses, phone numbers of sim cards, or personal names that the user has set on their phone.

Autofill makes life easy. It makes users spend less time on forms and makes them happy.

We as developers have to make our forms compatible with autofill to help users that opt-in for it. We have to tell the browser or OS what data our form fields are collecting. This will make the system fill the right data into the right field.

TextFields in Flutter accept an autofillHints property. It takes a list of AutoFillHints enums. This enum has values like email, name, newPassword, password, telephoneNumber, username, etc. There are many options. Set the right ones on each field.

Autofill is also helpful with passwords. Many users use password managers. These managers rely on autofill to know when to fill in passwords in apps or websites for the users.

When autofill is properly set, these managers can also suggest new passwords for the user's account on your app (which is what you want).

You can wrap related fields (like email and password fields) in an AutofillGroup widget. This widget tells the system that the fields are linked. This is a further useful feature for password managers.

Set autofill in your fields. The platform where Flutter is running will collaborate with your form and help the user as you expect.

7. Disabling, Loading, and Submitting.

Your form should usually have a button towards its end for submitting all the user-inputted data. This button should call a submit function. The submit function should validate the form before you submit its values.

// in submit function
if (formKey.currentState!.validate()) {
  // submit the form
} else {
  // toast "resolve all errors"
}
Enter fullscreen mode Exit fullscreen mode

If your Flutter code is running on the web, you may want to set the onFieldSubmitted property on the TextFormFields to the submit function. This will make the form submit when the user presses the enter key while on a desktop browser. Web users are used to this on websites so let's give them the same experience.

Most of the time, submitting your form is an asynchronous process. You would be submitting this form to your backend. You want to take care of the user flow after submission. You can navigate the user to another waiting screen or show a loader to tell the user to wait.

If errors occur during submission (maybe network error), you want to reinstate the form with the values the user submitted (or just deactivate the loading), so that the user can retry submitting.

You don't want the user to change any of the values of fields while the form is been submitted. You may want to have an isSubmitting boolean. Then set the enabled property on all form fields to !_isSubmitting (that's when not submitting). This will make disable the fields when _isSubmitting is true.

The submit function should toggle the value of _isSubmitting. It will make it true before it starts and make it false after processing.

// in submit function
if (formKey.currentState!.validate()) {
  setState(() => _isSubmitting = true);
  try {
    // submit form data 
  } catch (e) {
    // toast the error ("e")
  } finally {
    setState(() => _isSubmitting = false);
  }
} else {
  // toast "resolve all errors"
}
Enter fullscreen mode Exit fullscreen mode

You can use Snackbar or any custom toast in pub.dev to toast error feedback from the backend after submission.

Supposedly, if the user's submission was successful, your UI should change. The user shouldn't see the form again. Depending on your app, after successful form submission, you could still toast successful (or a useful message), then navigate the user to another screen or close the form.

Conclusion

Forms are how we collect users' data. Forms should not be difficult for users.

The above are tips to improve your forms in Flutter. You can combine them or use all of them.

Build forms that make life easy for the user. Your objective should be to give your users a nice experience while they are in your app.

Cheers!

Top comments (0)