gpt4 book ai didi

flutter_bloc - 每个 Bloc 有多个存储库,还是只有一个?

转载 作者:行者123 更新时间:2023-12-03 20:59:52 27 4
gpt4 key购买 nike

假设我有一个带有嵌套集合的 firestore 数据库。一所学校可以有不同的类(class),每门类(class)有不同的部分,每个部分有不同的页面。真的很简单。

从 Firestore 模式的角度来看,这也很简单。嵌套集合优雅地解决了这个问题。

- schools
- schoolDoc1
- courses
- courseDoc1
- sections
- sectionDoc1
- pages
- pageDoc1

但是现在 BLOC 使用存储库来处理数据。这就是我不清楚的地方。

所以我有一个 SchoolBloc,它具有获取学校并将其存储到 SharedPreferences 中的功能。为什么使用 SharedPreferences?因为我需要使用学校文档id,所以在构造查询的时候获取CourseBloc中的所有类(class)。这些类(class)是学校文档中的嵌套集合。
Firestore.instance.collection('schools')
.document('<the school document id>')
.collection('courses').snapshot();

到目前为止一切都很好。 SchoolBloc 有两个功能。一个从firestore获取学校并将其保存到SharedPreferences中。另一个从 SharedPreferences 加载学校。这一切都可以通过一个存储库完成。

但现在变得棘手了。当我想加载 CourseBloc 中的所有类(class)时,我需要先检索学校文档 ID,然后才能创建查询,以获取所有类(class)。我将需要所有查询的学校文档 ID。因此,将 id 传递给执行查询的每个单独函数是没有意义的。

所以这就是我的大脑爆炸的地方,我开始挣扎。我该如何从逻辑上解决这个问题?

每个 SchoolBloc 和 CourseBloc 都只有一个存储库,它被注入(inject)到 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 之外,将 SchoolRepository 注入(inject) CourseBloc 是否有意义?然后首先使用学校存储库检索学校文档ID,然后使用CourseRepository 获取所有类(class)?或者一个 BLOC 是否应该只有一个被注入(inject)的存储库?
  • 或者让 CourseRepository 从 SharedPreferences 中检索学校文档 ID 是否更有意义?

  • 理解 BLOC 模式并不难,但是学习设计复杂应用程序的最佳实践却非常困难,因为那里的所有示例都非常简单,或者不使用 BLOC 模式。试图解决这个问题真的很令人沮丧。

    我不知道什么是好的做法,什么不是。文档很好,但也留下了很大的解释空间。

    代码下方。

    main.dart

    这里我使用 MultiBlocProvider 来初始化 block 。这也是我处理导航的地方。
    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

    这里有两种方法:
  • 从 Firestore 加载 School 并将其保存到 SharedPreferences
  • 从共享首选项加载学校。

  • 这里的代码:
    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。是否认为我注入(inject) 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();
    }
    }

    最佳答案

    BLoC 是应用程序的业务逻辑发生的地方,因此可以在 BLoC 内部调用来自存储库的多个 Firestore 请求。对于这个用例,可以调用 Firestore 查询来获取“类(class)”所需的“学校”。

    关于flutter_bloc - 每个 Bloc 有多个存储库,还是只有一个?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58898985/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com