0

I try to make a login page for my app who permit 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: '/', 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
════════════════════════════════════════════════════════════════════════════════════════════════════
TheSpyer56
  • 1
  • 1
  • 2
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community May 24 '22 at 04:02

4 Answers4

1

First and foremost, you try to use 2 way different solutions for achieving the very same goal for 2 different parts of the app. The goal is Dependency Inversion. The different parts are:

  1. providing BLoC
  2. providing everything else

One way is to use get_it Service Locator. The second is BLoC canonical Dependency Injection via BlocProvider. For lower layers you should use RepositoryProvider or simple constructor Dependency Injection based on injecting dependency to sub-tree which is canonical to Flutter.

IMO the answer to your question is that you just should stay with the only one way to avoid potential problems.

PS By the way in your example AuthBloc is provided via getIt.registerFactory() which provides BLoC in essentialy different way than BlocProvider. Former creates new Bloc each time, latter provides a singleton.

PrzemekTom
  • 1,328
  • 1
  • 13
  • 34
  • I've found slightly different and way more accurate opinion regarding dependency injection topic: https://stackoverflow.com/a/69296696/1714102 – PrzemekTom Feb 14 '23 at 20:15
0

Maybe you are missing a builder?

Try the code below.

 @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocProvider(
        create: (context) => getIt<AuthBloc>(),
        child: Builder(
          builder: (context) => body(context),
        ),
      ),
    );
  }
Bruno J.
  • 248
  • 2
  • 13
0

You provide the bloc, but not the BlocBuilder. See below (I combined the build and body methods, since build() just called body()).

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocProvider(
        create: (context) => getIt<AuthBloc>(),
        child: BlocBuilder<AuthBloc, AuthState>(
          builder: (context, state) {
    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();
                      })
                    ],
                  ),
                ))
          ],
        ),
      ),
    );
  }
TarHalda
  • 1,050
  • 1
  • 9
  • 27
0

Use implements AutoRouteWrapper from Auto Route Package

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

@override
Widget wrappedRoute(BuildContext context) {          
return BlocProvider(create: (ctx)=> getIt<AuthBloc>(), child: this);          
 }
}