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();
}
Dont do this:
stream: tasksDetailsStream(),
Instead create a Stream variable inside your class and pass it to the StreamBuilder:
Your StreamBuilder gets the stream variable: