基于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.
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.
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.
- Shared Temporary State: Use a global
AppBlocto manage cross-page variables. For persistence, usehydrated_blocinstead of manualshared_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

