0

As I was unaware that ListTiles exists I implemented the following from scratch: Custom ListTile containing header number, description with date, "remove"-icon

My widget-structure looks like this:

  • StudiesItemWidget (equals scaffold of widget, contains the following three)
  • StudiesItemDuration (equals leading number)
  • StudiesItemDescription (equals main body of widget, containing description + date)
  • StudiesItemRemove (equals trailing icon, on pressed should remove containing item from list)

Here's the relevant code:

Provider for items containing the data for the widgets:

class StudyProvider with ChangeNotifier {
  List<StudiesItem> _studiesItemList = [
    /*StudiesItem(duration: 100, subject: 'AM', date: DateTime.now()),
    StudiesItem(duration: 100, subject: 'AM', date: DateTime.now()),
    StudiesItem(duration: 100, subject: 'POS', date: DateTime.now()),
    StudiesItem(duration: 100, subject: 'DBI', date: DateTime.now()),*/
  ];

  void addItem(StudiesItem studiesItem) {
    _studiesItemList.add(studiesItem);
    notifyListeners();
  }

  List<StudiesItem> get studiesItems {
    return List.unmodifiable(_studiesItemList);
  }
}

Main studies-item-widget:

class StudiesItemWidget extends StatelessWidget {
  final StudiesItem studiesItem;

  const StudiesItemWidget({
    Key? key,
    required this.studiesItem
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 5,
      child: Container(
        width: MediaQuery.of(context).size.width * 0.95,
        height: MediaQuery.of(context).size.height * 0.075,
        decoration: BoxDecoration(
            border: Border.all(color: Colors.grey),
            borderRadius: BorderRadius.all(Radius.circular(4))),
        child: Chip(
          label: Row(
            children: [
              StudiesItemDuration(timeAmount: studiesItem.duration),
              StudiesItemDescription(subject: studiesItem.subject, date: studiesItem.date),
              StudiesItemRemove(containingWidget: this)
            ],
          ),
          backgroundColor: Colors.white,
          shape: BeveledRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(4)),
          ),
        ),
      ),
    );
  }
}

Widget responsible for deletion:

class StudiesItemRemove extends StatelessWidget {
  final StudiesItemWidget containingWidget;

  const StudiesItemRemove({
    required this.containingWidget,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    var studiesItems = Provider.of<StudyProvider>(context).studiesItems;

    return Expanded(
      child: Container(
        alignment: Alignment.centerRight,
        child: IconButton(
            onPressed: () {
              studiesItems.remove(containingWidget.studiesItem);                // ToDo: fix
            },
            icon: Icon(Icons.delete)
        ),
      ),
    );
  }
}

In case this is relevant, the list of widgets in the homepage gets built using the following snippet:

var studyProvider = Provider.of<StudyProvider>(context, listen: true);    
...
body: Column(
  children: [
    (studyProvider.studiesItems.isEmpty) ? EmptyStudiesText() : BarChartWidget(),
    Expanded(
      child: ListView.builder(
        scrollDirection: Axis.vertical,
        itemCount: studyProvider.studiesItems.length,
        itemBuilder: (context, index) {
          return StudiesItemWidget(studiesItem: studyProvider.studiesItems[index]);
        },
      ),
    ),
  ],
),

Clicking the icon doesn't do anything. As not even printing "remove pressed" on pressed I believe the method doesn't get called at all. Passing the function in different ways (not sure I said that correctly but I mean: "() { code(); } or () => code()") also doesn't make a difference.

Can someone help me out? Thanks

Note: I just changed the remove-call to directly call the providers removeItem function (still doesn't work):

onPressed: () {
    studiesItemProvider.removeItem(containingWidget.studiesItem); // ToDo: fix
},
...
bool removeItem(StudiesItem studiesItem) {
    var success = _studiesItemList.remove(studiesItem);
    notifyListeners();
    return success;
} 

Second Note: As per request, here is my StudiesItem class:

class StudiesItem {
  int duration;
  final String subject;
  final DateTime date;

  StudiesItem({
    required this.duration,
    required this.subject,
    required this.date,
  });

  StudiesItem.clone(StudiesItem original):
      this(
        subject: original.subject,
        duration: original.duration,
        date: original.date
      );

  @override
  String toString() {
    return 'StudiesItem{duration: $duration, subject: $subject, date: $date}';
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is StudiesItem &&
          runtimeType == other.runtimeType &&
          duration == other.duration;

  @override
  int get hashCode => duration.hashCode;

  static StudiesItem mergeItems(StudiesItem item1, StudiesItem item2) {
    assert(item1 != item2);

    return StudiesItem(
        duration: item1.duration + item2.duration,
        subject: item1.subject,
        date: item1.date
    );
  }
}

The mergeItems function is used in another (fully working) code-segment to summarize the total data. It should not interfere with the rest of the app.

WaveZee
  • 11
  • 3
  • Remove final keyword from _studiesItemList – JB Jason Apr 05 '23 at 12:16
  • Removed the keyword but that doesn't fix the main problem of the onPressed function never firing – WaveZee Apr 05 '23 at 12:27
  • In the widget responsible for deleting, try adding this after studiesItem.remove: (context as Element).markNeedsBuild(); – Texv Apr 05 '23 at 12:38
  • Could there be an inference based on the Chip used in the StudiesItemWidget as Chips have some buitl-in delete-functionality? – WaveZee Apr 05 '23 at 12:40
  • it would be easier if you could provide minimal full snippet/git. maybe you can try passing item instead of widget on `StudiesItemRemove` also try to use `context.read` inside button – Md. Yeasin Sheikh Apr 05 '23 at 15:57
  • try this too, List get studiesItems =>[..._studiesItemList]; – JB Jason Apr 05 '23 at 18:01

2 Answers2

0

Could you share the StudiesItem Class?

I would guess the override for the equality operator is missing?

@override
bool operator ==(Object other) {}

If I recall correctly this would be needed for the remove() function to identify the actual instance of StudiesItem that should be removed.

Max de Boer
  • 146
  • 2
  • 5
  • Updated the question (+ @JBJson's suggestion) – WaveZee Apr 05 '23 at 12:28
  • @WaveZee Okay now I also don't really know what it could be. I would try debugging the code line by line, and if the onPressed() is actually never triggered I would try swapping the IconButton with a GestureDetector or something similar to check if thats causing it (probably not though) I also guess no exceptions get thrown? (I have installed Sentry. That captures all the exceptions and makes them hard to spot, maybe its also happening for you?) – Max de Boer Apr 05 '23 at 12:41
  • Changed the button to a GestureDetector, onPressed to onTap and icon to child; doesn't make a difference + no exceptions thrown in IntelliJ. I'll just ask my professor... Thank you for your time – WaveZee Apr 05 '23 at 12:50
0

Thanks everyone for trying to help, in the end, none of the suggestions worked. I ended up throwing my listtile-from-scratch away and used regular list-tiles. This fixed the issue:

@override
Widget build(BuildContext context) {
  return Card(
    elevation: 5,
    child: Container(
      width: MediaQuery.of(context).size.width * 0.95,
      height: MediaQuery.of(context).size.height * 0.075,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey),
        borderRadius: BorderRadius.all(Radius.circular(4))
      ),
      child: ListTile(
        leading: StudiesItemDuration(timeAmount: studiesItem.duration),
        title: StudiesItemDescription(subject: studiesItem.subject, date: studiesItem.date),  // Note: date should be subTitle-param
        trailing: StudiesItemRemove(containingItem: studiesItem),
      ),
    ),
  );
}

I'm guessing flutter couldn't handle the massive tree of Columns, Rows, Expandeds and so on. Maybe it has something to do with home some of the widgets used have traits that can affect how the work together (e.g. Material).

CMIIW

WaveZee
  • 11
  • 3