Fabian
Fabian

Reputation: 23

Deleting Item out of List, Listview.build shows wrong data

I have a Stateful widget that i pass a list to (for example 2 items). After I delete an item, the widget should rebuild itself. Unfortunately, the deleted item is still displayed and the other one is not. When I re-enter the widget, the correct item is loaded. There is a similar problem List not updating on deleting item but maybe someone can explain me what i did wrong and why provider is helping me here instead of setState?

My code is:

import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:trip_planner/util/dialog_box.dart';
import 'package:trip_planner/util/previewUrl.dart';

class BookingPage extends StatefulWidget {
  final List toDoList;

  BookingPage({
    super.key,
    required this.toDoList,
  });

  @override
  State<BookingPage> createState() => _BookingPageState();
}

class _BookingPageState extends State<BookingPage> {
  //text controller
  final _controller = TextEditingController();
  final _database = FirebaseDatabase.instance.ref();

  //Liste is an example what i have in my list
  List toDoList2 = [
    ["https://www.booking.com/Share-Rnv2Kf", true],
    ["https://www.booking.com/Share-3hKQ0r", true],
  ];

  void initState(){
    super.initState();
  }

  void deleteTask(int index){
    setState(() {
      widget.toDoList.removeAt(index);
    });
    //DatabaseReference _testRef = _database.child("Hotel:");
    //_testRef.set(widget.toDoList.toString());
  }

  //save new Item
  void saveNewItem(){
    setState(() {
      widget.toDoList.add([_controller.text, false]);
      //DatabaseReference _testRef = _database.child("Hotel:");
      //_testRef.set(widget.toDoList.toString());
      _controller.clear();
    });
    Navigator.of(context).pop();
  }

  void createNewItem(){
    showDialog(
      context: context,
      builder: (context){
        return DialogBox(
          controller: _controller,
          onSave: saveNewItem,
          onCancel: () => Navigator.of(context).pop(),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Booking Seiten'),
        elevation: 0,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: createNewItem,
        child: Icon(Icons.add),
      ),
      body: ListView.builder(
        itemCount: widget.toDoList.length,
        itemBuilder: (context, index){
          return PreviewUrl(
            url2: widget.toDoList[index][0],
            deleteFunction: (context) => setState(() => deleteTask(index)),
          );
        },
      ),
    );
  }
}

i thought setState does the same thing as when i re-enter the widget, but it doesn't.

import 'package:any_link_preview/any_link_preview.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:url_launcher/url_launcher.dart';

class PreviewUrl extends StatelessWidget {
  final String url2;
  //Function(bool?)? onChanged;
  Function(BuildContext)? deleteFunction;



  PreviewUrl({
    super.key,
    required this.url2,
    required this.deleteFunction,
    //required this.onChanged,
  });

  Future openBrowserURL({
    required String url,
    bool inApp = false,
  })  async {
    if(await canLaunch(url)){
      await launch(
        url,
        forceSafariVC: inApp, //iOS
        forceWebView: inApp, //Android
        enableJavaScript: true, //Android
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(1.0),
      child: Slidable(
        endActionPane: ActionPane(
          motion: StretchMotion(),
          children: [
            SlidableAction(
              onPressed: deleteFunction,
              icon: Icons.delete,
              backgroundColor: Colors.red.shade300,
              borderRadius: BorderRadius.circular(12),
            )
          ],
        ),
        child: Container(
          child: AnyLinkPreview.builder(
            link: url2,
            itemBuilder: (context, metadata, imageProvider) => Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (imageProvider != null)
                  GestureDetector(
                    onTap: () async {
                      final url = url2;
                      openBrowserURL(url: url, inApp: true);
                    },
                    child: Container(
                      constraints: BoxConstraints(
                        maxHeight: MediaQuery.of(context).size.width *0.25,
                      ),
                      decoration: BoxDecoration(
                        borderRadius: BorderRadius.only(
                          topLeft: Radius.circular(12),
                          topRight: Radius.circular(12)),
                        image: DecorationImage(
                          image: imageProvider,
                          fit: BoxFit.cover,
                        ),
                      ),
                    ),
                  ),
                Container(
                  width: double.infinity,
                  color: Theme.of(context).primaryColor.withOpacity(0.6),
                  padding: const EdgeInsets.symmetric(
                      vertical: 10, horizontal: 15),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      if (metadata.title != null)
                        Text(
                          metadata.title!,
                          maxLines: 1,
                          style:
                          const TextStyle(fontWeight: FontWeight.w500),
                        ),
                      const SizedBox(height: 5),
                      if (metadata.desc != null)
                        Text(
                          metadata.desc!,
                          maxLines: 1,
                          style: Theme.of(context).textTheme.bodySmall,
                        ),
                      Text(
                        metadata.url ?? url2,
                        maxLines: 1,
                        style: Theme.of(context).textTheme.bodySmall,
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Upvotes: 1

Views: 718

Answers (1)

Andrija
Andrija

Reputation: 1909

If you run the simplified version of your code in DartPad - it will work:

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

List toDoList = [
    ["Button 1", true],
    ["Button 2", true],
  ];

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: BookingPage(toDoList: toDoList),
        ),
      ),
    );
  }
}

class BookingPage extends StatefulWidget {
  final List toDoList;

  const BookingPage({
    super.key,
    required this.toDoList,
  });

  @override
  State<BookingPage> createState() => _BookingPageState();
}

class _BookingPageState extends State<BookingPage> {
  //Liste is an example what i have in my list
  List toDoList2 = [
    ["Button 1", true],
    ["Button 2", true],
  ];

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

  void deleteTask(int index) {
    setState(() {
      widget.toDoList.removeAt(index);
    });
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Booking Seiten'),
        elevation: 0,
      ),
      body: ListView.builder(
        itemCount: widget.toDoList.length,
        itemBuilder: (context, index) {
          return ElevatedButton(
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.lightBlue,
              padding: const EdgeInsets.all(12),
              textStyle: const TextStyle(fontSize: 22),
            ),
            child: Text(widget.toDoList[index][0]!),
            onPressed: () => setState(() => deleteTask(index)),
          );
        },
      ),
    );
  }
}

Which tells me that the problem is your PreviewUrl. My guess is - it is a statful widget, and when the tree rebuilds - it will link the old State object to the first item.

Using Keys might help, something like:

return PreviewUrl(
            key: ObjectKey(widget.toDoList[index]),
            url2: widget.toDoList[index][0],
            deleteFunction: (context) => setState(() => deleteTask(index)),
          );

Upvotes: 3

Related Questions