skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    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


  2. 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.

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