Dagger 2在Android中未注入字段

4
我正在尝试创建我的第一个 Android Dagger 应用程序。 我阅读了一些教程,现在正在努力让它运行起来。 问题是,在 MainActivity 中调用 settings.get() 时,我得到了


java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object javax.inject.Provider.get()' on a null object reference
    at biz.golek.whattodofordinner.MainActivity.onOptionsItemSelected(MainActivity.java:57)

这是:

settings.get().Run();

我创建了:

MainActivity:

public class MainActivity extends AppCompatActivity {

    @Inject
    Provider<ShowSettingsController> settings;

    /.../

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            settings.get().Run();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

应用:

public class WhatToDoForDinnerApp extends com.orm.SugarApp {

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityInjector());
    }

}

ActivityInjector:

public class ActivityInjector implements Application.ActivityLifecycleCallbacks {
    private ApplicationComponent component;

    public ActivityInjector(){
        this.component = DaggerApplicationComponent.builder()
                .settingsModule(new SettingsModule())
                .build();
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState)       {
        component.inject(activity);
    }

    /.../
}

应用组件:

@Singleton
@Component(modules = { SettingsModule.class })
public interface ApplicationComponent {
    void inject(Activity activity);
}

并且是SettingsModule模块:

@Module
public class SettingsModule {

    @Provides
    @Singleton
    static ShowSettingsController provideShowSettingsController(){
        return new ShowSettingsControllerImpl();
    }

    /.../
}

整个代码在我的Github上,链接为:whattodofordinner。 我有什么错误的想法吗? 提前感谢您。

我正在学习Dagger 2,但我认为你需要一个组件来处理你的设置模块。这个教程对我很有帮助:https://blog.gouline.net/2015/05/04/dagger-2-even-sharper-less-square/ - dazza5000
4个回答

1

我认为在你的ApplicationComponent中,你需要为每个Activity定义一个inject方法。无法使用组合。

@Singleton
@Component(modules = { SettingsModule.class })
public interface ApplicationComponent {
   void inject(MainActivity activity);
   void inject(AnotherActivity activity);
}

编辑于 04.02.16:

此外,你的 provides 方法是静态声明的。尝试将其去除静态声明。

@Module
public class SettingsModule {

   @Provides
   @Singleton
   ShowSettingsController provideShowSettingsController(){
      return new ShowSettingsControllerImpl();
   }

   /.../
}

您还可以在ApplicationComponent中声明一个provides语句:
@Singleton
@Component(modules = { SettingsModule.class })
public interface ApplicationComponent {

   SettingsController settingsController();

   void inject(MainActivity activity);
   void inject(AnotherActivity activity);
}

我已经检查过了,还是不起作用。我在ActivityInjector.onActivityCreated中添加了一个仅供检查的MainActivity类型转换,并将inject方法的签名更改为MainActivity。 - Bartosz Gołek
你尝试过清理并重新构建你的项目吗?很抱歉,我没有更多的想法。 - Christopher
是的,我做了。也没有什么帮助。 - Bartosz Gołek
@BartoszGołek:请尝试从provides方法中删除static关键字。请查看我的更新答案。 - Christopher
我找到了另一种解决方案,但根据Dagger用户指南,提供者方法可以是静态的:链接。请参阅“满足依赖关系”部分的示例。 非常感谢您的帮助。 - Bartosz Gołek

0

看起来你的ApplicationComponent中缺少SettingsModule。如果你想让设置模块在整个应用程序范围内可用,那么请像这样将其添加到模块列表中:

@Singleton
@Component(modules = { AppModule.class, SettingsModule.class })
public interface ApplicationComponent {
    void inject(Activity activity);
}

你的ActivityInjector也需要进行更新:

public class ActivityInjector implements Application.ActivityLifecycleCallbacks {
private ApplicationComponent component;

public ActivityInjector(){
    this.component = DaggerApplicationComponent.builder()
            .appModule(new AppModule())
            .settingsModule(new SettingsModule())
            .build();
}

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState)       {
    component.inject(activity);
}

/.../

我在发布之前清理了代码,看起来我不小心删除了模块。我已经在问题中修复了它。 - Bartosz Gołek
你应该明确地表明你已经编辑了问题并提供了正确的答案,否则对于任何来访者来说都可能会产生困惑。 - Mike Scamell
1
提供者应避免在应用程序的整个生命周期内持有引用,并进行延迟初始化。据我所读,这是 Dagger 的一部分。 - Bartosz Gołek
我没有意识到,谢谢你让我知道。我会编辑我的答案。 - Mike Scamell
1
是的,我知道这一点,Provider正是我想要的。当主活动被实例化时,没有必要实例化设置控制器。然后,如果它没有被使用,就应该被垃圾回收器收集。当我再次调用设置时,我会再次调用get,Dagger将创建Controller及其依赖项的新实例。然后只有容器负责决定控制器是单个实例还是多个实例。而不是使用它的类的实现。 - Bartosz Gołek
显示剩余2条评论

0

我终于让它工作了。不知道为什么,但是 Dagger 不想通过字段注入依赖项,但使用构造函数完美地工作。在我看来,使用构造函数甚至更好(通过构造函数参数显式显示依赖项),因此我已更改我的类以使用注入的构造函数。

第二件事是如何将依赖项放入 Activity 中,而不使其意识到容器(组件)。

我通过定义 IAware 接口来实现:

public interface IAware<T> {
    void Set(T item);
}

然后是标记接口IControllersProviderAware:

public interface IControllersProviderAware extends IAware<ControllersProvider> {
}

ControllersProvider类也不知道容器,它只知道javax.inject,因为使用了Provider类,但它是通用的,不依赖于Dagger(容器特定实现):

public class ControllersProvider {
    private final Provider<ShowSettingsController> showSettingsControllerProvider;

    public ControllersProvider(Provider<ShowSettingsController> showSettingsControllerProvider)
    {
        this.showSettingsControllerProvider = showSettingsControllerProvider;
    }

    public ShowSettingsController getShowSettingsController() {
        return showSettingsControllerProvider.get();
    }
}

最后要做的事情是实现ControllersProviderInjector,它负责在activity知道ControllersProvider的情况下将其设置为activity:
    public class ControllersProviderInjector implements Application.ActivityLifecycleCallbacks {

    private ControllersProvider controllersProvider;

    @Inject
    public ControllersProviderInjector(ControllersProvider controllersProvider) {
        this.controllersProvider = controllersProvider;
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        if (activity instanceof IControllersProviderAware)
            ((IControllersProviderAware)activity).Set(controllersProvider);
    }
}

所有内容都在模块中注册:

@Module
public class AppModule {

    @Provides
    @Singleton
    static ControllersProvider provideControllersProvider(
            Provider<ShowSettingsController> showSettingsControllerProvider
    ){
        return new ControllersProvider(
            showSettingsControllerProvider
        );
    }
}

最后,实现Activity的部分:
public class MainActivity extends AppCompatActivity implements IControllersProviderAware {

    private ControllersProvider controllerProvider;

    /.../

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            this.controllerProvider.getShowSettingsController().Run();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void Set(ControllersProvider item) {
        this.controllerProvider = item;
    }
}

这样做的主要优点是将“复杂性”与将getComponent方法添加到App并从Activity的onCreate中使用它(如此处所示)分离出来,使业务逻辑独立于容器特定内容。当有任何比dagger更好的容器时,只需更改我的ControllersProvider实现和容器特定内容即可更改它。

当然,所有内容都在我的github上:https://github.com/bartoszgolek/whattodofordinner

感谢每个人的帮助,希望您会喜欢我的工作。


0

我在github上查看了您的项目,并注意到您对dagger框架的使用有些误解。我进行了一些修复,依赖注入正常。

首先,您需要使您的依赖关系图可供其他应用程序组件使用。

public class WhatToDoForDinnerApp extends com.orm.SugarApp {
    private ApplicationComponent component;

    public ApplicationComponent getComponent() {
        return component;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        component = DaggerApplicationComponent.builder()
            .appModule(new AppModule())
            .settingsModule(new SettingsModule())
            .addNewDinnerModule(new AddNewDinnerModule())
            .build();
        ...
    }
}

然后你需要告诉Dagger哪个类需要注入。

@Singleton
@Component(modules = {AppModule.class, SettingsModule.class, AddNewDinnerModule.class})
public interface ApplicationComponent {
    ViewStateManager viewStateManager();
    ControllersProviderInjector controllersProviderInjector();

    void inject(MainActivity activity); //now you are able to inject dependencies in your MainActivity
}

最后,您需要注入所需的内容。 在本例中,您可以注入在ApplicationComponent声明的所有模块(AppModuleSettingsModuleAddNewDinnerModule)提供的所有项。
public class MainActivity extends AppCompatActivity {

private View.OnClickListener listener;

@Inject
Provider<ShowSettingsController> showSettingsController;
@Inject
Provider<AddNewDinnerController> newDinnerController;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ((WhatToDoForDinnerApp) getApplication()).getComponent().inject(this); //at this moment your dependencies are injected

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(...);

    listener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            newDinnerController.get().Run();
        }
    };
}
  @Override
public boolean onOptionsItemSelected(MenuItem item) {
    ...
    if (id == R.id.action_settings) {
        showSettingsController.get().Run();
        return true;
    }
    ...
}

非常感谢, 我担心的是这个解决方案中,所有活动都知道组件。而组件是Dagger特定的。它将我与Dagger紧密联系在一起。第二件事是我想注入每个活动的要求方法。正如我在我的答案中所写的,我已经找到了一个解决方案,它摆脱了这些不便之处。它仍然有一些不便之处,比如为每个控制器添加控制器提供程序的方法,但控制器是业务逻辑的入口点,所以我认为它的危害更小。 - Bartosz Gołek
是的,我明白了。我的意思是你的代码似乎并不真正需要Dagger,但是如果你想使用它,你可能应该学习更多关于使用Dagger构建依赖图的知识。例如,有一个很棒的教程(包含多篇文章的源码):https://github.com/frogermcs/GithubClient - greenfrvr
这并不是说“它真的不需要”,而更像是“使用某些东西,但不受其约束”。这是为了防止在某个时候可能会有比Dagger2更好的解决方案,或者会出现Dagger3。那时我就不想改变所有的活动。再次感谢您的帮助。 - Bartosz Gołek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接