skip to Main Content

for ease, I’ve created a DartPad for you to try out.

I would like a simple list view with two text fields where users can add or delete items. The entered data is stored as a Map.

When I click on the ‘Add New’ button, it should create a new list tile. However, when I attempt to delete any item, it seems to delete a different list item. Another issue is that when I add a new item for the second time, the first item I created gets overridden with a blank text field. Could you please check this?

DART PAD

This is the code

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Shift List'),
        ),
        body: ShiftList(),
      ),
    );
  }
}

class ShiftList extends StatefulWidget {
  @override
  _ShiftListState createState() => _ShiftListState();
}

class _ShiftListState extends State<ShiftList> {
  Map<String, String> shifts = {
    '1': 'Morning',
    '2': 'Afternoon',
    '3': 'Night',
  };

  void addShift() {
    setState(() {
      String newKey = '';
      shifts[newKey] = '';
    });
  }

  void deleteShift(String key) {
    setState(() {
      shifts.remove(key);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: ListView.builder(
            itemCount: shifts.keys.length,
            itemBuilder: (context, index) {
              String key = shifts.keys.elementAt(index);
              String value = shifts[key]!;

              final keyController = TextEditingController(text: key);
              final valueController = TextEditingController(text: value);

              return ListTile(
                title: Row(
                  children: [
                    Expanded(
                      child: TextFormField(
                        controller: keyController,
                      ),
                    ),
                    SizedBox(width: 10),
                    Expanded(
                      child: TextFormField(
                        controller: valueController,
                      ),
                    ),
                  ],
                ),
                trailing: IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () {
                    deleteShift(keyController.text);
                  },
                ),
              );
            },
          ),
        ),
        ElevatedButton(
          onPressed: () {
            addShift();
          },
          child: Text('Add Shift'),
        ),
      ],
    );
  }
}

2

Answers


  1. The Issue


    The main issue here is the code does not have the capability to transform/change the value assigned to the blank string key into a non-blank string.

    Every time you call void addShift you are assuming that the last time you called it the "temporary" blank-string entry content/data was already move/update to something like shifts['4'] = ''; (4 here because you start with 3 items) BUT your code never move/update the blank string entry.

    The Solution


    To resolve this issue you would need to extend the TextEditingController keyController to being able to perform a a key swap on shifts:

    // Inside of your ListView.builder's itemBuilder
    final keyController = TextEditingController(text: key);
    final valueController = TextEditingController(text: value);
    if (key == "") {
      keyController.addListener(() {
        shifts[keyController.text] = "${valueController.text}";
      });
      valueController.addListener(() {
        shifts[keyController.text] = "${valueController.text}";
      });
    }
    
    

    That will fix the insertion issue and the deletion issue as well.

    Notes


    I would advice you to change shifts from Map<String, String> to List<Set<String>>. The List<>will automatically make your code avoid this extra work of swapping data between "indexes" with the.add` function.

    If you’re worried about duplicated "number labels" for the shifts you might as well change to List<String> and use the built-in index. To rearrange the indexes you would replace ListView with ReorderableListView.

    https://api.flutter.dev/flutter/material/ReorderableListView-class.html

    Login or Signup to reply.
  2. The issue lies in your addShift() function. You are creating an empty key each time with an empty value but you are not updating the hashmap with the new values from the textControllers. Since the map cannot have duplicate keys, whenever u press the add button again, it sees that the previously added key was empty as well so it will just override it as you are again creating just an empty key again instead of creating a unique key. On your UI you are adding the values to your tile widget but the inside the map it has that same empty entry.

    You can do a few things to fix this.

    1. Either you can generate keys in order whenever a tile is created so you can first pass them and just update the values.

    2. Create a popup whenever u press to add a button that takes the key and actual value, and then update your hashmap with those so the ListView builder can update and show it. You should not create unnecessary empty entries in the hashmap when the user has not finished adding the data first.

    Something like this:

    //Get values from the popup and then pass them to the function to update the map
      void addShift(String key, String value) {
        setState(() {
          shifts[key] = value;
        });
      }
    

    Also, your delete functions seem to be working fine.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search