Share
Flutter Mobile App. Development

Course Outline

Week 1
Week 2
Week 3
Week 4
Week 5
Week 6
Week 7
Week 8
Week 9
Week 10

Form

  • Forms
  • Form Validation


Shared Preference

  • Add the dependency.
  • Save data.
  • Read data.
  • Remove data
Week 11
Week 12
Week 13

SponsoredAdvertise

FORMS AND VALIDATION


This week, you will learn how to add validation to a form that has a single text field using the following steps:


  • Create a Form with a GlobalKey.
  • Add a TextFormField with validation logic.
  • Create a button to validate and submit the form.


1. Create a Form with a GlobalKey


First, create a Form. The Form widget acts as a container for grouping and validating multiple form fields.


When creating the form, provide a GlobalKey. This uniquely identifies the Form, and allows validation of the form in a later step.


// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
  @override
  MyCustomFormState createState() {
    return MyCustomFormState();
  }
}

class MyCustomFormState extends State {
  // Create a global key that uniquely identifies the Form widget
  // and allows validation of the form.
  //
  // Note: This is a `GlobalKey`,
  // not a GlobalKey.
  final _formKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    // Build a Form widget using the _formKey created above.
    return Form(
      key: _formKey,
      child: Column(
        children: [
              // Add TextFormFields and RaisedButton here.
        ]
     )
    );
  }
}


2. Add a TextFormField with validation logic


Although the Form is in place, it doesn’t have a way for users to enter text. That’s the job of a TextFormField. The TextFormField widget renders a material design text field and can display validation errors when they occur.


Validate the input by providing a validator() function to the TextFormField. If the user’s input isn’t valid, the validator function returns a String containing an error message. If there are no errors, the validator must return null.


For this example, create a validator that ensures the TextFormField isn’t empty. If it is empty, return a friendly error message.


TextFormField(
  // The validator receives the text that the user has entered.
  validator: (value) {
    if (value.isEmpty) {
      return 'Please enter some text';
    }
    return null;
  },
);



3. Create a button to validate and submit the form


Now that you have a form with a text field, provide a button that the user can tap to submit the information.


When the user attempts to submit the form, check if the form is valid. If it is, display a success message. If it isn’t (the text field has no content) display the error message.


RaisedButton(
  onPressed: () {
    // Validate returns true if the form is valid, otherwise false.
    if (_formKey.currentState.validate()) {
      // If the form is valid, display a snackbar. In the real world,
      // you'd often call a server or save the information in a database.

      Scaffold
          .of(context)
          .showSnackBar(SnackBar(content: Text('Processing Data')));
    }
  },
  child: Text('Submit'),
);


How does this work?


To validate the form, use the _formKey created in step 1. You can use the _formKey.currentState() method to access the FormState, which is automatically created by Flutter when building a Form.

The FormState class contains the validate() method. When the validate() method is called, it runs the validator() function for each text field in the form. If everything looks good, the validate() method returns true. If any text field contains errors, the validate() method rebuilds the form to display any error messages and returns false.


Example


import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appTitle = 'Form Validation Demo';

return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(appTitle),
),
body: MyCustomForm(),
),
);
}
}

// Create a Form widget.
class MyCustomForm extends StatefulWidget {
@override
MyCustomFormState createState() {
return MyCustomFormState();
}
}

// Create a corresponding State class.
// This class holds data related to the form.
class MyCustomFormState extends State {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey,
// not a GlobalKey.
final _formKey = GlobalKey();

@override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
onPressed: () {
// Validate returns true if the form is valid, or false
// otherwise.
if (_formKey.currentState.validate()) {
// If the form is valid, display a Snackbar.
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('Processing Data')));
}
},
child: Text('Submit'),
),
),
],
),
);
}
}


Image



TextFormField Examples


For Text Input


TextFormField(
controller: nameController,
// The validator receives the text that the user has entered.
validator: (value) {
if (value.isEmpty) {
return 'Please enter your name';
}
return null;
},
decoration: InputDecoration(
prefixIcon: Icon(
Icons.person,
color: Colors.white,
),
hintStyle: TextStyle(color: Colors.white),
filled: true,
fillColor: Colors.black45,
hintText: 'Name',
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(30.0))
),
),


controller


The controller uniquely identifies the TextFormField and the value of the field can be retrieved by using the controller name.


You will have to declare your controller as a global variable so that you can access it when you are processing the form and retrieving the field value.


Example Declarations of the Controller


final emailController = TextEditingController();
final passwordController = TextEditingController();


Retrieving Text From Controller Field


// Getting value from Controller
String email = emailController.text;
String password = passwordController.text;


validator


Validates the form. Returns a String of error message if the validation is false and null if true.


validator: (value) {
if (value.isEmpty) {
return 'Please enter your name';
}
return null; },


decoration


Decorates the input field. The border property gives border to the field. The border property could have a value of OutlineInputBorder which gives an outline to the field. The prefixIcon property give attach an icon to the field. The hintText is the placeholder of the field. Thus a text which is shown in the field to tell user what the field is for and disappears once the user starts typing.


For Email Address


TextFormField(
controller: emailController,
keyboardType: TextInputType.emailAddress,
// The validator receives the text that the user has entered.
validator: (value) {
if (value.isEmpty) {
return 'Please enter your email address';
}
return null;
},
decoration: InputDecoration(
prefixIcon: Icon(
Icons.email,
color: Colors.white,
),
hintStyle: TextStyle(color: Colors.white),
filled: true,
fillColor: Colors.black45,
hintText: 'Email',
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(30.0))
),
),


keyboardType: TextInputType.emailAddress


The keyboardType property indicates the keyboard to show when the field is focused. When the value is set to TextInputType.emailAddress , the keyboard shows the  and .com on the keyboard.


For Password


TextFormField(
obscureText: true,
controller: passwordController,
// The validator receives the text that the user has entered.
validator: (value) {
if (value.isEmpty) {
return 'Please enter your password';
}
return null;
},
decoration: InputDecoration(
filled: true,
prefixIcon: Icon(Icons.lock, color: Colors.white),
hintStyle: TextStyle(color: Colors.white),
fillColor: Colors.black45,
hintText: 'Password',
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(30.0))
),
),


The obscureText with a value set to true, makes the text in the field obscure (characters changes to * )


You'll see practical examples in the subsequent week when we design the login and registration GUI for our social network project.


Shared Preference


In order to know which user has logged in, you will have to save a unique key value on the mobile application which will be used to identify the user using your app.


Shared preference is used to store data on the mobile device. In our social network app, we will use the shared preference feature to save the unique key for the user using the application. Once a user signs up, we create this unique key and store it on the database on the server and save this data on the mobile application.


This unique key will be used to identify the user anytime the user wants to fetch information from the server.


If you have a relatively small collection of key-values to save, you can use the shared_preferences plugin.

Normally, you would have to write native platform integrations for storing data on both iOS and Android. Fortunately, the shared_preferences plugin can be used to persist key-value data on disk. 

The shared preferences plugin wraps NSUserDefaults on iOS and SharedPreferences on Android, providing a persistent store for simple data.


This recipe uses the following steps:


  1. Add the dependency.
  2. Save data.
  3. Read data.
  4. Remove data.


1. Add the dependency


Before starting, add the shared_preferences plugin to the pubspec.yaml file:


dependencies:
  flutter:
    sdk: flutter
  shared_preferences: newest version


2. Save data


To persist data, use the setter methods provided by the SharedPreferences class. Setter methods are available for various primitive types, such as setInt for int data type, setBool for boolean data type, and setString for string data type.


Setter methods do two things: First, synchronously update the key-value pair in-memory. Then, persist the data to disk.


// obtain shared preferences
final prefs = await SharedPreferences.getInstance();

// set value
prefs.setInt('counter', counter);


3. Read data


To read data, use the appropriate getter method provided by the SharedPreferences class. For each setter there is a corresponding getter. For example, you can use the getInt for int data type, getBool for boolean data type, and getString for string data type methods.


final prefs = await SharedPreferences.getInstance();

// Try reading data from the counter key. If it doesn't exist, return 0.
final counter = prefs.getInt('counter') ?? 0;


4. Remove data


To delete data, use the remove() method.


final prefs = await SharedPreferences.getInstance();

prefs.remove('counter');


Example


1. Add the dependency


shared_preferences: ^0.5.6


Add the shared_preferences dependency in the pubspec.yaml file. Your file should look like this:


dependencies:
flutter:
sdk: flutter
shared_preferences: ^0.5.6
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.3


NOTE


Your shared_preferences should be in the same vertical line as the:


cupertino_icons: ^0.1.3


As it has been explained, indenting is very important for pubspec.yaml file otherwise you will get an error.


2. Click on the Pub get


Click on the Pub get after updating the pubspec.yaml file.


3. Run the demo code


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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shared preferences demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Shared preferences demo'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
int _counter = 0;

@override
void initState() {
super.initState();
_loadCounter();
}

//Loading counter value on start
_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0);
});
}

//Incrementing counter after click
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0) + 1;
prefs.setInt('counter', _counter);
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}


Image


NOTE


Before you could use the shared preference, you will have to import the package as in the code above.


import 'package:shared_preferences/shared_preferences.dart';


If android studio still not recognizing the imported package, restart android studio by closing and starting android studio again.


Explanation to the above code


@override
void initState() {
super.initState();
_loadCounter();
}


The initState() method calls the _loadCounter() method when the app starts. You can call this method anything you want to initialize some variables or perform a task before the app loads.


_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0);
});
}


The _loadCounter() is an async. Remember when saving or reading shared preferred, the method should be async.


In the _loadCounter() method the SharedPreferences object is created. The saved counter value is fetched from the disk by calling the getInt method on the shared preference object. If it is net set yet, 0 is returned.

The getting of the saved counter value is done in the setState() method so that the screen can be updated once the value is fetched to update the counter value on the screen.


Anytime the + floating button is clicked, the counter value is fetched from the disk and increased by 1 and the counter value on the disk is updated using the setInt method on the shared preference object in the incrementCounter() method. Remember that it is also async because of the shared preference which awaits for the task to be performed.


The updating of the _counter value is also done in the setState() method in order to update the counter value on the screen.


_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0) + 1;
prefs.setInt('counter', _counter);
});
}


You can close the app after your first testing and open again. You will realize that the counter value fetched is the last counter value that was on the screen. This is because it was saved on the disk by the shared preference.

SponsoredAdvertise