dart – Flutter: Try to use BlocProvider with get_it and auto_route package but context don’t find provider

I try to make a login page for my app who permits to access when password and username are okay.

I have some problem with my bloc provider. My application uses the auto_route package for routing, get_it for dependency injection and block to handle business logic/status.

The flow is as follows: I initialize the dependencies of my get_it instance in an asynchronous init() function. In these dependencies is among others my AppRouter.

Once this is done, I runApp() my MyApp Widget which returns a MaterialApp.router() in which I delegate the routing to my AppRouter. The router is configured to have my LoginScreen as the initial page.

My Login page is a Scaffold for a child BlocProvider. This BlocProvider creates the bloc thanks to my locator service (get_it) and takes as child my body() widget.

In body() I have a button that takes an onPressed function as parameter. I give it my tryToLogin function as parameter, use the context to find my bloc and send the LoginWithPasswordAndUsername event.

It’s this last step that is stuck: when I click on my button, Flutter tells me that it can’t find my Provider in the BuildContext. I don’t understand where this can come from, is it my router that is at fault? or another element that I didn’t see? The logic seems good to me and I really can’t find it. Could you please help me?

You can find the code below.

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:lansknet/presentation/pages/Login.dart';
import 'package:lansknet/router/router.dart';
import 'package:lansknet/injection_container.dart' as di;

void main() async {
  WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
  FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
  await di.init().then((_) => FlutterNativeSplash.remove());
  runApp(MyApp());
}

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

  final _appRouter = di.getIt<AppRouter>();

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: _appRouter.delegate(),
      routeInformationParser: _appRouter.defaultRouteParser(),
      title: "Lansknet",
      debugShowCheckedModeBanner: false,
      theme: ThemeData(),
      darkTheme: ThemeData.dark(),
    );
  }
}

injection_container.dart

import 'package:get_it/get_it.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:lansknet/core/network_info.dart';
import 'package:lansknet/data/datasources/auth_local_data_source.dart';
import 'package:lansknet/data/datasources/auth_remote_data_source.dart';
import 'package:lansknet/data/repositories/auth_repository_impl.dart';
import 'package:lansknet/domain/repositories/AuthRepository.dart';
import 'package:lansknet/domain/usecases/Login.dart';
import 'package:lansknet/presentation/blocs/bloc/auth_bloc.dart';
import 'package:lansknet/presentation/utils/input_converter.dart';
import 'package:lansknet/router/router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;

final getIt = GetIt.instance;

Future<void> init() async {
  //! Features - Auth
  // Bloc
  getIt.registerFactory(
    () => AuthBloc(login: getIt()),
  );

  // Use cases
  getIt.registerLazySingleton(() => Login(getIt()));

  // Repository
  getIt.registerLazySingleton<AuthRepository>(
    () => AuthRepositoryImpl(
      localDataSource: getIt(),
      networkInfo: getIt(),
      remoteDataSource: getIt(),
    ),
  );

  // Data sources
  getIt.registerLazySingleton<AuthRemoteDataSource>(
    () => AuthRemoteDataSourceImpl(client: getIt()),
  );

  getIt.registerLazySingleton<AuthLocalDataSource>(
    () => AuthLocalDataSourceImpl(sharedPreferences: getIt()),
  );

  //! Core
  getIt.registerLazySingleton(() => InputConverter());
  getIt.registerLazySingleton<NetworkInfo>(() => NetworkInfoImpl(getIt()));

  //! External
  final sharedPreferences = await SharedPreferences.getInstance();
  getIt.registerLazySingleton(() => sharedPreferences);
  getIt.registerLazySingleton(() => http.Client());
  getIt.registerLazySingleton(() => InternetConnectionChecker());

  //! Routes
  getIt.registerSingleton<AppRouter>(AppRouter());
}

router.dart

import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:lansknet/presentation/pages/home.dart';
import 'package:lansknet/presentation/pages/login.dart';
part 'router.gr.dart';

@MaterialAutoRouter(replaceInRouteName: 'Screen,Route', routes: <AutoRoute>[
  AutoRoute(page: LoginScreen, initial: true, path: '/login'),
  AutoRoute(page: HomeScreen, path: '/home')
])
class AppRouter extends _$AppRouter {}

router.gr.dart

// **************************************************************************
// AutoRouteGenerator
// **************************************************************************

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// AutoRouteGenerator
// **************************************************************************
//
// ignore_for_file: type=lint

part of 'router.dart';

class _$AppRouter extends RootStackRouter {
  _$AppRouter([GlobalKey<NavigatorState>? navigatorKey]) : super(navigatorKey);

  @override
  final Map<String, PageFactory> pagesMap = {
    LoginRoute.name: (routeData) {
      return MaterialPageX<dynamic>(routeData: routeData, child: LoginScreen());
    },
    HomeRoute.name: (routeData) {
      return MaterialPageX<dynamic>(
          routeData: routeData, child: const HomeScreen());
    }
  };

  @override
  List<RouteConfig> get routes => [
        RouteConfig('/#redirect',
            path: "https://stackoverflow.com/", redirectTo: '/login', fullMatch: true),
        RouteConfig(LoginRoute.name, path: '/login'),
        RouteConfig(HomeRoute.name, path: '/home')
      ];
}

/// generated route for
/// [LoginScreen]
class LoginRoute extends PageRouteInfo<void> {
  const LoginRoute() : super(LoginRoute.name, path: '/login');

  static const String name="LoginRoute";
}

/// generated route for
/// [HomeScreen]
class HomeRoute extends PageRouteInfo<void> {
  const HomeRoute() : super(HomeRoute.name, path: '/home');

  static const String name="HomeRoute";
}

login.dart problem function is on the low of this

import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lansknet/injection_container.dart';
import 'package:lansknet/presentation/blocs/bloc/auth_bloc.dart';
import 'package:lansknet/presentation/widgets/call_to_action_button.dart';
import 'package:lansknet/presentation/widgets/large_button.dart';
import 'package:lansknet/presentation/widgets/loading_circle.dart';
import 'package:lansknet/presentation/widgets/login_field.dart';
import 'package:lansknet/presentation/widgets/login_input_text_field.dart';
import 'package:lansknet/router/router.dart';
import 'package:provider/provider.dart';
import 'package:lansknet/injection_container.dart';

class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final TextEditingController emailField = TextEditingController();
  final TextEditingController passwordField = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocProvider(
        create: (context) => getIt<AuthBloc>(),
        child: body(context),
      ),
    );
  }

  Widget body(BuildContext context) {
    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: SystemUiOverlayStyle.light,
      child: GestureDetector(
        child: Stack(
          children: <Widget>[
            Container(
                height: double.infinity,
                width: double.infinity,
                decoration: const BoxDecoration(
                  gradient: LinearGradient(
                      begin: Alignment.topCenter,
                      end: Alignment.bottomCenter,
                      colors: [
                        Color.fromRGBO(0, 255, 255, 1),
                        Color.fromRGBO(0, 200, 205, 1),
                        Color.fromRGBO(0, 150, 155, 1),
                        Color.fromRGBO(0, 120, 125, 1),
                      ]),
                ),
                child: SingleChildScrollView(
                  physics: const AlwaysScrollableScrollPhysics(),
                  padding:
                      const EdgeInsets.symmetric(horizontal: 25, vertical: 120),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      const Align(
                        alignment: Alignment.topLeft,
                        child: Text(
                          "Sign In",
                          style: TextStyle(
                              color: Colors.white,
                              fontSize: 40,
                              fontWeight: FontWeight.bold),
                        ),
                      ),
                      const SizedBox(height: 50),
                      LoginField(
                          emailField: emailField, passwordField: passwordField),
                      const CallToActionButton(text: 'Forgot password'),
                      //buildRemeberCb(),
                      LargeButton(
                          name: "Login",
                          onPressed: () =>
                              {tryToLogin(emailField, passwordField)},
                          color: const Color.fromRGBO(0, 200, 205, 1)),
                      BlocBuilder<AuthBloc, AuthState>(
                          builder: (context, state) {
                        if (state is Loading) {
                          return const LoadingCircle();
                        } else if (state is Loaded) {
                          AutoRouter.of(context)
                              .replaceAll([const HomeRoute()]);
                        } else if (state is Error) {
                          return SnackBar(
                            backgroundColor: Colors.redAccent,
                            content: Text(state.message),
                          );
                        }
                        return Container();
                      })
                    ],
                  ),
                ))
          ],
        ),
      ),
    );
  }

  void tryToLogin(TextEditingController email, TextEditingController password) {
    context
        .read<AuthBloc>()
        .add(LoginWithPasswordAndUsername(email.text, password.text));
  }
}

Output

══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
The following ProviderNotFoundException was thrown while handling a gesture:
Error: Could not find the correct Provider<AuthBloc> above this LoginScreen Widget

This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:

- You added a new provider in your `main.dart` and performed a hot-reload.
  To fix, perform a hot-restart.

- The provider you are trying to read is in a different route.

  Providers are "scoped". So if you insert of provider inside a route, then
  other routes will not be able to access that provider.

- You used a `BuildContext` that is an ancestor of the provider you are trying to read.

  Make sure that LoginScreen is under your MultiProvider/Provider<AuthBloc>.
  This usually happens when you are creating a provider and trying to read it immediately.

  For example, instead of:

  Widget build(BuildContext context) {
    return Provider<Example>(
      create: (_) => Example(),
      // Will throw a ProviderNotFoundError, because `context` is associated
      // to the widget that is the parent of `Provider<Example>`
      child: Text(context.watch<Example>()),
    ),
  }

  consider using `builder` like so:

  Widget build(BuildContext context) {
    return Provider<Example>(
      create: (_) => Example(),
      // we use `builder` to obtain a new `BuildContext` that has access to the provider
      builder: (context) {
        // No longer throws
        return Text(context.watch<Example>()),
      }
    ),
  }

If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter

When the exception was thrown, this was the stack:
#0      Provider._inheritedElementOf (package:provider/src/provider.dart:356:7)
#1      Provider.of (package:provider/src/provider.dart:293:30)
#2      ReadContext.read (package:provider/src/provider.dart:656:21)
#3      _LoginScreenState.tryToLogin (package:lansknet/presentation/pages/login.dart:109:10)
#4      _LoginScreenState.body.<anonymous closure> (package:lansknet/presentation/pages/login.dart:81:32)
#5      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:1005:21)
#6      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:198:24)
#7      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:613:11)
#8      BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:298:5)
#9      BaseTapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:269:7)
#10     GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:157:27)
#11     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:449:20)
#12     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:425:22)
#13     RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:329:11)
#14     GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:380:7)
#15     GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:344:5)
#16     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:302:7)
#17     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:285:7)
#21     _invoke1 (dart:ui/hooks.dart:170:10)
#22     PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:331:7)
#23     _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31)
(elided 3 frames from dart:async)

Handler: "onTap"
Recognizer:
  TapGestureRecognizer#b548c
════════════════════════════════════════════════════════════════════════════════════════════════════

Leave a Comment