Flutter & Shared Preferences: Storing Temporary Form Data
Developing applications often involves collecting user input through forms. A common challenge arises when users navigate away from a form or the app unexpectedly closes before submission: the entered data is lost. For critical input or lengthy forms, this can lead to a frustrating user experience. This article explores how to leverage Flutter's shared_preferences package to temporarily store form data, ensuring a seamless experience by allowing users to resume where they left off.
The Need for Temporary Form Data Storage
Consider a multi-step registration form or a complex settings page. If the user accidentally closes the app or switches to another application, all their progress is typically wiped clean. While a backend database is the ultimate destination for submitted data, for unsaved, in-progress data, a lightweight client-side storage solution is ideal. This is where shared_preferences shines.
Introducing shared_preferences
The shared_preferences package in Flutter provides a persistent, lightweight key-value store. It's essentially a wrapper around platform-specific storage mechanisms:
- iOS:
NSUserDefaults - Android:
SharedPreferences - Web:
localStorage
It's perfect for storing simple data types like strings, integers, booleans, doubles, and lists of strings. Crucially, it's not designed for large, complex datasets or relational data; for those, solutions like SQLite (via sqflite) or Hive are more appropriate. However, for temporary form data or user preferences, it's an excellent choice.
Setting Up shared_preferences
First, you need to add the package to your Flutter project's pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.2.0 # Use the latest stable version
After adding the dependency, run flutter pub get in your terminal to fetch the package.
Saving Form Data Temporarily
To save data, you first need to obtain an instance of SharedPreferences and then use its various setter methods based on the data type. Let's imagine a simple form collecting a username and email.
import 'package:shared_preferences/shared_preferences.dart';
Future saveTemporaryFormData(String userName, String userEmail) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('tempUserName', userName);
await prefs.setString('tempUserEmail', userEmail);
print('Temporary form data saved successfully!');
}
In this example, 'tempUserName' and 'tempUserEmail' are the keys used to identify your data. It's good practice to use descriptive keys, especially when dealing with temporary data to avoid conflicts with permanent preferences.
Retrieving Stored Form Data
When the user revisits the form, you'll want to pre-fill the fields with the temporarily saved data. Retrieval is just as straightforward:
import 'package:shared_preferences/shared_preferences.dart';
Future
Notice that getString() returns a nullable String?. This is because the key might not exist (e.g., it's the first time the user opens the form, or the data has been cleared), so you must handle potential null values.
Clearing Temporary Data
Once the form data is successfully submitted to your backend, or if the user explicitly cancels, you'll likely want to remove the temporary data. This prevents stale information from reappearing. You can remove individual keys or clear all preferences:
import 'package:shared_preferences/shared_preferences.dart';
Future clearTemporaryFormData() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('tempUserName');
await prefs.remove('tempUserEmail');
// Or to clear all stored preferences:
// await prefs.clear();
print('Temporary form data cleared!');
}
Using remove(key) is generally preferred for temporary form data to avoid inadvertently deleting other user preferences or settings stored with shared_preferences.
Integrating with a Flutter Form Widget
Let's put it all together in a simple Flutter widget. We'll use a StatefulWidget to manage the form state and TextEditingControllers to interact with the text fields.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Form Data Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const FormPage(),
);
}
}
class FormPage extends StatefulWidget {
const FormPage({super.key});
@override
State createState() => _FormPageState();
}
class _FormPageState extends State {
final TextEditingController _nameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
@override
void initState() {
super.initState();
_loadFormData();
}
@override
void dispose() {
_saveFormData(); // Save data automatically when the widget is disposed
_nameController.dispose();
_emailController.dispose();
super.dispose();
}
Future _loadFormData() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_nameController.text = prefs.getString('tempUserName') ?? '';
_emailController.text = prefs.getString('tempUserEmail') ?? '';
});
}
Future _saveFormData() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('tempUserName', _nameController.text);
await prefs.setString('tempUserEmail', _emailController.text);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Form data saved temporarily!')),
);
}
Future _submitFormData() async {
// Simulate API call or database save
await Future.delayed(const Duration(seconds: 1));
print('Submitting Name: ${_nameController.text}, Email: ${_emailController.text}');
// After successful submission, clear temporary data
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('tempUserName');
await prefs.remove('tempUserEmail');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Form submitted and temporary data cleared!')),
);
// Optionally clear controllers after submission
_nameController.clear();
_emailController.clear();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Temporary Form Data'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 20),
TextField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: _submitFormData,
child: const Text('Submit Form'),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: _saveFormData,
child: const Text('Save Form Temporarily'),
),
],
),
),
);
}
}
In this example:
_loadFormDatais called ininitStateto pre-fill the form when it loads._saveFormDatais called indispose, ensuring that data is saved whenever the form widget is removed from the widget tree (e.g., user navigates back).- A "Submit Form" button triggers
_submitFormData, which simulates submission and then clears the temporary data. - An explicit "Save Form Temporarily" button is also provided for users who want to manually save progress without leaving the page.
Conclusion
shared_preferences offers a simple, efficient, and robust way to manage temporary form data in Flutter applications. By implementing this pattern, you can significantly enhance the user experience, preventing data loss and allowing users to seamlessly continue their input across sessions or navigation changes. While not a replacement for a full database solution, for lightweight, temporary data storage, it's an indispensable tool in any Flutter developer's arsenal.