2
votes

I'm using the BLOC pattern to authenticate a user in my app. I have a main BlocProvider that wraps my app. And a BlocBuilder to build according to the authentication state.

If the user is unauthenticated i have onboarding / intro screens that will navigate to the login screen.

The login screen is wrapped in another BlocProvider that contains a button that will do the login, and add a logged in event when the login is successful.

Problem is when i navigate from the onboarding screens i loose the main authenticationBloc context. What do i need to to to have access to the authentication bloc after i pushed a new screen.

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  Bloc.observer = SimpleBlocObserver();

  runApp(
    MyApp(),
  );
}

class AuthenticationWrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider<AuthenticationBloc>(
        create: (context) => AuthenticationBloc()..add(AppStarted()),
        child: MyApp(),
      ),
    );
  }
}
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return BlocListener<AuthenticationBloc, AuthenticationState>(
      listener: (context, state) {
        if (state is Authenticated) {
          _appUserProfileRepository = AppUserProfileRepository();
        }
      },
      child: BlocBuilder<AuthenticationBloc, AuthenticationState>(
        builder: (context, state) {

          _authCredentialHelper = state.authCredentialHelper;

          if (state is Uninitialized) {
            return SplashScreen();
          }

          if (state is Unauthenticated) {
            return OnboardingScreens(authCredentialHelper: _authCredentialHelper);
          }

          if (state is InvalidRegistration) {
            return RegisterProfileScreen(authCredentialHelper: _authCredentialHelper);
          }

          if (state is Authenticated) {
              xxx
          }

          return Scaffold(body: Center(child: LoadingIndicator()));
        },
      ),
    );
  }
}

This is the onboarding screen where i loose the authenticationbloc context as soon as i navigate

class OnboardingScreens extends StatelessWidget {
  final AuthCredentialHelper authCredentialHelper;

  OnboardingScreens({this.authCredentialHelper});

  _pages(BuildContext context) {
    return [
      xxx
    ];
  }

  _getStartedClicked(BuildContext context) {
    Navigator.push(context, MaterialPageRoute(builder: (context) {
      return LoginScreen(authCredentialHelper: authCredentialHelper);
    }));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: IntroductionScreen(
          pages: _pages(context),
          onDone: () => _getStartedClicked(context),
          showSkipButton: true,
          done: xxx
        ),
      ),
    );
  }
}

When adding a breakpoint at 1. the context is fine with a valid value for BlocProvider.of(context)

Stepping to 2. gives me an error: BlocProvider.of() called with a context that does not contain a Cubit of type AuthenticationBloc.

  _getStartedClicked(BuildContext context) {
    1----->Navigator.push(context, MaterialPageRoute(builder: (context) {
    2----->return LoginScreen(authCredentialHelper: authCredentialHelper);
    }));
  }

This is the LoginScreen code

class LoginScreen extends StatelessWidget {
  final AuthCredentialHelper authCredentialHelper;

  LoginScreen({this.authCredentialHelper});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.arrow_back, color: darkBlue),
          onPressed: () => Navigator.of(context).pop(),
        ),
        backgroundColor: Colors.transparent,
        elevation: 0.0,
      ),
      body: SafeArea(
        child: Center(
          child: BlocProvider<LoginBloc>(
            create: (context) => LoginBloc(authCredentialHelper: authCredentialHelper),
            child: LoginForm(authCredentialHelper: authCredentialHelper),
          ),
        ),
      ),
    );
  }
}

Getting this error:

The following assertion was thrown building _InheritedProviderScope<LoginBloc>(value: Instance of 'LoginBloc'):
BlocProvider.of() called with a context that does not contain a Cubit of type AuthenticationBloc.

No ancestor could be found starting from the context that was passed to BlocProvider.of<AuthenticationBloc>().

This can happen if the context you used comes from a widget above the BlocProvider.
1
You can either manually pass the authbloc instance to the new route and re- provide it with another BlocProvider, or just don't bother with Navigator routes and have another Bloc layer to handle which page is displayed. - Kris
This is because when you call navigator.push(context, ... (context){ ...}), the second context is different from the first. - Kris

1 Answers

4
votes

Change this :

   Navigator.push(context, MaterialPageRoute(builder: (context) {
      return LoginScreen(authCredentialHelper: authCredentialHelper);
   }));

to

   Navigator.push(
     context,
     MaterialPageRoute(builder: (contextLoginScreen) {
        return BlocProvider.value(
            value: context.bloc<AuthenticationBloc>(),
            child: LoginScreen(authCredentialHelper: authCredentialHelper));
     }),
   );