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

时间:2019-11-17 09:08:23

标签: flutter dart bloc

假设我有一个带有嵌套集合的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(
      ...

问题

  1. 如果我需要获取学校文件ID,除了将CourseRepository注入CourseBloc之外,还需要将SchoolRepository注入吗?然后,首先使用学校存储库检索学校文档ID,然后使用CourseRepository获取所有课程?还是BLOC应该只有一个注入的存储库?

  2. 或者让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

这里有两种方法:

  1. 从Firestore加载学校并将其保存到SharedPreferences
  2. 从共享首选项中加载学校。

代码在这里:

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();
  }
}

1 个答案:

答案 0 :(得分:0)

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