On this page14 sections
Lecture 006 — Add & Update Records
| Category | 3 — Building the Screens |
| Lecture | 006 of 007 |
| Estimated video length | 20 min |
| Target | Flutter 3.44 · Dart 3.12 · hive 2.2 |
| Level | Beginner |
| Video | Part 2 — UI Setup: Display, Add, Update & Manage Data |
| Source code | GitHub — urraan_hive_final_code |
One form, two jobs. Opened from the + button it adds a record; opened by tapping a row it updates that record. The only difference is whether it receives an existing item.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Prerequisites
Lectures 001–005; TextField and TextEditingController (Essential Widgets).
Connection With Previous Lectures
The dashboard opens ScreenAddUpdateRecord() for add and ScreenAddUpdateRecord(item: item)
for update. Now we build that screen.
Learning Objectives
- Make one screen that works in add and update mode
- Fill the fields from an existing record in
initState - Save with
GeneralBox.addUpdate, reusing the key when updating - Dispose controllers
1. One Screen, Two Modes
class ScreenAddUpdateRecord extends StatefulWidget {
final DataObjects? item;
const ScreenAddUpdateRecord({super.key, this.item});
@override
State<ScreenAddUpdateRecord> createState() => _ScreenAddUpdateRecordState();
}item == null → add. item != null → update. A getter keeps the checks readable:
bool get _isUpdate => widget.item != null;2. Fill the Form for Update
final controllerName = TextEditingController();
final controllerAddress = TextEditingController();
@override
void initState() {
super.initState();
final item = widget.item;
if (item != null) {
controllerName.text = item.name;
controllerAddress.text = item.address;
}
}
@override
void dispose() {
controllerName.dispose();
controllerAddress.dispose();
super.dispose();
}initState runs once, before the first build — the right place to copy values into the
controllers. dispose frees them.
3. Save
void _save() {
final item = DataObjects()
..key = widget.item?.key ?? GeneralBox.getUniqueKey()
..name = controllerName.text.trim()
..address = controllerAddress.text.trim();
if (GeneralBox.addUpdate(item)) {
Navigator.pop(context);
}
}The one important line is widget.item?.key ?? GeneralBox.getUniqueKey(): reuse the old key
when updating, create a new key when adding. Same key → put replaces the record. New key →
put adds one.
Navigator.pop returns to the dashboard, which has already refreshed itself.
4. The Full Screen (Name and Address)
// lib/screen_add_update_record.dart
import 'package:flutter/material.dart';
import 'data_hive/data_objects.dart';
import 'data_hive/general_box.dart';
class ScreenAddUpdateRecord extends StatefulWidget {
final DataObjects? item;
const ScreenAddUpdateRecord({super.key, this.item});
@override
State<ScreenAddUpdateRecord> createState() => _ScreenAddUpdateRecordState();
}
class _ScreenAddUpdateRecordState extends State<ScreenAddUpdateRecord> {
final controllerName = TextEditingController();
final controllerAddress = TextEditingController();
bool get _isUpdate => widget.item != null;
@override
void initState() {
super.initState();
final item = widget.item;
if (item != null) {
controllerName.text = item.name;
controllerAddress.text = item.address;
}
}
@override
void dispose() {
controllerName.dispose();
controllerAddress.dispose();
super.dispose();
}
void _save() {
final item = DataObjects()
..key = widget.item?.key ?? GeneralBox.getUniqueKey()
..name = controllerName.text.trim()
..address = controllerAddress.text.trim();
if (GeneralBox.addUpdate(item)) {
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(_isUpdate ? 'Update Record' : 'Add Record')),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
TextField(
controller: controllerName,
decoration: const InputDecoration(
labelText: 'Name',
hintText: 'Enter name',
),
),
const SizedBox(height: 10),
TextField(
controller: controllerAddress,
decoration: const InputDecoration(
labelText: 'Address',
hintText: 'Enter address',
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _save,
child: Text(_isUpdate ? 'Update Record' : 'Add Record'),
),
],
),
);
}
}Run it: add two people, tap one, change the address, press Update Record — the list shows the new address. Close and reopen the app — everything is still there.
Code Examples
Example 1 — Why the Key Matters
// update: same key → one record, edited
item.key = widget.item!.key;
// bug: new key on update → a second record appears
item.key = GeneralBox.getUniqueKey();Common Beginner Mistakes
1. Generating a new key on update — you get a duplicate instead of an edit.
2. Filling controllers in build — the text resets on every rebuild and the user cannot
type. Use initState.
3. Forgetting dispose — controllers leak memory.
4. Using context after an await without checking mounted — in this screen _save is
synchronous, so it is fine; in Lecture 007 the image picker is async and needs care.
Best Practices
trim()user text before saving.- Reject empty names (see the challenge).
- Keep add and update in one screen; the form is identical.
Mini Challenge
Do not save when the name is empty: show a SnackBar saying Name is required and stay on
the screen.
Practice Questions
- What decides add vs update? 2. Why reuse
widget.item?.key? 3. Where do controllers get their starting text? 4. What doesNavigator.popdo after saving?
Practice Answers
- Whether
widget.itemisnull. putwith the same key replaces the record.initState.- Closes the form and returns to the dashboard.
Key Takeaways
- One screen,
itemoptional: null = add, not null = update. - Update =
putwith the same key. - Fill controllers in
initState, dispose them indispose.