Firebase Firestore Stream 非常慢并且延迟很高

问题描述 投票:0回答:1

我目前正在开发一个应用程序,您可以在其中设置/添加具有特定日期和时间的活动,并在时间到来时对其进行评估。

我在主屏幕的所谓“每日目标”中展示了他们的活动评估卡。在那里,用户可以看到要评估的内容以及是否进行了评估。我编写的这个机制是通过 Stream (Firebase Firestore Stream) 和一些 if 语句完成的。

问题是我有一些问题:

  1. 加载每日目标(活动评估卡)需要很长时间

  2. 每日目标(活动评估卡)中需要很长时间才能看到任何更新(例如查看是否被评估:评估后卡会消失,这需要很长时间,因此用户可以对此进行评估)再次打卡,我不想要)

这是我的数据源代码:

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 firebase dart google-cloud-firestore flutter-streambuilder
1个回答
0
投票

您可以在 Flutter 中使用 FutureBuilder 或 StreamBuilder 来优化 UI 性能和响应能力,因为它们正在处理异步操作。

基本上,这些构建器在内部管理异步操作的状态并重建 UI。这种方法将降低手动管理状态和更新 UI 的复杂性。

例如,FutureBuilder 非常适合一次性数据获取操作,而 StreamBuilder 则适合可能随时间变化的数据流。通过将异步处理委托给这些构建器,您的应用程序可以保持流畅的交互和更新,从而增强用户体验。

尝试使用它们!它会让你的 UI 更快,并且会在需要时更新 UI。

但是如果你想处理状态管理,我建议你使用 BLOC 来处理它!

© www.soinside.com 2019 - 2024. All rights reserved.