我目前正在开发一个应用程序,您可以在其中设置/添加具有特定日期和时间的活动,并在时间到来时对其进行评估。
我在主屏幕的所谓“每日目标”中展示了他们的活动评估卡。在那里,用户可以看到要评估的内容以及是否进行了评估。我编写的这个机制是通过 Stream (Firebase Firestore Stream) 和一些 if 语句完成的。
问题是我有一些问题:
加载每日目标(活动评估卡)需要很长时间
每日目标(活动评估卡)中需要很长时间才能看到任何更新(例如查看是否被评估:评估后卡会消失,这需要很长时间,因此用户可以对此进行评估)再次打卡,我不想要)
这是我的数据源代码:
Stream<List<ActivityData>?> getLastActivity() {
String userId = FirebaseAuth.instance.currentUser!.uid;
// Firestore real-time updates stream
Stream<List<ActivityData>?> firestoreStream = FirebaseFirestore.instance
.collection('user_scheduled_activities')
.doc(userId)
.collection('scheduled_activities')
.where(
'start_timestamp_of_activity',
isLessThanOrEqualTo: Timestamp.fromDate(DateTime.now()),
)
.snapshots()
.map((QuerySnapshot<Map<String, dynamic>> snapshot) {
DateTime now = DateTime.now();
List<ActivityData> currentActivities = [];
for (var doc in snapshot.docs) {
Map<String, dynamic> activityData = doc.data();
DateTime startDateTimeOfActivity =
(activityData['start_timestamp_of_activity'] as Timestamp).toDate();
DateTime endDateTimeOfActivity =
(activityData['end_timestamp_of_activity'] as Timestamp).toDate();
bool isCancelled = activityData['is_cancelled'];
int? cancelReasonId = activityData['cancel_reason_id'];
Timestamp? cancelTimestampOfActivity =
activityData['cancel_timestamp_of_activity'];
DateTime? cancelDateTimeOfActivity;
if (cancelTimestampOfActivity != null) {
cancelDateTimeOfActivity = cancelTimestampOfActivity.toDate();
}
bool isDeleted = activityData['is_deleted'];
Timestamp? deleteTimestampOfActivity =
activityData['delete_timestamp_of_activity'];
DateTime? deleteDateTimeOfActivity;
if (deleteTimestampOfActivity != null) {
deleteDateTimeOfActivity = deleteTimestampOfActivity.toDate();
}
String notificationPrePEId = activityData['notification_pre_pe_id'];
String notificationPEId = activityData['notification_pe_id'];
String? selectedBehaviouralGoalsDocumentId =
activityData['selected_behavioural_goals_document_id'];
bool isSnoozed = activityData['is_snoozed'] ?? false;
// Check if the activity is active based on the current time
if (now.isAfter(startDateTimeOfActivity) &&
now.isBefore(endDateTimeOfActivity)) {
currentActivities.add(ActivityData(
activityId: doc.id,
typeIdOfActivity: activityData['type_id_of_activity'],
dateTimeOfActivity:
(activityData['timestamp_of_activity'] as Timestamp).toDate(),
titleOfActivity: activityData['title_of_activity'],
duration: activityData['duration'],
repetitionId: activityData['repetition_id'],
note: activityData['note'],
isPESubmitted: activityData['is_p_e_submitted'],
isPrePESubmitted: activityData['is_pre_p_e_submitted'],
answersOfPE: activityData['answers_of_p_e'],
answersOfPrePE: activityData['answers_of_pre_p_e'],
answersOfPEBehaviouralGoals:
activityData['answers_of_p_e_behavioural_goals'],
dateTimeOfSubmissionOfPE:
(activityData['timestamp_of_submission_of_p_e'] as Timestamp?)
?.toDate(),
dateTimeOfSubmissionOfPrePE:
(activityData['timestamp_of_submission_of_pre_p_e'] as Timestamp?)
?.toDate(),
startDateTimeOfPrePE:
(activityData['start_timestamp_of_pre_p_e'] as Timestamp)
.toDate(),
endDateTimeOfPrePE: (activityData['end_timestamp_of_pre_p_e'] as Timestamp)
.toDate(),
startDateTimeOfPE: (activityData['start_timestamp_of_p_e'] as Timestamp)
.toDate(),
endDateTimeOfPE:
(activityData['end_timestamp_of_p_e'] as Timestamp).toDate(),
startDateTimeOfActivity:
(activityData['start_timestamp_of_activity'] as Timestamp)
.toDate(),
endDateTimeOfActivity:
(activityData['end_timestamp_of_activity'] as Timestamp).toDate(),
cancelDateTimeOfActivity: cancelDateTimeOfActivity,
cancelReasonId: cancelReasonId,
isCancelled: isCancelled,
isDeleted: isDeleted,
deleteDateTimeOfActivity: deleteDateTimeOfActivity,
notificationPEId: notificationPEId,
notificationPrePEId: notificationPrePEId,
selectedBehaviouralGoalsDocumentId: selectedBehaviouralGoalsDocumentId,
isSnoozed: isSnoozed));
}
}
// Filter out deleted activities
final activityDataListWithoutDeleted =
currentActivities.where((element) => !element.isDeleted).toList();
return activityDataListWithoutDeleted;
});
// Time-based stream that emits at a regular interval
Stream<DateTime> timeStream = Stream.periodic(
const Duration(seconds: 60), // Emits every minute, adjust if needed
(_) => DateTime.now(),
);
// Combine Firestore and time streams so that either one triggers an update
return timeStream.asyncMap((now) async {
return await firestoreStream.first; // Return latest Firestore data
}).asBroadcastStream(); // Convert to a broadcast stream so multiple listeners can use it }
这是我在小部件(Flutter 前端)中的代码:
class DailyActivities extends ConsumerWidget {
final Function behaviouralGoalsFunction;
const DailyActivities({
super.key,
required this.behaviouralGoalsFunction,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final activityStream = ref.watch(getLastActivityProvider);
return activityStream.when(
data: (activities) {
print('Activity Stream');
if (activities == null || activities.isEmpty) {
return const Center(
child: Text('No activities available'),
);
} else {
bool isActivitySet() {
if (activities.isEmpty) {
return false;
} else {
return true;
}
}
bool isPEFinished(int index) {
bool result = false;
if (activities[index].isPESubmitted ||
activities[index].isCancelled) {
result = true;
}
return result;
}
bool isPrePEFinished(int index) {
bool result = false;
if (activities[index].isPrePESubmitted ||
activities[index].isCancelled) {
result = true;
}
return result;
}
List<NespTodoData> loadTodos(List<ActivityData> lastActivities) {
if (lastActivities.isEmpty) {
return [];
}
List<NespTodoData> todos = [];
for (var i = 0; i < lastActivities.length; i++) {
if (!(DateTime.now()).isAfter(
(lastActivities[i].endDateTimeOfPrePE)
.add(const Duration(minutes: 30)))) {
todos.add(
NespTodoData(
// title: 'Uppvärmning', //Pre Performance Evaluation
// title: 'Status', //Pre Performance Evaluation
// title: 'Incheckning', //Pre Performance Evaluation
title: 'Incheckning', //Pre Performance Evaluation
subtitle:
'Logga din status inför ${lastActivities[i].titleOfActivity} (${DateFormat('HH:mm').format(lastActivities[i].dateTimeOfActivity)})',
// 'Vad tycker du om din ${lastActivities[i].titleOfActivity}-${lastActivities[i].typeIdOfActivity == 1 ? 'träning' : 'match'}',
finished: isPrePEFinished(i), //!
onTap: () {
if ((DateTime.now()).isAfter(
(lastActivities[i].endDateTimeOfPrePE)
.add(const Duration(minutes: 30)))) {
NespPopupFunc(
context,
title: "You can not do the check-in!",
paragraph:
'You can not do the check-in after: ${lastActivities[i].endDateTimeOfPrePE}',
);
} else {
context.push(
Uri(
path: RouteLocation.prePerformanceEvaluation,
queryParameters: {
'activityId': lastActivities[i].activityId,
},
).toString(),
);
}
}),
);
}
if (DateTime.now().isAfter(lastActivities[i].startDateTimeOfPE)) {
todos.add(
NespTodoData(
title: 'Prestationsutvärdering', //Performance Evaluation
subtitle:
'Dags att utvärdera ${lastActivities[i].titleOfActivity} (${DateFormat('HH:mm').format(lastActivities[i].dateTimeOfActivity)})',
// 'Utvärdera din prestation efter ${lastActivities[i].titleOfActivity}-${lastActivities[i].typeIdOfActivity == 1 ? 'träningen' : 'matchen'}',
finished: isPEFinished(i),
locked: !(DateTime.now())
.isAfter(lastActivities[i].startDateTimeOfPE),
onTap: () {
if (!(DateTime.now())
.isAfter(lastActivities[i].startDateTimeOfPE)) {
NespPopupFunc(
context,
title: "Sakta i backarna!",
paragraph:
'Utvärderingen låses upp \n ${DateFormat('HH:mm').format(lastActivities[i].startDateTimeOfPE)}', // \n(d/M/y)
);
} else {
context.push(
Uri(
path: RouteLocation.performanceEvaluation,
queryParameters: {
'activityId': lastActivities[i].activityId,
},
).toString(),
);
}
},
),
);
}
}
return todos;
}
List<NespTodoData> todos = [
NespTodoData(
title: 'Lägg till en aktivitet',
subtitle: 'Lägg till en träning eller din nästa match för idag',
finished: isActivitySet(),
onTap: () {
context.push(Uri(
path: RouteLocation.profile,
queryParameters: {'id': '2'}).toString());
}),
NespTodoData(
title: 'Välj beteendemål',
// subtitle: 'Sätt upp konkreta mål att jobba mot',
subtitle: 'Utmana dig själv och nå nya höjder',
onTap: () {
behaviouralGoalsFunction();
},
finished: ref.watch(goalProvider).isSet,
),
...loadTodos(activities),
];
String todosCounter() {
int numberOfTodos = todos.length;
int numberOfFinishedTodos =
todos.where((todo) => todo.finished).length;
return "$numberOfFinishedTodos/$numberOfTodos";
}
return TodoList(
titleLink: () {
context.push(RouteLocation.dailyActivities);
},
title: 'Dagens aktiviteter',
titleLinkText: todosCounter(),
todos: todos.where((todo) => !todo.finished).toList(),
);
}
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stackTrace) => Center(
child: Text('Error: $error'),
),
); }}
您可以在 Flutter 中使用 FutureBuilder 或 StreamBuilder 来优化 UI 性能和响应能力,因为它们正在处理异步操作。
基本上,这些构建器在内部管理异步操作的状态并重建 UI。这种方法将降低手动管理状态和更新 UI 的复杂性。
例如,FutureBuilder 非常适合一次性数据获取操作,而 StreamBuilder 则适合可能随时间变化的数据流。通过将异步处理委托给这些构建器,您的应用程序可以保持流畅的交互和更新,从而增强用户体验。
尝试使用它们!它会让你的 UI 更快,并且会在需要时更新 UI。
但是如果你想处理状态管理,我建议你使用 BLOC 来处理它!