使用 get_it 的 Flutter 依赖注入设置问题

Posted

技术标签:

【中文标题】使用 get_it 的 Flutter 依赖注入设置问题【英文标题】:Flutter dependency injection set-up problem with get_it 【发布时间】:2020-10-25 10:51:38 【问题描述】:

我正在尝试学习如何在 Flutter 项目中设置依赖注入,但遇到了一个问题:

I/flutter (14507): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (14507): The following assertion was thrown building _InheritedProviderScope<NumberTriviaBloc>(value: null):
I/flutter (14507): No type GetConcreteNumberTrivia is registered inside GetIt.
I/flutter (14507):  Did you forget to pass an instance name?
I/flutter (14507): (Did you accidentally do  GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;did you
I/flutter (14507): forget to register it?)
I/flutter (14507): 'package:get_it/get_it_impl.dart':
I/flutter (14507): Failed assertion: line 251 pos 14: 'instanceFactory != null'
I/flutter (14507): 
I/flutter (14507): The relevant error-causing widget was:
I/flutter (14507):   _InheritedProviderScope<NumberTriviaBloc>

依赖初始化:

final sl = GetIt.instance;

void init() 
  //! Features - Number Trivia
  //* Bloc
  sl.registerFactory<NumberTriviaBloc>(() => NumberTriviaBloc(concrete: sl(), inputConverter: sl(), random: sl()));

  //* Use cases
  sl.registerLazySingleton<UseCase<NumberTrivia, Params>>(() => GetConcreteNumberTrivia(sl()));
  sl.registerLazySingleton<UseCase<NumberTrivia, NoParams>>(() => GetRandomNumberTrivia(sl()));

  //* Repository
  sl.registerLazySingleton<NumberTriviaRepository>(() => NumberTriviaRepositoryImplementation(
      localDataSource: sl(), networkInfo: sl(), remoteDataSource: sl()));

  //* Data sources
  sl.registerLazySingleton<NumberTriviaRemoteDataSource>(
      () => NumberTriviaRemoteDataSourceImpl(client: sl()));

  sl.registerLazySingleton<NumberTriviaLocalDataSource>(
      () => NumberTriviaLocalDataSourceImpl(sharedPreferences: sl()));

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

  //! External
  sl.registerLazySingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
  sl.registerLazySingleton<http.Client>(() => http.Client());
  sl.registerLazySingleton<DataConnectionChecker>(() => DataConnectionChecker());

我不明白是什么问题,这里是其他东西初始化的代码:

class NumberTriviaBloc extends Bloc<NumberTriviaEvent, NumberTriviaState> 
  final GetConcreteNumberTrivia getConcreteNumberTrivia;
  final GetRandomNumberTrivia getRandomNumberTrivia;
  final InputConverter inputConverter;

  NumberTriviaBloc(
      @required GetConcreteNumberTrivia concrete,
      @required GetRandomNumberTrivia random,
      @required this.inputConverter)
      : assert(concrete != null),
        assert(random != null),
        assert(inputConverter != null),
        getConcreteNumberTrivia = concrete,
        getRandomNumberTrivia = random;
  ...


class GetConcreteNumberTrivia implements UseCase<NumberTrivia, Params>
  final NumberTriviaRepository repository;

  GetConcreteNumberTrivia(this.repository);

  @override
  Future<Either<Failure, NumberTrivia>> call(Params params) async 
    return await repository.getConcreteNumberTrivia(params.number);
  


class GetRandomNumberTrivia implements UseCase<NumberTrivia, NoParams> 
  final NumberTriviaRepository repository;

  GetRandomNumberTrivia(this.repository);

  @override
  Future<Either<Failure, NumberTrivia>> call(NoParams params) async 
    return await repository.getRandomNumberTrivia();
  


class NumberTriviaRepositoryImplementation implements NumberTriviaRepository 
  final NumberTriviaRemoteDataSource remoteDataSource;
  final NumberTriviaLocalDataSource localDataSource;
  final NetworkInfo networkInfo;

  NumberTriviaRepositoryImplementation(
      @required this.remoteDataSource,
      @required this.localDataSource,
      @required this.networkInfo);
  ...


class NumberTriviaRemoteDataSourceImpl implements NumberTriviaRemoteDataSource 
  final http.Client client;

  NumberTriviaRemoteDataSourceImpl(@required this.client);
  ...


class NumberTriviaLocalDataSourceImpl implements NumberTriviaLocalDataSource 
  final SharedPreferences sharedPreferences;

  NumberTriviaLocalDataSourceImpl(@required this.sharedPreferences);
  ...


class InputConverter 

class NetworkInfoImpl extends NetworkInfo 
  final DataConnectionChecker connectionChecker;

  NetworkInfoImpl(this.connectionChecker);
  ...

小部件是:

void main() 
  di.init();
  runApp(MyApp());


class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primaryColor: Colors.green.shade800,
        accentColor: Colors.green.shade600,
      ),
      home: NumberTriviaPage(),
    );
  


class NumberTriviaPage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text('Number Trivia'),
      ),
      body: SingleChildScrollView(child: buildBody(context)),
    );
  

  BlocProvider<NumberTriviaBloc> buildBody(BuildContext context) 
    return BlocProvider<NumberTriviaBloc>(
      create: (_) => sl<NumberTriviaBloc>(),
      child: Center(
        child: Padding(
          padding: const EdgeInsets.all(10),
          child: Column(
            children: <Widget>[
              SizedBox(
                height: 10,
              ),
              BlocBuilder<NumberTriviaBloc, NumberTriviaState>(
                // ignore: missing_return
                builder: (context, state) 
                  if (state is Empty) 
                    return MessageDisplay(
                      message: 'Start searching',
                    );
                   else if (state is Loading) 
                    return LoadingWidget();
                   else if (state is Loaded) 
                    return TriviaDisplay(
                      numberTrivia: state.trivia,
                    );
                   else if (state is Error) 
                    return MessageDisplay(
                      message: state.message,
                    );
                  
                ,
              ),
              SizedBox(
                height: 20,
              ),
              TriviaControl()
            ],
          ),
        ),
      ),
    );
  

我不明白为什么这对我不起作用。 我按照 youtube 上的教程进行操作,并在 github 存储库上检查了本教程中的代码,但这对我没有帮助。 那你能给我解释一下是怎么回事吗?

在 tnc1997 答案的帮助下,我遇到了一个新错误:

lib/injection_container.dart:27:91: Error: The argument type 'NumberTriviaRepository/*1*/' can't be assigned to the parameter type 'NumberTriviaRepository/*2*/'.
 - 'NumberTriviaRepository/*1*/' is from 'package:clean_architecture/features/number_trivia/domain/repositories/number_trivia_repository.dart' ('lib/features/number_trivia/domain/repositories/number_trivia_repository.dart').
 - 'NumberTriviaRepository/*2*/' is from 'lib/features/number_trivia/domain/repositories/number_trivia_repository.dart'.
  sl.registerLazySingleton<UseCase<NumberTrivia, NoParams>>(() => GetRandomNumberTrivia(sl<NumberTriviaRepository>()));

这是同一个文件,下面一行使用了类似的合约

//* Use cases
  sl.registerLazySingleton<UseCase<NumberTrivia, Params>>(() => GetConcreteNumberTrivia(sl<NumberTriviaRepository>()));
  sl.registerLazySingleton<UseCase<NumberTrivia, NoParams>>(() => GetRandomNumberTrivia(sl<NumberTriviaRepository>()));

  //* Repository
  sl.registerLazySingleton<NumberTriviaRepository>(() => NumberTriviaRepositoryImplementation(
      localDataSource: sl<NumberTriviaLocalDataSource>(), networkInfo: sl<NetworkInfo>(), remoteDataSource: sl<NumberTriviaRemoteDataSource>()));

现在我比以前更困惑了

【问题讨论】:

是否有机会添加显示NumberTriviaPage 的代码示例以及显示initialise 依赖关系的代码示例? 能分享一下di.init()的实现吗? 不是您问题的答案,但仍可能对您有用。看看可注射的。它是一个库,它将使用代码生成器根据您放在类上的注释为您创建 get_it 依赖注入设置。 pub.dev/packages/injectable 【参考方案1】:

我很确定在使用 GetIt 包时,您需要依赖接口而不是构造函数中的具体类型。毕竟,依赖注入的原则之一是依赖接口而不是实现,从而允许您轻松切换实现。我在下面添加了一个代码示例,展示了如何做到这一点。如果您还没有这样做,您可能需要查看文档here 以获取更多信息和代码示例。如果您对代码示例有任何问题,请随时发表评论!

class NumberTriviaBloc extends Bloc<NumberTriviaEvent, NumberTriviaState> 
  final UseCase<NumberTrivia, Params> getConcreteNumberTrivia;
  final UseCase<NumberTrivia, NoParams> getRandomNumberTrivia;
  final InputConverter inputConverter;

  NumberTriviaBloc(
      @required UseCase<NumberTrivia, Params> concrete,
      @required UseCase<NumberTrivia, NoParams> random,
      @required this.inputConverter)
      : assert(concrete != null),
        assert(random != null),
        assert(inputConverter != null),
        getConcreteNumberTrivia = concrete,
        getRandomNumberTrivia = random;
  ...

sl.registerFactory<NumberTriviaBloc>(() => NumberTriviaBloc(concrete: sl<UseCase<NumberTrivia, Params>>(), inputConverter: sl<InputConverter>(), random: sl<UseCase<NumberTrivia, NoParams>>()));

【讨论】:

谢谢你的回复,但我遇到了一个新错误,我编辑了我的帖子,现在它包含了它,你能检查一下吗? 这可能是不同导入语句格式的问题。您能否仔细检查您的所有导入语句是否使用相同的格式,并且您没有一个像这样package:clean_architecture/features/... 写的导入语句和另一个像这样写lib/features/... 的导入语句?

以上是关于使用 get_it 的 Flutter 依赖注入设置问题的主要内容,如果未能解决你的问题,请参考以下文章

剖析Flutter的常用库get_it

Flutter get_it 和 FutureBuilder 无法使用热重载

Flutter/Dart get_It 单例定位器方法失败

如何将 get_it 用于 Provider.of<ExampleProvider>(context)?

Flutter - 使用依赖注入时何时关闭流

有啥方法可以将提供者依赖注入到 Flutter 中的普通类中?