Search code examples
flutterdartflutter-layoutsqflite

Flutter listview keeps duplicating every time screen reloads


The list view I created keeps duplicating every time I reopen the page. I called a method at the state widget to call the list from the Sqflite package, but I want this method to be called only one time. I tried using dispose, but apparently, a method can't be disposed(?) The snippet of the code is below, thanks

 class TodoList extends StatefulWidget {
  const TodoList({Key? key}) : super(key: key);

  @override
  State<TodoList> createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {

  getAllTodos() async {
    
    List<TodoModel> _todoList = await TodoDatabase.instance.queryAlltodos();

    _todoList.forEach((todo) {
      var todoModel = TodoModel(
          title: todo.title,
          description: todo.description,
          created: todo.created,
          isChecked: todo.isChecked);
      context.read<TodoProvider>().todoitem.add(todoModel);
      
    });
  }

  @override
  void initState() {
    super.initState();
    getAllTodos();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    final theme = Theme.of(context);
    return FutureBuilder<List<TodoModel>>(
      future: TodoDatabase.instance.queryAlltodos(),
      builder: (BuildContext context, AsyncSnapshot<List<TodoModel>> snapshot) {
        if(!snapshot.hasData) {
          return const Center(child: CircularProgressIndicator(color: Colors.amber),);
        } return
        Padding(
              padding: const EdgeInsets.all(10),
              child: Consumer<TodoProvider>(
                  builder: (context, value, child) => (value.newTodo.isEmpty)
                      ? const Center(child: Text('No Todos'))
                      : ListView.separated(
                          itemCount: value.newTodo.length,
                          physics: const BouncingScrollPhysics(),
                          separatorBuilder: (context, index) => SizedBox(
                                height: size.height * 0.01,
                              ),
                          itemBuilder: (_, i) =>  Slidable(
                                key: ValueKey(i),
                                startActionPane: ActionPane(
                                  motion: StretchMotion(),
                                  children: [
                                    SlidableAction(
                                      backgroundColor: Colors.green,
                                      foregroundColor: Colors.black,
                                      icon: Icons.edit,
                                      label: 'Edit',
                                      onPressed: (context) async {
                                        TodoModel todo = value.newTodo[i];
                                        editTodo(context, todo);
                                        // Navigator.of(context).pushNamed(
                                        //   EditScreen.editscreen,
                                        //   arguments: todoData
                                        // );
                                        showSnackBar(
                                            context, 'Edit ${todo.title}?');
                                      },
                                    )
                                  ],
                                ),
                                endActionPane: ActionPane(
                                  motion: ScrollMotion(),
                                  children: [
                                    SlidableAction(
                                        backgroundColor: Colors.red,
                                        foregroundColor: Colors.black,
                                        spacing: 0,
                                        icon: Icons.delete,
                                        label: 'delete',
                                        onPressed: (context) async {
                                          showDialog(
                                              context: context,
                                              builder: (context) {
                                                return AlertDialog(
                                                    shape: RoundedRectangleBorder(
                                                      borderRadius:
                                                          BorderRadius.circular(
                                                              20),
                                                    ),
                                                    title:
                                                        const Text('Delete this TODO',style: TextStyle(color: Colors.amber)),
                                                    content:
                                                        const Text('Are you Sure?'),
                                                    actions: [
                                                      TextButton(
                                                        child: const Text('No',style: TextStyle(color: Colors.amber)),
                                                        onPressed: () {
                                                          Navigator.pop(context);
                                                        },
                                                      ),
                                                      TextButton(
                                                          child: const Text('Yes',style: TextStyle(color: Colors.amber)),
                                                          onPressed: () async {
                                                            TodoModel todo =
                                                                value.newTodo[i];
                                                            String result =
                                                                await context
                                                                    .read<
                                                                        TodoProvider>()
                                                                    .deleteToDo(
                                                                        todo);
                                                            if (result != 'OK!') {
                                                              return showSnackBar(
                                                                  context,
                                                                  result);
                                                            } else {
                                                              showSnackBar(
                                                                  context,
                                                                  'Deleted');
                                                              Navigator.pop(
                                                                  context);
                                                            }
                                                          })
                                                    ]);
                                              });
                                        })
                                  ],
                                ),
                                child: InkWell(
                                  onTap: () {},
                                  child: ListTile(
                                    tileColor: theme.primaryColorLight,
                                    shape: RoundedRectangleBorder(
                                      borderRadius: BorderRadius.circular(20),
                                    ),
                                    leading: Checkbox(
                                      shape: RoundedRectangleBorder(
                                          borderRadius: BorderRadius.circular(3)),
                                      value: value.newTodo[i].isChecked,
                                      activeColor: theme.primaryColorDark,
                                      checkColor: theme.primaryColorLight,
                                      onChanged: (_) async {
                                        // return setState(() {  // this works too
                                        //   todo.isChecked = value;
                                        // });
                                        TodoModel todo = value.todoitem[i];
    
                                        await context
                                            .read<TodoProvider>()
                                            .updateTodo(todo);
                                      },
                                    ),
                                    title: Text(value.newTodo[i].title),
                                    subtitle: Text(value.newTodo[i].description),
                                  ),
                                ),
                              ))),
            );
  });
  }
}

void editTodo(BuildContext context, TodoModel todo) {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) => EditScreen(todo: todo),
    ),
  );
}

Solution

  • Your problem seems to be in an inner layer of the code.

    I had a similar problem, with duplicated data. See the following screenshot:

    enter image description here

    My problem was in the provider (Riverpod) call to the repository. I was making the call again and again when rebuilding the screen (I'm not sure if "rebuilding" is a good practice).

    I added a condition to the use of that method, and that solved the problem.

    See where the // ADDED LINE comments are.

    final pruebasListProvider =
        StateNotifierProvider<PruebasListNotifier, List<Prueba>>((ref) {
      final getPruebasList = ref.watch(pruebasRepositoryProvider).getPruebasList;
    
      return PruebasListNotifier(fetchPruebasList: getPruebasList);
    });
    
    typedef GetPruebasListCallback = Future<List<Prueba>> Function();
    
    class PruebasListNotifier extends StateNotifier<List<Prueba>> {
      bool isLoading = false;
      bool isLoaded = false; // ADDED LINE
      GetPruebasListCallback fetchPruebasList;
    
      PruebasListNotifier({required this.fetchPruebasList}) : super([]);
    
      Future<void> loadPruebasList() async {
        if (isLoading) return;
        isLoading = true; 
    
        if (isLoaded) return;  // ADDED LINE
        final List<Prueba> pruebas = await fetchPruebasList();
    
        state = [...state, ...pruebas];
    
        await Future.delayed(const Duration(milliseconds: 300));
        isLoading = false;
        isLoaded = true;  // ADDED LINE
      }
    }
    
    

    Solved:

    enter image description here

    I'd say you have a similar problem at this line:

    future: TodoDatabase.instance.queryAlltodos(),
    

    But this is my best guess. I can't see inside that method.