felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.79k stars 3.39k forks source link

How to handle BLOC architecture for this #1341

Closed callawey closed 4 years ago

callawey commented 4 years ago

There is very good amount of examples but not a single complex one

Now lets say this is my route design

Home->Visit Plans->Visit Plan Rows->Retailer Detail->Sales (on sales, products, basket, calculations etc.)

Now from top to down, meaning on creating sales, i have to update all of them till Visit Plans route.

For example, Updating is visited flag for retailer, or Completed flag for Visit Plan when sales created.

I already implemented an architecture for this with a Bloc for each route and providing previous BLoc on child route but i am not sure is this the correct way to handle it. For now, its working perfectly but still i want some opinion.

This is my router class with Blocs from top to sales screens:

class Router {
  final _visitPlansBloc = VisitPlansBloc(
    visitPlansRepository: DBVisitPlansRepository(),
  );

  final _visitPlanRowsBloc = VisitPlanRowsBloc(
    visitPlanRowsRepository: DBVisitPlanRowsRepository(),
  );

  final _retailerBloc = RetailerBloc(
    retailerRepository: DBRetailerRepository(),
  );

  Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case RouteNames.startRoute:
        return MaterialPageRoute(builder: (_) => SplashPage());
      case RouteNames.loginRoute:
        return MaterialPageRoute(builder: (_) => LoginPage());
      case RouteNames.homeRoute:
        return MaterialPageRoute(
            builder: (_) => MultiBlocProvider(providers: <BlocProvider>[
                  BlocProvider<SyncBloc>(
                    create: (context) => SyncBloc(syncService: SyncService(), userBloc: BlocProvider.of<UserBloc>(context)),
                  )
                ], child: HomePage()));
      case RouteNames.visitPlanRoute:
        return MaterialPageRoute(builder: (_) => BlocProvider.value(value: _visitPlansBloc, child: VisitPlanPage()));
      case RouteNames.visitPlanRowsRoute:
        var data = settings.arguments as VisitPlanModel;
        return MaterialPageRoute(builder: (_) => BlocProvider.value(value: _visitPlansBloc, child: BlocProvider.value(value: _visitPlanRowsBloc, child: VisitPlanRowPage(visitPlanModel: data))));
      case RouteNames.visitPlanRowDetailRoute:
        var data = settings.arguments as VisitPlanRowModel;
        return MaterialPageRoute(
            builder: (_) => MultiBlocProvider(providers: <BlocProvider>[
                  BlocProvider<VisitPlansBloc>.value(value: _visitPlansBloc),
                  BlocProvider<VisitPlanRowsBloc>.value(value: _visitPlanRowsBloc),
                  BlocProvider<RetailerBloc>.value(value: _retailerBloc)
                ], child: RetailerDetailPage(visitPlanRowModel: data)));
      case RouteNames.salesConfirmationRoute:
        var data = settings.arguments as VisitPlanRowModel;
        return MaterialPageRoute(
            builder: (_) => MultiBlocProvider(providers: <BlocProvider>[
                  BlocProvider<VisitPlansBloc>.value(value: _visitPlansBloc),
                  BlocProvider<VisitPlanRowsBloc>.value(value: _visitPlanRowsBloc),
                  BlocProvider<RetailerBloc>.value(value: _retailerBloc),
                  BlocProvider<OrderMainBloc>(
                    create: (context) => OrderMainBloc(
                      orderMainRepository: DBOrderMainRepository(),
                    )..add(LoadOrderMain(data.vphId)),
                  ),
                  BlocProvider<OrderProductsBloc>(
                    create: (context) => OrderProductsBloc(orderMainBloc: BlocProvider.of<OrderMainBloc>(context), productRepository: DBProductRepository(), visitPlanId: data.vphId),
                  ),
                  BlocProvider<OrderProductsFilterBloc>(
                    create: (context) => OrderProductsFilterBloc(orderProductsBloc: BlocProvider.of<OrderProductsBloc>(context)),
                  )
                ], child: SalesConfirmationPage(visitPlanRowModel: data)));
      default:
        return MaterialPageRoute(
            builder: (_) => Scaffold(
                  body: Center(child: Text('No route defined for ${settings.name}')),
                ));
    }
  }

  void dispose() {
    _visitPlansBloc.close();
    _visitPlanRowsBloc.close();
    _retailerBloc.close();
  }
}
felangel commented 4 years ago

Hi @callawey πŸ‘‹ Thanks for opening an issue!

The approach you've provided seems totally reasonable. One suggestion for potentially improving/simplifying things would be to use nested Navigators for each "feature" or "flow" and that way you can provide the blocs required above the Navigator and not need to pass the blocs through each route push via BlocProvider.value.

For example:

BlocProvider(
  create: (context) => FeatureABloc(),
  child: Navigator(onGenerateRoute: (_) => FeatureAPage.route()),
);
class FeatureAPage extends StatelessWidget {
  static Route route() {
    return MaterialPageRoute(builder: (_) => FeatureAPage());
  }

  @override
  Widget build(BuildContext context) { ... }
}

With this approach the FeatureABloc instance is available even across multiple MaterialPageRoutes within FeatureAPage. Hope that helps πŸ‘

felangel commented 4 years ago

Closing for now but feel free to comment with additional questions and I'm happy to continue the conversation πŸ‘