skip to Main Content

I’m creating a Flutter Widget and when I try to remove an item from the list I’m using, it always removes the last one, I was thinking it could be a Key problem, but nothing suits it, do anyone know how I could solve this?

The code

create_game.dart

import 'package:flutter/material.dart';
import 'package:pontinho/components/custom_input.dart';

class CreateGame extends StatefulWidget {
  const CreateGame({super.key});

  @override
  State<CreateGame> createState() => _CreateGameState();
}

class _CreateGameState extends State<CreateGame> {
  List<String> names = [''];

  void changeName(int nameIndex, String change) {
    setState(() {
      names[nameIndex] = change;
    });
  }

  void removeName(int nameIndex) {
    print(names);
    print(nameIndex);
    setState(() {
      names.removeAt(nameIndex);
    });
  }

  ListView createNamesInput() {
    return ListView.builder(
      itemCount: names.length,
      shrinkWrap: true,
      itemBuilder: (context, index) {
        return ListTile(
          key: ObjectKey(index),
          title: CustomInput(
            key: ObjectKey(index),
            labelText: "Nome",
            onChanged: (String changed) => changeName(index, changed),
            text: names[index],
            onRemoved: () => removeName(index),
          ),
        );
      },
    );
    // return names
    //     .asMap()
    //     .entries
    //     .map((el) => CustomInput(
    //           key: ObjectKey('${el.key}'),
    //           labelText: "Nome",
    //           onChanged: changeName,
    //           index: el.key,
    //           text: names[el.key],
    //           onRemoved: removeName,
    //         ))
    //     .toList();
  }

  void addName() {
    setState(() {
      names.add('');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: GestureDetector(
          onTap: (() => Navigator.pop(context)),
          child: const Icon(
            Icons.arrow_back,
            color: Colors.black,
            size: 40,
          ),
        ),
        backgroundColor: Colors.white,
        titleTextStyle: const TextStyle(
          color: Colors.black,
          fontSize: 20,
        ),
        title: const Text("CRIE SEU JOGO"),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(
          vertical: 8,
          horizontal: 16,
        ),
        // child: createNamesInput(),
        child: Column(
          children: [
            createNamesInput(),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 10),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  TextButton(
                    onPressed: addName,
                    child: Row(
                      children: const [
                        Icon(Icons.add),
                        Text('Adicionar Jogador'),
                      ],
                    ),
                  ),
                ],
              ),
            ),
            SizedBox(
              width: double.infinity,
              height: 50,
              child: ElevatedButton(
                onPressed: () => print('Iniciar!'),
                child: const Text('Iniciar!'),
              ),
            )
          ],
        ),
      ),
    );
  }
}

custom_input.dart

import 'package:flutter/material.dart';

typedef OneArgumentCallback = void Function(String changed);

class CustomInput extends StatefulWidget {
  final OneArgumentCallback onChanged;
  final VoidCallback onRemoved;
  final String labelText;
  final String text;

  const CustomInput({
    super.key,
    required this.onChanged,
    required this.labelText,
    required this.text,
    required this.onRemoved,
  });

  @override
  State<CustomInput> createState() => _CustomInputState();
}

class _CustomInputState extends State<CustomInput> {
  late final TextEditingController inputController;

  @override
  void initState() {
    super.initState();
    inputController = TextEditingController(text: widget.text);
  }

  void changeContent(String value) {
    widget.onChanged(
      value,
    );
  }

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      key: widget.key,
      controller: inputController,
      textDirection: TextDirection.ltr,
      decoration: InputDecoration(
        border: const UnderlineInputBorder(),
        labelText: widget.labelText,
        suffixIcon: IconButton(
          onPressed: () => widget.onRemoved(),
          icon: const Icon(
            Icons.close,
            color: Colors.red,
          ),
        ),
      ),
      autocorrect: false,
      onChanged: (value) => changeContent(value),
    );
  }
}

2

Answers


  1. I was thinking it could be a Key problem

    That’s correct; You need to use names[index] as the value for your Key:

    ListTile(
              key: ObjectKey(names[index]),
              title: CustomInput(
    
    Login or Signup to reply.
  2. Indeed it is a key issue, you have to create a combined key that must be unique for each item, I merged the index with names[index],

    CustomInput(
      key: ObjectKey('$index:${names[index]}'),
      labelText: "Nome",
      onChanged: (String changed) => changeName(index, changed),
      text: names[index],
      onRemoved: () => removeName(index),
    ),
    

    note that if you try this code alone the textfield will lose focus because the key has changed, this will be solved by removing the setState inside the onChange

    void changeName(int nameIndex, String change) {
      names[nameIndex] = change;
    }
    

    here you don’t need setState because the UI will be updated by default when you are typing in the textfield

    I hope I made it clear

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