Watch the video (20 min)
On this page14 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. One Screen, Two Modes
  5. 2. Fill the Form for Update
  6. 3. Save
  7. 4. The Full Screen (Name and Address)
  8. Code Examples
  9. Common Beginner Mistakes
  10. Best Practices
  11. Mini Challenge
  12. Practice Questions
  13. Practice Answers
  14. Key Takeaways

Lecture 006 — Add & Update Records

Category3 — Building the Screens
Lecture006 of 007
Estimated video length20 min
TargetFlutter 3.44 · Dart 3.12 · hive 2.2
LevelBeginner
VideoPart 2 — UI Setup: Display, Add, Update & Manage Data
Source codeGitHub — 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

dart
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:

dart
bool get _isUpdate => widget.item != null;

2. Fill the Form for Update

dart
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

dart
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)

dart
// 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

dart
// 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

  1. What decides add vs update? 2. Why reuse widget.item?.key? 3. Where do controllers get their starting text? 4. What does Navigator.pop do after saving?

Practice Answers

  1. Whether widget.item is null.
  2. put with the same key replaces the record.
  3. initState.
  4. Closes the form and returns to the dashboard.

Key Takeaways

  • One screen, item optional: null = add, not null = update.
  • Update = put with the same key.
  • Fill controllers in initState, dispose them in dispose.

Video lecture

Lecture 006 · 20 min