0

Context: I want to share Language across entire app. Due to that I want to have single instance of LanguageBloc which I can access across application to retrieve it's state which is LanguageModel

Problem: How to solve dependencies? Because I am using onGenerateRoute I don't know how to make one instance BlocProvider from LanguageBloc that can be accessed in different screen. I noticed I am creating each time new instance and of course new instance has no knowledge of LanguageBloc

Code:

main

void main() {
  runApp(App(Routes()));
}

class App extends StatelessWidget {
  final Routes routes;

  const App(this.routes, {super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'test',
      onGenerateRoute: routes.onGenerateRoute,
      home: RepositoryProvider(
        create: (context) => LanguageService(),
        child: InitialScreen(key: UniqueKey()),
      ),
      theme: ThemeData(
        primarySwatch: kPrimary,
        textTheme: kTextTheme,
      ),
    );
  }
}

Route

class Routes {
  Route onGenerateRoute(RouteSettings settings) {
    switch (settings.name) {
      case RoutesNames.initialScreen:
        return MaterialPageRoute(
          builder: (_) => InitialScreen(key: UniqueKey()),
        );
      case RoutesNames.welcomeScreen:
        return MaterialPageRoute(
          builder: (_) => RepositoryProvider(
            create: (context) => UserService(),
            child: WelcomeScreen(key: UniqueKey()),
          ),
        );
      case RoutesNames.privacyPolicyScreen:
        return MaterialPageRoute(
          builder: (_) => PrivacyPolicyScreen(key: UniqueKey()),
        );
      case RoutesNames.termsAndConditionsScreen:
        return MaterialPageRoute(
          builder: (_) => TermsAndConditionScreen(key: UniqueKey()),
        );
      case RoutesNames.homeScreen:
        return MaterialPageRoute(
          builder: (_) => HomeScreen(key: UniqueKey()),
        );
      default:
        return MaterialPageRoute(
          builder: (_) => WelcomeScreen(key: UniqueKey()),
        );
    }
  }
}

InitialScreen

class InitialScreen extends StatelessWidget {
  const InitialScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => LanguageBloc(
        languageService: RepositoryProvider.of<LanguageService>(context),
      )..add(const LanguageInitialEvent()),
      child: Scaffold(
        body: InitialScreenView(
          key: UniqueKey(),
        ),
      ),
    );
  }
}

class InitialScreenView extends StatelessWidget {
  const InitialScreenView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<LanguageBloc, LanguageState>(
      builder: (builderContext, state) {
        if (state is LanguageSuccessfulState) {
          SchedulerBinding.instance.addPostFrameCallback((_) {
            Navigator.pushReplacementNamed(context, RoutesNames.welcomeScreen);
          });
        }
      },
    );

WelcomeScreen

class WelcomeScreen extends StatelessWidget {
  WelcomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => EnterBloc(
        userService: RepositoryProvider.of<UserService>(context),
      )..add(const EnterInitialEvent()),
      child: Scaffold(
        body: WelcomeScreenView(),
      ),
    );
  }
}

class WelcomeScreenView extends StatelessWidget {
  WelcomeScreenView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<EnterBloc, EnterState>(
      builder: (builderContext, state) {
        //...
      }
    );

Question: How in WelcomeScreenView.build I can access LanguageState?

What I have tried:

  • I tried using MultiBlocProvider in front of MaterialApp so I would create only one instance. Sadly that gives null value and it cannot show first screen
  • I tried using context.select<LanguageBloc, LanguageModel?>((LanguageBloc bloc) but first it was giving me null value, but later after a lot of tweaking it gave me Not registed this is probably because I create new instance
  • this is what I want to achieve and I tried make my solution based on this How to use bloc pattern between two screens so I removed BlocProvider in WelcomeScreen and also in InitialScreen. I made MultiBlocProvider in App and register there LanguageBloc and EnterBloc but this gives me type 'Null' is not a subtype of type 'LanguageBloc' in type cast
medevil
  • 13
  • 5

1 Answers1

0

Move the repository to the first level and on the second level place the MultiBlocProvider, to not have other bloc instances, remove the bloc create and repository create from the screens and keep only the blocBuilder. With this, both repositories and blocs will be available in the context of the entire application.

class App extends StatelessWidget {
  final Routes routes;

  const App(this.routes, {super.key});

  @override
  Widget build(BuildContext context) {
    return MultiRepositoryProvider(
      providers: [
        RepositoryProvider<LanguageService>(
          create: (context) => LanguageService(),
        ),
        RepositoryProvider<UserService>(
          create: (context) => UserService(),
        ),
      ],
      child: MultiBlocProvider(
        providers: [
          BlocProvider<LanguageBloc>(
              create: (context) => LanguageBloc(
                    languageService:
                        RepositoryProvider.of<LanguageService>(context),
                  )..add(const LanguageInitialEvent())),
          BlocProvider<EnterBloc>(
              create: (context) => EnterBloc(
                    userService: RepositoryProvider.of<UserService>(context),
                  )..add(const EnterInitialEvent())),
        ],
        child: MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'test',
          onGenerateRoute: routes.onGenerateRoute,
          home: InitialScreen(key: UniqueKey()),
          theme: ThemeData(
            primarySwatch: kPrimary,
            textTheme: kTextTheme,
          ),
        ),
      ),
    );
  }
}
Chance
  • 1,497
  • 2
  • 15
  • 25
  • This is working fine :) Now I can do `context.select((LanguageBloc bloc) { }` One more question. Would that mean that I need to register all services and blocs before MaterialApp? It feels bit like a code smell and performance loss – medevil Oct 16 '22 at 17:32
  • Yes, exactly that. You could also do it on demand, but that would involve creating singletons and services locator. – Chance Oct 16 '22 at 17:39