假设我有一个带有嵌套集合的Firestore数据库。一所学校可以有不同的课程,每个课程都有不同的部分,每个部分都有不同的页面。真的很简单就会想到。
从Firestore模式的角度来看,这也很简单。嵌套集合可以很好地解决这个问题。
- schools
- schoolDoc1
- courses
- courseDoc1
- sections
- sectionDoc1
- pages
- pageDoc1
但是现在BLOC进入了带有存储库的图片,以处理数据。这就是让我不清楚的地方。
所以我有一个SchoolBloc,它具有获取学校并将其存储到SharedPreferences中的功能。为什么选择SharedPreferences?因为我需要使用学校文档ID,所以在构造查询以获取CourseBloc中的所有课程时。这些课程是学校文档中的嵌套集合。
Firestore.instance.collection('schools')
.document('<the school document id>')
.collection('courses').snapshot();
到目前为止一切都很好。 SchoolBloc具有两个功能。一个从消防局获得学校并将其保存到SharedPreferences中的人。另一个从SharedPreferences中加载学校。所有这些都可以通过一个存储库完成。
但是现在变得棘手了。当我想将所有课程加载到CourseBloc中时,我将需要先检索学校文档ID,然后才能创建查询以获取所有课程。对于所有查询,我都需要学校文件ID。因此,将id传递给每个执行查询的函数都没有意义。
所以这是我的大脑爆炸的地方,我开始挣扎。如何从逻辑上解决这个问题?
SchoolBloc和CourseBloc都只有一个存储库,注入到main.dart文件中。
class TeachApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>(
builder: (context) {
return AuthenticationBloc(
userRepository: FirebaseUserRepository(),
)..dispatch(AppStarted());
},
),
BlocProvider<SchoolBloc>(
builder: (context) {
return SchoolBloc(
schoolRepository: FirebaseSchoolRepository(),
);
},
),
BlocProvider<CourseBloc>(
builder: (context) {
return CourseBloc(
courseRepository: FirebaseCourseRepository(),
);
},
),
BlocProvider<SectionBloc>(
builder: (context) {
return SectionBloc(
sectionRepository: FirebaseSectionRepository(),
);
},
),
],
child: MaterialApp(
...
问题
如果我需要获取学校文件ID,除了将CourseRepository注入CourseBloc之外,还需要将SchoolRepository注入吗?然后,首先使用学校存储库检索学校文档ID,然后使用CourseRepository获取所有课程?还是BLOC应该只有一个注入的存储库?
或者让CourseRepository从SharedPreferences中检索学校文档ID会更有意义吗?
了解BLOC模式并不难,但是要学习设计复杂应用程序的最佳实践却非常困难,因为那里的所有示例都很简单,或者不使用BLOC模式。试图把我的头脑弄清楚真是令人沮丧。
我不知道什么是好的做法,什么不是。该文档很好,但也留下了很多解释的空间。
代码下方。
main.dart
在这里,我使用MultiBlocProvider初始化块。这也是我处理导航的地方。
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:school_repository/school_repository.dart';
import 'package:teach_mob/core/blocs/authentication/authentication.dart';
import 'package:teach_mob/ui/shared/palette.dart';
import 'package:teach_mob/ui/views/course_add_view.dart';
import 'package:teach_mob/ui/views/course_view.dart';
import 'package:teach_mob/ui/views/home_view.dart';
import 'package:teach_mob/ui/views/login_failed_view.dart';
import 'package:teach_mob/ui/views/section_add_view.dart';
import 'package:teach_mob/ui/views/section_view.dart';
import 'package:user_repository/user_repository.dart';
import 'package:teach_mob/core/blocs/blocs.dart';
import 'core/constants/app_constants.dart';
import 'core/repositories/course_repository/lib/course_repository.dart';
import 'core/repositories/section_repository/lib/section_repository.dart';
void main() async {
BlocSupervisor.delegate = SimpleBlocDelegate();
runApp(TeachApp());
}
class TeachApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>(
builder: (context) {
return AuthenticationBloc(
userRepository: FirebaseUserRepository(),
)..dispatch(AppStarted());
},
),
BlocProvider<SchoolBloc>(
builder: (context) {
return SchoolBloc(
schoolRepository: FirebaseSchoolRepository(),
);
},
),
BlocProvider<CourseBloc>(
builder: (context) {
return CourseBloc(
courseRepository: FirebaseCourseRepository(),
);
},
),
BlocProvider<SectionBloc>(
builder: (context) {
return SectionBloc(
sectionRepository: FirebaseSectionRepository(),
);
},
),
],
child: MaterialApp(
title: "TeachApp",
routes: {
RoutePaths.Home: (context) => checkAuthAndRouteTo(HomeView(), context),
RoutePaths.Course: (context) => checkAuthAndRouteTo(CourseView(), context),
RoutePaths.CourseAdd: (context) => checkAuthAndRouteTo(CourseAddView(
onSave: (id, name, description, teaserImage) {
BlocProvider.of<CourseBloc>(context)
.dispatch(AddCourseEvent(Course(
name: name,
description: description,
teaserImage: teaserImage
))
);
},
isEditing: false,
), context),
RoutePaths.CourseEdit: (context) => checkAuthAndRouteTo(CourseAddView(
onSave: (id, name, description, teaserImage) {
BlocProvider.of<CourseBloc>(context)
.dispatch(UpdateCourseEvent(Course(
id: id,
name: name,
description: description,
teaserImage: teaserImage
))
);
},
isEditing: true
), context),
RoutePaths.Section: (context) => checkAuthAndRouteTo(SectionView(), context),
RoutePaths.SectionAdd: (context) => checkAuthAndRouteTo(SectionAddView(
onSave: (id, name) {
BlocProvider.of<SectionBloc>(context)
.dispatch(AddSectionEvent(Section(
name: name
))
);
},
isEditing: false,
), context),
RoutePaths.SectionEdit: (context) => checkAuthAndRouteTo(SectionAddView(
onSave: (id, name) {
BlocProvider.of<SectionBloc>(context)
.dispatch(UpdateSectionEvent(Section(
id: id,
name: name
))
);
},
isEditing: true
), context)
},
theme: ThemeData(
scaffoldBackgroundColor: Palette.backgroundColor
)
)
);
}
BlocBuilder<AuthenticationBloc, AuthenticationState> checkAuthAndRouteTo(Widget view, BuildContext context) {
return BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is Authenticated) {
return view;
}
if (state is Unauthenticated) {
return LoginFailedView();
}
return Center(child: CircularProgressIndicator());
},
);
}
}
SchoolBloc.dart
这里有两种方法:
代码在这里:
import 'dart:async';
import 'dart:convert';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import 'package:school_repository/school_repository.dart';
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
import 'package:teach_mob/core/blocs/school/school_event.dart';
import 'package:teach_mob/core/blocs/school/school_state.dart';
class SchoolBloc extends Bloc<SchoolEvent, SchoolState> {
final SchoolRepository _schoolRepository;
StreamSubscription _schoolSubscription;
// Repository is injected through constructor, so that it can
// be easily tested.
SchoolBloc({@required SchoolRepository schoolRepository})
: assert(schoolRepository != null),
_schoolRepository = schoolRepository;
// Each Bloc needs an initial state. The state must be
// defined in the state file and can't be null
@override
SchoolState get initialState => SchoolInitState();
// Here we map events to states. Events can also trigger other
// events. States can only be yielded within the main function.
// yielding in listen methods will not work. Hence from a listen
// method, another event has to be triggered, so that the state
// can be yielded.
@override
Stream<SchoolState> mapEventToState(SchoolEvent event) async* {
if(event is LoadSchoolAndCacheEvent) {
yield* _mapLoadSchoolAndCacheEventToState(event);
} else if(event is LoadSchoolFromCacheEvent) {
yield* _mapLoadSchoolFromCacheEvent(event);
} else if(event is SchoolLoadedFromCacheEvent) {
yield* _mapSchoolLoadedFromCacheEvent(event);
} else if(event is SchoolCachedEvent) {
yield* _mapSchoolCachedEventToState(event);
}
}
// Get a single school and store it in shared preferences
Stream<SchoolState> _mapLoadSchoolAndCacheEventToState(LoadSchoolAndCacheEvent event) async* {
final prefs = await StreamingSharedPreferences.instance;
yield SchoolDataLoadingState();
_schoolSubscription?.cancel();
_schoolSubscription = _schoolRepository.school(event.id).listen(
(school) {
final schoolString = json.encode(school.toEntity().toJson());
prefs.setString("school", schoolString);
dispatch(SchoolCachedEvent(school));
}
);
}
// Load the school from shared preferences
Stream<SchoolState> _mapLoadSchoolFromCacheEvent(LoadSchoolFromCacheEvent event) async* {
final prefs = await StreamingSharedPreferences.instance;
yield SchoolDataLoadingState();
final schoolString = prefs.getString("school", defaultValue: "");
schoolString.listen((value){
final Map schoolMap = json.decode(value);
final school = School(id: schoolMap["id"],
name: schoolMap["name"]);
dispatch(SchoolLoadedFromCacheEvent(school));
});
}
// Yield school loaded state
Stream<SchoolState> _mapSchoolLoadedFromCacheEvent(SchoolLoadedFromCacheEvent event) async* {
yield SchoolDataLoadedState(event.school);
}
}
Stream<SchoolState> _mapSchoolCachedEventToState(SchoolCachedEvent event) async* {
yield SchoolDataLoadedState(event.school);
}
CourseBloc.dart
如果查看_mapLoadCoursesToState函数,您将看到,我在存储库类中定义了一个setter以传递学校文档ID,因为在所有查询中都将需要它。不知道是否有更优雅的方法。
在这里,我很困惑,也不知道如何从SharedPreferences中检索学校文档ID。是否认为我以这种方式注入SchoolRepository并检索了文档?还是建议的最佳做法是什么?
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import 'package:teach_mob/core/blocs/course/course_event.dart';
import 'package:teach_mob/core/blocs/course/course_state.dart';
import 'package:teach_mob/core/repositories/course_repository/lib/course_repository.dart';
class CourseBloc extends Bloc<CourseEvent, CourseState> {
final CourseRepository _courseRepository;
StreamSubscription _courseSubscription;
// Repository is injected through constructor, so that it can
// be easily tested.
CourseBloc({@required CourseRepository courseRepository})
: assert(courseRepository != null),
_courseRepository = courseRepository;
@override
get initialState => CourseInitState();
@override
Stream<CourseState> mapEventToState(CourseEvent event) async* {
if(event is LoadCoursesEvent) {
yield* _mapLoadCoursesToState(event);
} else if(event is CoursesLoadedEvent) {
yield* _mapCoursesLoadedToState(event);
} else if(event is AddCourseEvent) {
yield* _mapAddCourseToState(event);
} else if(event is UpdateCourseEvent) {
yield* _mapUpdateCourseToState(event);
} else if(event is DeleteCourseEvent) {
yield* _mapDeleteCourseToState(event);
}
}
// Load all courses
Stream<CourseState> _mapLoadCoursesToState(LoadCoursesEvent event) async* {
yield CoursesLoadingState();
_courseSubscription?.cancel();
_courseRepository.setSchool = "3kRHuyk20UggHwm4wrUI";
_courseSubscription = _courseRepository.courses().listen(
(courses) {
dispatch(
CoursesLoadedEvent(courses),
);
},
);
}
Stream<CourseState> _mapCoursesLoadedToState(CoursesLoadedEvent event) async* {
yield CoursesLoadedState(event.courses);
}
Stream<CourseState> _mapAddCourseToState(AddCourseEvent event) async* {
_courseRepository.addCourse(event.course);
}
Stream<CourseState> _mapUpdateCourseToState(UpdateCourseEvent event) async* {
_courseRepository.updateCourse(event.updatedCourse);
}
Stream<CourseState> _mapDeleteCourseToState(DeleteCourseEvent event) async* {
_courseRepository.deleteCourse(event.course);
}
@override
void dispose() {
_courseSubscription?.cancel();
super.dispose();
}
}
答案 0 :(得分:0)
BLoC是应用程序的业务逻辑发生的地方,因此可以在BLoC内部调用来自存储库的多个Firestore请求。对于此用例,可以调用Firestore查询来获取“课程”所需的“学校”。