To create a Flutter application that includes a form with various input fields and a submit button.
To understand how to create and use custom text fields in a Flutter application and handle user input format.
- Flutter SDK: version 2.0.0 or higher
- IDE: Visual Studio Code (Supported) or android studio (Supported) or IntelliJ IDEA (Supported).
- Operating System: Windows (7 or higher), macOS (10.12 or higher), or Linux (Ubuntu, Debian, Fedora, CentOS, or similar)
-
Create a new Flutter project by running the following command in your terminal:
flutter create my_flutter_app
The command creates a Flutter project directory called
my_flutter_app
that contains a simple demo app that uses Material Components. -
Change to the Flutter project directory.
cd my_flutter_app
-
Open the
lib/main.dart
file in your Flutter project. -
Replace the existing code with the following code snippet:
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runApp(const MainApp()); class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Form Application')), body: Column( children: <Widget>[ Image.asset('images/3d_avatar_21.png', width: 100, height: 100), // Custom text fields for user input const CustomTextField(label: 'First Name'), const CustomTextField(label: 'Last Name'), const CustomTextField(label: 'Email', suffixText: '@mlritm.ac.in'), const CustomTextField( prefixText: '+91 ', label: 'Phone Number', keyboardType: TextInputType.phone, maxLength: 10, ), const Divider(indent: 8, endIndent: 8), // Divider const CustomTextField(label: 'Username'), const CustomTextField(label: 'Password', obscureText: true), const CustomTextField(label: 'Confirm Password', obscureText: true), ElevatedButton( onPressed: () {}, child: const Text('Register'), ), ], ), ), ); } } // Custom text field widget class CustomTextField extends StatelessWidget { final String label; final TextInputType? keyboardType; final bool obscureText; final String? prefixText, suffixText; final int? maxLength; const CustomTextField({ super.key, required this.label, this.keyboardType, this.suffixText, this.prefixText, this.maxLength, this.obscureText = false, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: TextFormField( keyboardType: keyboardType, obscureText: obscureText, inputFormatters: maxLength != null ? [LengthLimitingTextInputFormatter(maxLength)] : null, decoration: InputDecoration( border: const OutlineInputBorder(), labelText: label, suffixText: suffixText, prefixText: prefixText, ), ), ); } }
Save the file.
-
Get the image
3d_avatar
from 3d_avatar_21.png and save it as theimages/3d_avatar_21.png
in your Flutter project. -
Open the
pubspec.yaml
file in your Flutter project and add the following lines afterflutter:
to include the image asset in your project:flutter: # ... assets: - images/3d_avatar_21.png <-- Add this line
Save the file.
-
Run your Flutter project using the following command:
flutter run
Select the appropriate device to run the app.
-
During the app execution, you can use the following commands:
- Enter
r
to hot reload the app and see the changes you made to the code. - Enter
q
to quit the app.
- Enter
exp_7_a_output.mp4
-
Add a constraint validator to the
CustomTextField
widget to validate the user input. For example, validate the phone number field to accept only 10 digits. -
Add a
TextEditingController
to eachCustomTextField
widget and print the user input to the console when the submit button is pressed and also clear the text fields after submission.
Solution 1
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const MainApp());
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Form Application')),
body: const SingleChildScrollView(
// SafeArea widget to avoid UI elements from being hidden by the device's notch, etc.
child: RegisterForm(),
),
),
);
}
}
class RegisterForm extends StatefulWidget {
const RegisterForm({super.key});
@override
State<RegisterForm> createState() => _RegisterFormState();
}
class _RegisterFormState extends State<RegisterForm> {
// About Controllers: https://api.flutter.dev/flutter/widgets/TextEditingController-class.html
final _firstNameController = TextEditingController();
final _lastNameController = TextEditingController();
final _emailController = TextEditingController();
final _phoneController = TextEditingController();
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
@override
void dispose() {
_firstNameController.dispose();
_lastNameController.dispose();
_emailController.dispose();
_phoneController.dispose();
_usernameController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
void _handleSubmit() {
if (kDebugMode) {
print('''
Form Values:
First Name: ${_firstNameController.text}
Last Name: ${_lastNameController.text}
Email: ${_emailController.text}@mlritm.ac.in
Phone: +91 ${_phoneController.text}
Username: ${_usernameController.text}
Password: ${_passwordController.text}
Confirm Password: ${_confirmPasswordController.text}
''');
}
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Image.asset('images/3d_avatar_21.png', width: 100, height: 100),
CustomTextField(controller: _firstNameController, label: 'First Name'),
CustomTextField(controller: _lastNameController, label: 'Last Name'),
CustomTextField(
controller: _emailController,
label: 'Email ID',
suffixText: '@mlritm.ac.in'),
CustomTextField(
controller: _phoneController,
prefixText: '+91 ',
label: 'Mobile Number',
keyboardType: TextInputType.phone,
maxLength: 10,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9*]')),
LengthLimitingTextInputFormatter(10)
],
),
const Divider(indent: 8, endIndent: 8),
CustomTextField(controller: _usernameController, label: 'Username'),
CustomTextField(
controller: _passwordController,
label: 'Password',
obscureText: true,
passwordVisibilityToggle: false),
CustomTextField(
controller: _confirmPasswordController,
label: 'Confirm Password',
obscureText: true,
passwordVisibilityToggle: true),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FilledButton.icon(
onPressed: _handleSubmit,
icon: const Icon(Icons.login),
label: const Text('Register'),
),
],
),
),
],
);
}
}
class CustomTextField extends StatefulWidget {
final String label;
final TextEditingController? controller; // Add controller
final TextInputType? keyboardType;
final bool obscureText;
final String? prefixText, suffixText;
final int? maxLength;
final List<TextInputFormatter>? inputFormatters;
final bool passwordVisibilityToggle;
const CustomTextField({
super.key,
required this.label,
this.controller, // Add controller
this.keyboardType,
this.suffixText,
this.prefixText,
this.maxLength,
this.obscureText = false,
this.passwordVisibilityToggle = false,
this.inputFormatters,
});
@override
State<CustomTextField> createState() => _CustomTextFieldState();
}
class _CustomTextFieldState extends State<CustomTextField> {
bool _obscureText = true;
@override
void initState() {
super.initState();
_obscureText = widget.obscureText;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: TextFormField(
controller: widget.controller, // Add controller
keyboardType: widget.keyboardType,
obscureText: _obscureText,
inputFormatters: widget.inputFormatters,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: widget.label,
suffixText: widget.suffixText,
prefixText: widget.prefixText,
suffixIcon: widget.obscureText && widget.passwordVisibilityToggle
? IconButton(
icon: Icon(
_obscureText
? Icons.visibility_outlined
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
)
: null,
),
),
);
}
}
Solution 2
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const MainApp());
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Form Application')),
body: const SafeArea(
child: SingleChildScrollView(
// SafeArea widget to avoid UI elements from being hidden by the device's notch, etc.
child: RegisterForm(),
),
),
),
);
}
}
class RegisterForm extends StatefulWidget {
const RegisterForm({super.key});
@override
State<RegisterForm> createState() => _RegisterFormState();
}
class _RegisterFormState extends State<RegisterForm> {
/* About TextEditingController:
* https://api.flutter.dev/flutter/widgets/TextEditingController-class.html
*/
final List<TextEditingController> controllers = List.generate(
7,
(_) => TextEditingController(),
);
TextEditingController get _firstNameController => controllers[0];
TextEditingController get _lastNameController => controllers[1];
TextEditingController get _emailController => controllers[2];
TextEditingController get _phoneController => controllers[3];
TextEditingController get _usernameController => controllers[4];
TextEditingController get _passwordController => controllers[5];
TextEditingController get _confirmPasswordController => controllers[6];
@override
void dispose() {
for (var controller in controllers) {
controller.dispose();
}
super.dispose();
}
void _handleSubmit() {
if (kDebugMode) {
print('''
First Name: ${_firstNameController.text}
Last Name: ${_lastNameController.text}
Email: ${_emailController.text}@mlritm.ac.in
Phone: +91 ${_phoneController.text}
Username: ${_usernameController.text}
Password: ${_passwordController.text}
Confirm Password: ${_confirmPasswordController.text}
''');
}
}
void _handleClear() {
for (var controller in controllers) {
controller.clear();
}
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Image.asset('images/3d_avatar_21.png', width: 100, height: 100),
CustomTextField(controller: _firstNameController, label: 'First Name'),
CustomTextField(controller: _lastNameController, label: 'Last Name'),
CustomTextField(
controller: _emailController,
label: 'Email ID',
suffixText: '@mlritm.ac.in'),
CustomTextField(
controller: _phoneController,
prefixText: '+91 ',
label: 'Mobile Number',
keyboardType: TextInputType.phone,
maxLength: 10,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9*]')),
LengthLimitingTextInputFormatter(10)
],
),
const Divider(indent: 8, endIndent: 8),
CustomTextField(controller: _usernameController, label: 'Username'),
CustomTextField(
controller: _passwordController,
label: 'Password',
obscureText: true,
passwordVisibilityToggle: false),
CustomTextField(
controller: _confirmPasswordController,
label: 'Confirm Password',
obscureText: true,
passwordVisibilityToggle: true),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: TextButton(
onPressed: _handleClear, child: const Text('Clear')),
),
FilledButton.icon(
onPressed: _handleSubmit,
icon: const Icon(Icons.login),
label: const Text('Register'),
),
],
),
),
],
);
}
}
class CustomTextField extends StatefulWidget {
final String label;
final TextEditingController? controller; // Add controller
final TextInputType? keyboardType;
final bool obscureText;
final String? prefixText, suffixText;
final int? maxLength;
final List<TextInputFormatter>? inputFormatters;
final bool passwordVisibilityToggle;
const CustomTextField({
super.key,
required this.label,
this.controller, // Add controller
this.keyboardType,
this.suffixText,
this.prefixText,
this.maxLength,
this.obscureText = false,
this.passwordVisibilityToggle = false,
this.inputFormatters,
});
@override
State<CustomTextField> createState() => _CustomTextFieldState();
}
class _CustomTextFieldState extends State<CustomTextField> {
bool _obscureText = true;
@override
void initState() {
super.initState();
_obscureText = widget.obscureText;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: TextFormField(
controller: widget.controller, // Add controller
keyboardType: widget.keyboardType,
obscureText: _obscureText,
inputFormatters: widget.inputFormatters,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: widget.label,
suffixText: widget.suffixText,
prefixText: widget.prefixText,
suffixIcon: widget.obscureText && widget.passwordVisibilityToggle
? IconButton(
icon: Icon(
_obscureText
? Icons.visibility_outlined
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
)
: null,
),
),
);
}
}
Note
The above exercises are optional and can be done as an extension to the current experiment.
By following the above steps, you have successfully created a Flutter application with a form that includes custom text fields and a submit button. This exercise helps in understanding the creation and usage of custom widgets in Flutter.
- Flutter - TextField class
- Flutter - TextFormField
- Flutter - InputDecoration
- Flutter - Form
- Flutter - Image.asset
- Flutter - Image class
- Flutter - LengthLimitingTextInputFormatter
- Flutter - FilteringTextInputFormatter
- Flutter - TextInputType
- Flutter - FilteringTextInputFormatter
- Flutter - TextEditingController