Trying to keep Widgets alive in Listview.builder

60 views Asked by At

Whenever I am scrolling in my Listview.builder, the state gets reset and all the cards which are displaying a Task and the Date along with it above gets mixed up.

I tried many things like a scrollcontroller, KeepAlives and so on, but i dont know how to make it work properly

While scrolling the Listview.builder the tasks with the date above it get mixed up. For example there is one date like 04/02/2024 and accordingly two tasks are under it. Now if I scroll down and get up again, there are now two times the date and the task under it. like: 04/02/2024: task, 04/02/2024: task instead of 04/02/2024: task, task`

Also sorry for my messy code, this is my first project:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_projects/model/classes.dart';
import 'package:flutter_projects/model/streams.dart';
import 'package:flutter_projects/view/screens/complete_task_screen.dart';
import 'package:flutter_projects/viewModel/firestore_functions/crud.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';

import 'custom_countdown.dart';

class TaskStreamBuilder extends StatelessWidget {
  String globalTaskId = '';
  String globalMemberId = '';
  String globalMemberName = '';
  String? pickupDate;
  String? dropoffDate;
  String? dateToday = DateFormat('d/M/yyyy').format(DateTime.now());
  TaskFilter? taskFilter;
  bool isTaskAllScreen;
  String? dateTaskTypeCheck;

  TaskStreamBuilder({
    this.taskFilter,
    required this.isTaskAllScreen,
  });

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: tasksDetailsStream(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return Center(
            child: CircularProgressIndicator(),
          );
        }

        List<TaskDetails> tasks = [];

        String? tatCountdown;

        String? currentDate;
        for (var doc in snapshot.data!.docs) {
          Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
          String pickUpDate = data['pickupDate'] ?? '';
          String dropOffDate = data['dropoffDate'] ?? '';

          DateTime? dtpickUpDate = pickUpDate.isNotEmpty
              ? DateFormat('dd/MM/yyyy').parse(pickUpDate)
              : null;
          DateTime? dtdropOffDate = dropOffDate.isNotEmpty
              ? DateFormat('dd/MM/yyyy').parse(dropOffDate)
              : null;

          TaskDetails task = TaskDetails(
            taskType: data['taskType'],
            pickupDate: pickUpDate,
            dropoffDate: dropOffDate,
            dtDropoffDate: dtdropOffDate,
            dtPickupDate: dtpickUpDate,
            memberName: data['memberName'] ?? 'No member name',
            notes: data['notes'] ?? 'No notes',
            pickupTime: data['pickupTime'] ?? 'No time',
            dropoffTime: data['dropoffTime'] ?? 'No time',
            phoneNumber: data['phoneNumber'] ?? 'No phone number',
            memberId: data['memberId'] ?? 'No member id',
            address: data['address'] ?? 'No address',
            pickupCompletedAt: null,
            dropoffCompletedAt: null,
            id: doc.id ?? 'No id',
          );

          if (shouldIncludeTask(task)) {
            tasks.add(task);
          }
        }

        tasks.sort((a, b) {
          DateTime? aDate =
              a.taskType == 'Pick-Up' ? a.dtPickupDate! : a.dtDropoffDate!;
          DateTime? bDate =
              b.taskType == 'Pick-Up' ? b.dtPickupDate! : b.dtDropoffDate!;

          int dateComparison = aDate!.compareTo(bDate!);
          if (dateComparison != 0) {
            // If the dates are not equal, return the result of comparing the dates
            return dateComparison;
          } else {
            // If the dates are equal, compare the task types
            int typeComparison = b.taskType.compareTo(a.taskType);
            if (typeComparison != 0) {
              // If the task types are not equal, return the result of comparing the task types
              return typeComparison;
            } else {
              // If the task types are equal, compare the times
              DateTime now = DateTime.now();
              DateTime aTime = DateFormat('HH:mm a').parse(
                  a.taskType == 'Pick-Up' ? a.pickupTime! : a.dropoffTime!);
              DateTime bTime = DateFormat('HH:mm a').parse(
                  b.taskType == 'Pick-Up' ? b.pickupTime! : b.dropoffTime!);
              Duration aDuration = aTime.difference(now);
              Duration bDuration = bTime.difference(now);
              return aDuration.compareTo(bDuration);
            }
          }
        });

        return Column(
          children: [
            Expanded(
              child: ListView.builder(
                itemCount: tasks.length,
                itemBuilder: (context, index) {
                  TaskDetails task = tasks[index];

                  // Check if the date is different from the previous task
                  if (task.pickupDate != currentDate &&
                          task.taskType == 'Pick-Up' &&
                          isTaskAllScreen == true ||
                      task.dropoffDate != currentDate &&
                          task.taskType == 'Drop-Off' &&
                          isTaskAllScreen == true) {
                    currentDate = task.taskType == 'Pick-Up'
                        ? task.pickupDate
                        : task.dropoffDate;

                    String? currentDateReset = null;
                    currentDateReset = currentDate;

                    return Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        SizedBox(height: 20),
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 16),
                          child: Text(
                            currentDateReset!,
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 13,
                            ),
                          ),
                        ),
                        _buildTaskCard(context, task),
                      ],
                    );
                  }

                  // If the date is the same, continue displaying the task
                  return _buildTaskCard(context, task);
                },
              ),
            ),
          ],
        );
      },
    );
  }

  @override
  Widget _buildTaskCard(BuildContext context, TaskDetails task) {
    String? pickUpDate = task.dtPickupDate != null
        ? DateFormat('dd/MM/yyyy').format(task.dtPickupDate!)
        : null;
    String? dropOffDate = task.dtDropoffDate != null
        ? DateFormat('dd/MM/yyyy').format(task.dtDropoffDate!)
        : null;

    return Slidable(
      startActionPane: ActionPane(
        motion: const ScrollMotion(),
        children: [
          SlidableAction(
            onPressed: (context) {
              globalTaskId = task.id;
              globalMemberId = task.memberId;
              globalMemberName = task.memberName;
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => CompleteTaskScreen(
                    globalTaskId: globalTaskId,
                    globalMemberId: globalMemberId,
                    globalMemberName: globalMemberName,
                  ),
                ),
              );
            },
            label: 'Done',
            backgroundColor: Colors.green,
            icon: Icons.check,
          ),
          SlidableAction(
            onPressed: (context) {
              deleteTask(task.id);
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('Task deleted'),
                  duration: Duration(seconds: 1),
                ),
              );
            },
            label: 'Delete',
            backgroundColor: Colors.red,
            icon: Icons.delete,
          ),
        ],
      ),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8.0),
        child: Card(
          surfaceTintColor: Colors.white,
          color: Colors.white,
          elevation: 2.5,
          child: Column(
            children: [
              Theme(
                data: Theme.of(context)
                    .copyWith(dividerColor: Colors.transparent),
                child: ExpansionTile(
                  expandedCrossAxisAlignment: CrossAxisAlignment.start,
                  expandedAlignment: Alignment.centerLeft,
                  title: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Icon(
                            Icons.local_shipping_outlined,
                            color: task.taskType == 'Pick-Up'
                                ? Colors.green[300]
                                : Colors.blue,
                            size: 18,
                          ),
                          Expanded(
                            child: Text(
                              '   ${task.taskType} for',
                              style: GoogleFonts.nunito(
                                fontSize: 16,
                                color: Colors.black,
                              ),
                            ),
                          ),
                          Align(
                            alignment: Alignment.centerRight,
                            child: getTATCountDown(task),
                          ),
                        ],
                      ),
                      Text(
                        '       ${task.memberName}',
                        style: GoogleFonts.nunito(
                          fontSize: 16,
                          color: Colors.black,
                        ),
                      ),
                    ],
                  ),
                  children: [
                    Padding(
                      padding: EdgeInsets.symmetric(horizontal: 16),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Divider(
                            color: Colors.indigoAccent,
                          ),
                          Row(
                            children: [
                              Icon(
                                Icons.location_on_outlined,
                                color: Colors.black,
                                size: 18,
                              ),
                              Text(' ${task.address}',
                                  style: GoogleFonts.nunito(
                                    fontSize: 16,
                                  )),
                            ],
                          ),
                          Divider(
                            color: Colors.indigoAccent,
                          ),
                          Row(
                            children: [
                              Icon(
                                Icons.phone_outlined,
                                color: Colors.black,
                                size: 18,
                              ),
                              Text(' ${task.phoneNumber}',
                                  style: GoogleFonts.nunito(
                                    fontSize: 16,
                                  )),
                            ],
                          ),
                          Divider(
                            color: Colors.indigoAccent,
                          ),
                          Row(
                            children: [
                              Icon(
                                Icons.access_time_outlined,
                                color: Colors.black,
                                size: 18,
                              ),
                              Text(
                                  ' ${task.taskType == 'Pick-Up' ? task.pickupTime : task.dropoffTime}',
                                  style: GoogleFonts.nunito(
                                    fontSize: 16,
                                  )),
                            ],
                          ),
                          Divider(
                            color: Colors.indigoAccent,
                          ),
                          Row(
                            children: [
                              Icon(
                                Icons.date_range_outlined,
                                color: Colors.black,
                                size: 18,
                              ),
                              Text(
                                task.taskType == 'Pick-Up'
                                    ? (' $pickUpDate') ?? 'No date'
                                    : (' $dropOffDate') ?? 'No date',
                                style: GoogleFonts.nunito(
                                  fontSize: 16,
                                ),
                              ),
                            ],
                          ),
                          SizedBox(height: 11),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  bool shouldIncludeTask(TaskDetails task) {
    // Your logic for taskFilterToday

    if (isTaskAllScreen == true) {
      if (taskFilter != null) {
        if (taskFilter!.isSubmitted!) {
          final isActivated = taskFilter!.isActivated;
          final isPickUp = taskFilter!.isPickUp;
          final isDropOff = taskFilter!.isDropOff;

          switch (isActivated!) {
            case false:
              return true;

            case true:
              switch (isPickUp!) {
                case true:
                  if (isDropOff == false) {
                    if (taskFilter!.date == null) {
                      return task.taskType == 'Pick-Up';
                    } else {
                      return task.taskType == 'Pick-Up' &&
                          task.pickupDate == taskFilter!.date;
                    }
                  } else {
                    if (taskFilter!.date == null) {
                      return task.taskType == 'Pick-Up' ||
                          task.taskType == 'Drop-Off';
                    } else {
                      return task.taskType == 'Pick-Up' &&
                              task.pickupDate == taskFilter!.date ||
                          task.taskType == 'Drop-Off' &&
                              task.dropoffDate == taskFilter!.date;
                    }
                  }

                case false:
                  switch (taskFilter!.isDropOff) {
                    case true:
                      if (taskFilter!.date == null) {
                        return task.taskType == 'Drop-Off';
                      } else {
                        return task.taskType == 'Drop-Off' &&
                            task.dropoffDate == taskFilter!.date;
                      }
                    case false:
                      switch (taskFilter!.date) {
                        case null:
                          return true;

                        default:
                          return task.pickupDate == taskFilter!.date ||
                              task.dropoffDate == taskFilter!.date;
                      }

                    // Handle the case when isDropOff is false or null
                    default:
                      return true;
                  }
              }
          }
        }
      }

      return true;
    } else if (isTaskAllScreen == false) {
      if (taskFilter != null) {
        final isActivated = taskFilter!.isActivated;
        final isPickUp = taskFilter!.isPickUp;

        if (taskFilter!.isSubmitted!) {
          switch (isActivated!) {
            case false:
              return task.pickupDate == dateToday ||
                  task.dropoffDate == dateToday;

            case true:
              switch (isPickUp) {
                case true:
                  return task.taskType == 'Pick-Up' &&
                      task.pickupDate == dateToday;
                case false:
                  switch (taskFilter!.isDropOff) {
                    case true:
                      return task.taskType == 'Drop-Off' &&
                          task.dropoffDate == dateToday;
                    case false:
                      return task.pickupDate == dateToday ||
                          task.dropoffDate == dateToday;
                    default:
                      return true;
                  }
                default:
                  return true;
              }
          }
        }
      }
    }

    return false;
  }
}

Widget getTATCountDown(TaskDetails task) {
  String? pickUpDate = task.pickupDate;
  String? dropOffDate = task.dropoffDate;
  String? pickUpTime = task.pickupTime;
  String? dropOffTime = task.dropoffTime;
  Duration? differencePickUp = Duration.zero;
  Duration? differenceDropOff = Duration.zero;
  String? tatCountdownHoursDropOff;
  String? tatCountdownDaysDropOff;
  String? tatCountDownMinutesDropOff;
  String? tatCountdownHoursPickUp;
  String? tatCountDownMinutesPickUp;
  String? tatCountdownDaysPickUp;

  if (task.taskType == 'Pick-Up') {
    if (pickUpDate != '' && pickUpTime != null) {
      DateTime pickUpDateTime = DateFormat('dd/MM/yyyy HH:mm a').parse(
        '$pickUpDate $pickUpTime',
      );
      DateTime now = DateTime.now();
      differencePickUp = pickUpDateTime.difference(now);
      tatCountdownDaysPickUp = differencePickUp.inDays.toString();
      tatCountdownHoursPickUp =
          differencePickUp.inHours.remainder(24).toString();
      tatCountDownMinutesPickUp =
          differencePickUp.inMinutes.remainder(60).toString();
    }
  } else if (task.taskType == 'Drop-Off') {
    if (dropOffDate != '' && dropOffTime != null) {
      DateTime dropOffDateTime = DateFormat('dd/MM/yyyy HH:mm a').parse(
        '$dropOffDate $dropOffTime',
      );
      DateTime now = DateTime.now();

      differenceDropOff = dropOffDateTime.difference(now);
      tatCountdownDaysDropOff = differenceDropOff.inDays.toString();
      tatCountdownHoursDropOff =
          differenceDropOff.inHours.remainder(24).toString();
      tatCountDownMinutesDropOff =
          differenceDropOff.inMinutes.remainder(60).toString();
    }
  }

  if (differencePickUp != Duration.zero) {
    if (differencePickUp.isNegative) {
      return Text(
        'Due',
        style: TextStyle(
          color: Colors.red,
        ),
      );
    } else {
      return CustomCountdown(
        duration: Duration(
          days: int.parse(tatCountdownDaysPickUp ?? '0'),
          hours: int.parse(tatCountdownHoursPickUp ?? '0'),
          minutes: int.parse(tatCountDownMinutesPickUp ?? '0'),
        ),
      );
    }
  }
  if (differenceDropOff != Duration.zero) {
    if (differenceDropOff.isNegative) {
      return Text(
        'Due',
        style: TextStyle(
          color: Colors.red,
          fontWeight: FontWeight.bold,
        ),
      );
    } else {
      return CustomCountdown(
        duration: Duration(
          days: int.parse(tatCountdownDaysDropOff ?? '0'),
          hours: int.parse(tatCountdownHoursDropOff ?? '0'),
          minutes: int.parse(tatCountDownMinutesDropOff ?? '0'),
        ),
      );
    }
  }
  return Container();
}
1

There are 1 answers

1
Ozan Taskiran On

Dont do this:

stream: tasksDetailsStream(),

Instead create a Stream variable inside your class and pass it to the StreamBuilder:

Stream taskDetailsStream = tasksDetailsStream();

Your StreamBuilder gets the stream variable:

stream : taskDetailsStream,