Home > Mobile >  My Flutter ListView is always removing the last item from the list
My Flutter ListView is always removing the last item from the list

Time:01-04

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),
    );
  }
}

CodePudding user response:

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(

CodePudding user response:

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

  • Related