You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于flutter_bloc:跨页面存临时变量及Firestore嵌套查询优化

Hey there! Let's break down your two core questions and walk through cleaner, more idiomatic solutions that align with BLoC pattern best practices.

1. 跨页面保留临时变量的最佳实现方式

Your current approach using shared_preferences works, but it can get messy when you have to read/write from multiple places. Here are two better alternatives tailored to BLoC architecture:

Option 1: Use a Global App Bloc for Core Shared State

Create a top-level AppBloc to manage global state like the current schoolId and courseId. This acts as a single source of truth, and other blocs (like your CourseBloc) can listen to its state changes to stay in sync.

Example AppBloc Implementation:

class SetCurrentSchoolEvent extends AppEvent {
  final String schoolId;
  SetCurrentSchoolEvent(this.schoolId);
}

class SetCurrentCourseEvent extends AppEvent {
  final String courseId;
  SetCurrentCourseEvent(this.courseId);
}

class AppState {
  final String currentSchoolId;
  final String currentCourseId;

  AppState({this.currentSchoolId, this.currentCourseId});

  AppState copyWith({String currentSchoolId, String currentCourseId}) {
    return AppState(
      currentSchoolId: currentSchoolId ?? this.currentSchoolId,
      currentCourseId: currentCourseId ?? this.currentCourseId,
    );
  }
}

class AppBloc extends Bloc<AppEvent, AppState> {
  @override
  AppState get initialState => AppState();

  @override
  Stream<AppState> mapEventToState(AppEvent event) async* {
    if (event is SetCurrentSchoolEvent) {
      yield state.copyWith(currentSchoolId: event.schoolId);
    } else if (event is SetCurrentCourseEvent) {
      yield state.copyWith(currentCourseId: event.courseId);
    }
  }
}

Then, your CourseBloc can listen to the AppBloc to automatically update its data source when the school ID changes:

class CourseBloc extends Bloc<CourseEvent, CourseState> {
  final CourseRepository Function(String) courseRepoFactory;
  final AppBloc appBloc;
  StreamSubscription _appSubscription;
  CourseRepository _courseRepo;
  StreamSubscription _courseSubscription;

  CourseBloc({@required this.courseRepoFactory, @required this.appBloc})
      : assert(courseRepoFactory != null),
        assert(appBloc != null) {
    // Listen for school ID changes and refresh the repository
    _appSubscription = appBloc.listen((appState) {
      if (appState.currentSchoolId != null) {
        _courseRepo = courseRepoFactory(appState.currentSchoolId);
        add(LoadCoursesEvent());
      }
    });
  }

  // ... rest of your bloc logic remains mostly the same

  @override
  void dispose() {
    _appSubscription?.cancel();
    _courseSubscription?.cancel();
    super.dispose();
  }
}

Option 2: Persist State with HydratedBloc (for long-term retention)

If you need to retain these variables across app restarts, hydrated_bloc is far cleaner than manual shared_preferences handling. It automatically serializes your state to local storage and integrates seamlessly with the BLoC lifecycle.

Modify your AppBloc to extend HydratedBloc:

class AppBloc extends HydratedBloc<AppEvent, AppState> {
  @override
  AppState get initialState => super.initialState ?? AppState();

  @override
  Stream<AppState> mapEventToState(AppEvent event) async* {
    // Same state logic as before
    if (event is SetCurrentSchoolEvent) {
      yield state.copyWith(currentSchoolId: event.schoolId);
    } else if (event is SetCurrentCourseEvent) {
      yield state.copyWith(currentCourseId: event.courseId);
    }
  }

  @override
  AppState fromJson(Map<String, dynamic> json) {
    return AppState(
      currentSchoolId: json['currentSchoolId'],
      currentCourseId: json['currentCourseId'],
    );
  }

  @override
  Map<String, dynamic> toJson(AppState state) {
    return {
      'currentSchoolId': state.currentSchoolId,
      'currentCourseId': state.currentCourseId,
    };
  }
}

This way, your global state persists automatically without manual storage calls.

2. Firestore Nested Collection Query: Document ID传递优化

Yes, you do need to pass parent document IDs (like schoolId) to build nested collection queries—but you can do this far more elegantly than hardcoding or reading from storage directly.

Refactor Your Repository to Accept Dynamic IDs

Instead of hardcoding the schoolId in your repository, inject it via the constructor. This makes the repository reusable across different schools and keeps it decoupled from global state:

class FirebaseCourseRepository implements CourseRepository {
  final String schoolId;
  CollectionReference get _courseCollection => Firestore.instance
      .collection('schools')
      .document(schoolId)
      .collection('courses');

  FirebaseCourseRepository({@required this.schoolId}) : assert(schoolId != null);

  @override
  Stream<List<Course>> courses() {
    return _courseCollection.snapshots().map((snapshot) => snapshot.documents
        .map((doc) => Course.fromEntity(CourseEntity.fromSnapshot(doc)))
        .toList());
  }

  @override
  Future<void> addCourse(Course course) {
    return _courseCollection.add(course.toEntity().toDocument());
  }

  @override
  Future<void> updateCourse(Course updatedCourse) {
    return _courseCollection
        .document(updatedCourse.id)
        .updateData(updatedCourse.toEntity().toDocument());
  }

  @override
  Future<void> deleteCourse(Course course) async {
    return _courseCollection.document(course.id).delete();
  }
}

Inject the Repository Dynamically

Use a factory function or dependency injection container (like get_it) to create repository instances with the current schoolId from your AppBloc:

// Example using a factory function when initializing CourseBloc
final courseRepoFactory = (String schoolId) => FirebaseCourseRepository(schoolId: schoolId);
final courseBloc = CourseBloc(courseRepoFactory: courseRepoFactory, appBloc: appBloc);

This way, whenever the AppBloc updates the school ID, the CourseBloc automatically gets a new repository instance tied to that school, and reloads the courses—no manual ID passing required.

Key Takeaways
  • Shared Temporary State: Use a global AppBloc to manage cross-page variables. For persistence, use hydrated_bloc instead of manual shared_preferences.
  • Firestore Queries: Inject parent document IDs into your repositories via constructors, and tie repository creation to your global state. This keeps your code decoupled, testable, and aligned with BLoC principles.

内容的提问来源于stack exchange,提问作者Christopher Armstrong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:40:20