Skip to content

Factory not registered? It is! #137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
migalv opened this issue Dec 8, 2020 · 7 comments
Closed

Factory not registered? It is! #137

migalv opened this issue Dec 8, 2020 · 7 comments

Comments

@migalv
Copy link

migalv commented Dec 8, 2020

I'm getting an error telling me that I don't have a factory registered, but I'm trying to get it

This is the class I'm trying to get but as you can see it's annotated with @Injectable

auth_bloc.dart

import 'package:freezed_annotation/freezed_annotation.dart';

@injectable
class AuthBloc extends Bloc<AuthEvent, AuthState> {

}

Also in the injection.config.dart you can clearly see that the AuthBloc has it's factory defined

Future<GetIt> $initGetIt(
  GetIt get, {
  String environment,
  EnvironmentFilter environmentFilter,
}) async {
  final gh = GetItHelper(get, environment, environmentFilter);

  ...
  final sharedPreferences =
      await sharedPreferencesInjectableModule.sharedPreferences;
  gh.factory<SharedPreferences>(() => sharedPreferences);
  gh.factory<AuthBloc>(() => AuthBloc(get<IAuthRepository>()));

  ...

  return get;
}

This is my main.dart

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  configureInjection(Environment.dev);
  runApp(MyApp());
}

Here is where I'm trying to getIt

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<AuthBloc>(
          create: (context) {
            return getIt<AuthBloc>() // <============ HERE
              ..add(
                const AuthEvent.authCheckRequested(), 
              );
          },
        ),
      ],
      child: MaterialApp(
         ...
      ),
    );
  }
}

This is the injection.dart file

import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'package:sales_app/injection.config.dart';

final GetIt getIt = GetIt.instance;

@injectableInit
void configureInjection(String env) {
  $initGetIt(getIt, environment: env);
}

This is the error message I'm getting

Object/factory with  type AuthBloc is not registered inside GetIt. 
(Did you accidentally do  GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;
Did you forget to register it?)
'package:get_it/get_it_impl.dart':
Failed assertion: line 298 pos 9: 'instanceFactory != null'

These are my pubspeck.yaml dependencies

...
dependencies:
  injectable: ^1.0.5
...
dev_dependencies:
  injectable_generator: ^1.0.6
...

Please help, I need to turn this code next week! D:

@escamoteur
Copy link
Collaborator

I think I spotted it in your config.dart file

 gh.factory<SharedPreferences>(() => sharedPreferences);

I think that should be

 gh.factory<SharedPreferences>(() => sharedPreferences());

@migalv
Copy link
Author

migalv commented Dec 9, 2020

I think I spotted it in your config.dart file

 gh.factory<SharedPreferences>(() => sharedPreferences);

I think that should be

 gh.factory<SharedPreferences>(() => sharedPreferences());

Hey @escamoteur thanks for the reply, but that's not the issue because sharedPreferencies is instantiated just above the factory registration.

I think it has to be because of the await & async functionality, something is not working correctly

@escamoteur
Copy link
Collaborator

Sorry, missed that. Yeah, your problem is your init function is not awaited as it looks like injectable doesn't know how to deal with async singeltons.
I think you should register it as an asyncSingleton and use allReady() together with a FutureBuilder see
https://github.com/fluttercommunity/get_it#asynchronous-singletons

@escamoteur
Copy link
Collaborator

did you solve it?

@migalv
Copy link
Author

migalv commented Dec 10, 2020

Hey sorry, yes I solved it.

I had to make the configureInjection method to be asynchronous like this:

import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'package:sales_app/injection.config.dart';

final GetIt getIt = GetIt.instance;

@injectableInit
// Add => Future<void>
Future<void> configureInjection(String env) {
  return $initGetIt(getIt, environment: env);
}

Then await it in the main method like so:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  await configureInjection(Environment.dev);
  runApp(MyApp());
}

Yeah, I think the developers from Injectable need to add the possibility to create async factories.

Thanks for your help!

@migalv migalv closed this as completed Dec 10, 2020
@escamoteur
Copy link
Collaborator

Glad to hear this.
Instead of awaiting in main it's a good idea to use a futurebuilder with a loading screen like I show in the readme.

@migalv
Copy link
Author

migalv commented Dec 11, 2020

Oh yeah, I was thinking of doing that. But I don't know how to exactly to do so, because this is how my code looks right now.

My MaterialApp widget is like this

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<AuthBloc>(
          create: (context) {
            return getIt<AuthBloc>()..add(const AuthEvent.authCheckRequested());
          },
        ),
      ],
      child: MaterialApp(
        title: 'MPower Sales App',
        builder: ExtendedNavigator.builder<AppRouter>(
          initialRoute: "/",
          router: AppRouter(),
          builder: (context, extendedNav) => Theme(
            data: MPowerBasicTheme().themeData,
            child: extendedNav,
          ),
        ),
      ),
    );
  }
}

I'm using the BLoC library to provide the AuthBloc (which determines if the user is logged in or not) to all the widgets in my app. For that reason, I have a MultiblocProvider as the parent of my MaterialApp

As you can see, I'm using getIt to get the AuthBloc from there, so I need to have the getIt injections configured by there.

I'm also using a Splash Page to decide whether to show the Login Page or the Home Page. This Splash Page is the initial route of my navigator "/", so it's a child of the MaterialApp

class SplashPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocListener<AuthBloc, AuthState>(
      listener: (context, state) {
        state.when(
          waiting: () {},
          authenticated: (user) {
            context.navigator.popAndPush(Routes.customerListPage);
          },
          unauthenticated: () {
            context.navigator.popAndPush(Routes.loginPage);
          },
        );
      },
      child: SplashScreen.callback(
        name: "assets/animations/loading_mpower_logo_gray.flr",
        loopAnimation: 'Loading',
        backgroundColor: Colors.white,
        height: 64,
        width: 64,
        isLoading: true,
        onSuccess: (_) {},
        onError: (_, __) {},
      ),
    );
  }
}

And I feel like this would be the perfect spot to add any awaits (like await Firebase.initializeApp(); or await configureInjection(Environment.dev);) so while the Splash Page is loading I perform all these asynchronous operations.

As you can also see I'm using a popAndPush approach, this way I don't have a Splash page as the child of all my widgets. The issue is that, if I await for the getIt configuration in the Splash Page, then use getIt to get the AuthBloc to decide which page to show, I would lose the BlocProvider because I'm using the popAndPush approach and the BlocProvider would get disposed from the tree.

If I did all of this my code would like something like this:

My MaterialApp widget would like like this. I remove the BlocProvider.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'MPower Sales App',
      builder: ExtendedNavigator.builder<AppRouter>(
        initialRoute: "/",
        router: AppRouter(),
        builder: (context, extendedNav) => Theme(
          data: MPowerBasicTheme().themeData,
          child: extendedNav,
        ),
      ),
    );
  }
}

Then I would change my Splash Page to something along these lines:

class SplashPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SplashScreen.callback(
      name: "assets/animations/loading_mpower_logo_gray.flr",
      loopAnimation: 'Loading',
      backgroundColor: Colors.white,
      height: 64,
      width: 64,
      onSuccess: (state) {
          state.when(
            waiting: () {},
            authenticated: (user) {
              context.navigator.popAndPush(Routes.customerListPage);
            },
            unauthenticated: () {
              context.navigator.popAndPush(Routes.loginPage);
            },
         );
      },
      onError: (_, __) {},
      until: _initializeAppResources,
    );
  }
}

Future _initializeAppResources() async {
  // We initialize any app Resources
  await Firebase.initializeApp();
  await configureInjection(Environment.dev);

  // One the getIt configuration is finished we can get the AuthBloc and await for the first state
  final authBloc = getIt<AuthBloc>()..add(const AuthEvent.authCheckRequested());
  return await authBloc.sate.first;
}

I'm not sure about the authBloc.state.first part, but I think I could change it to listen to the state changes instead of awaiting.
The issue is that, I don't know where to place the AuthBloc Provider, and I think it's a good practice to have the AuthBloc at the top of your widget tree, this way you can always now if your user is authenticated or not.

I hope I explained myself. It would be super helpful if you could illuminate me with a solution to put all those asynchronous functions in the SplashPage :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants