Forms, Input Handling, and Validation

Creating Forms

Flutter provides a Form widget that acts as a container for TextFormField widgets, making it easy to group and manage multiple form fields.

  • Using the Form Widget: Wrap your input fields within a Form widget. Assign a unique GlobalKey<FormState> to the form to manage its state.

final _formKey = GlobalKey<FormState>();

@override
Widget build(BuildContext context) {
  return Form(
    key: _formKey,
    child: Column(
      children: <Widget>[
        TextFormField(
          decoration: InputDecoration(labelText: ‘Enter your username’),
        ),
        // Add more TextFormFields here
      ],
    ),
  );
}

Handling User Input

TextFormField widgets automatically manage the input state. However, you should capture the input when submitting a form.

  • Capturing Input: Use the onSaved property of TextFormField to save input value to a variable when the form is saved.

TextFormField(
  decoration: InputDecoration(labelText: ‘Enter your username’),
  onSaved: (value) {
    // Save the input value to a variable
    username = value;
  },
),

Validating Input

Validation ensures the user correctly enters the required information before processing the form.

  • Using Validators: The validator property of TextFormField allows you to provide validation logic that is automatically invoked when the form is saved.

TextFormField(
  decoration: InputDecoration(labelText: ‘Enter your email’),
  validator: (value) {
    if (value.isEmpty || !value.contains(‘@’)) {
      return ‘Please enter a valid email.’;
    }
    return null; // Return null if the input is valid
  },
),

  • Triggering Validation: Use the validate() method to trigger the validation of all the TextFormField widgets in the form.

ElevatedButton(
  onPressed: () {
    if (_formKey.currentState.validate()) {
      // If the form is valid, display a Snackbar or save form data
      _formKey.currentState.save();
    }
  },
  child: Text(‘Submit’),
),

Local Persistence

Storing data locally on the device allows your app to cache data, save preferences, and more. Flutter supports various methods for local persistence, including shared preferences, file system access, and SQLite databases.

Shared Preferences

Shared Preferences is a key-value store for lightweight data. It’s perfect for storing simple data, such as user settings.

  • Adding the Shared Preferences Package: Include the shared_preferences package in your yaml file and run flutter pub get.
  • Using Shared Preferences:
    • Import the package and use it to read and write preferences.

import ‘package:shared_preferences/shared_preferences.dart’;

Future<void> savePreference(String key, String value) async {
  final SharedPreferences prefs = await SharedPreferences.getInstance();
  prefs.setString(key, value);
}

Future<String> getPreference(String key) async {
  final SharedPreferences prefs = await SharedPreferences.getInstance();
  return prefs.getString(key) ?? ”;
}

File System Access

You can read and write files directly to the device’s file system for more complex data storage needs.

import ‘package:path_provider/path_provider.dart’;
import ‘dart:io’;

Future<String> get _localPath async {
  final directory = await getApplicationDocumentsDirectory();
  return directory.path;
}

Future<File> get _localFile async {
  final path = await _localPath;
  return File(‘$path/data.txt’);
}

Future<File> writeFile(String data) async {
  final file = await _localFile;
  return file.writeAsString(data);
}

Future<String> readFile() async {
  try {
    final file = await _localFile;
    return await file.readAsString();
  } catch (e) {
    return ”;
  }
}

SQLite

SQLite provides a robust solution for structured data storage. With the sqflite package, Flutter apps can create, query, update, and delete data within a local SQLite database.

Setting Up sqflite

1. Add sqflite to your pubspec.yaml:

dependencies:
 sqflite: latest_version
 path_provider: latest_version

Replace latest_versionwith the current version numbers of sqflite and path_provider.

2. Import sqflite and path_provider:

import ‘package:sqflite/sqflite.dart’;
import ‘package:path/path.dart’;
import ‘package:path_provider/path_provider.dart’;

3. Open Database:

Create a function to open the database, creating it if it doesn’t exist, and define its schema.

Future<Database> openDatabase() async {
  final directory = await getApplicationDocumentsDirectory();
  final path = join(directory.path, ‘my_database.db’);
  return await openDatabase(path, version: 1, onCreate: (db, version) async {
    await db.execute(”’
      CREATE TABLE my_table(
        id INTEGER PRIMARY KEY,
        name TEXT
      )
    ”’);
  });
}

CRUD Operations:

1. Create (Insert):

Future<void> insertData(Database db, Map<String, dynamic> data) async {
 await db.insert(‘my_table’, data);
}

2. Read (Query):

Future<List<Map<String, dynamic>>> queryData(Database db) async {
 return await db.query(‘my_table’);
}

3. Update:

Future<void> updateData(Database db, Map<String, dynamic> data) async {
 await db.update(‘my_table’, data, where: ‘id = ?’, whereArgs: [data[‘id’]]);
}

4. Delete:

Future<void> deleteData(Database db, int id) async {
 await db.delete(‘my_table’, where: ‘id = ?’, whereArgs: [id]);
}

This setup guides you through adding SQLite to your Flutter project, opening a database, and performing basic CRUD operations, mirroring the straightforward examples given for shared preferences and file system access.