Dagger 2 Scoped Activity 上下文问题

Dagger 2 Scoped Activity Context Issue

我正在尝试关注 this template 的 MVP/Dagger2/RxJava 项目。

我无法将 Activity 上下文注入到我的演示者中,每次其他注入都会通过,因为我知道子组件可以开放访问所有父级提供的逻辑。

application 组件在Application class 中构建,然后在base presenter activity 中访问,然后注入presenter 和其余部分的相关依赖项。配置持久化组件主要用于保存演示者状态。

如果我只是手动将上下文从 activity 传递给演示者,那么 DI 的目的就会失败。

我已尝试为所有组件和模块添加作用域,以确保可以从图中正确访问依赖项,但这没有奏效。

我正在尝试使用上下文的构造函数注入,我实际上在 activity 中接收到演示者与之通信但演示者没有的上下文,抛出错误。所以我想知道为什么 activity 可以访问 activity 上下文但演示者不能。

如有任何指导,我们将不胜感激。

Error

Error:(13, 8) error: [<packageName>.injection.component.ActivityComponent.inject(<packageName>.login.LoginActivity)] android.app.Activity cannot be provided without an @Inject constructor or from an @Provides-annotated method.
android.app.Activity is injected at
<packageName>.login.presenter.LoginActivityPresenter.<init>(activity, …)
<packageName>.login.presenter.LoginActivityPresenter is injected at
<packageName>.login.LoginActivity.presenter
<packageName>.login.LoginActivity is injected at
<packageName>.injection.component.ActivityComponent.inject(loginActivity)
A binding with matching key exists in component: <packageName>.injection.component.ActivityComponent

我的components/modules如下图:

Application Component

@Singleton
@Component(modules = {ApplicationModule.class, BusModule.class, PrefsModule.class, NetModule.class})
public interface ApplicationComponent {
    @ApplicationContext Context context();
    Application application();
    EventBus bus();
    SharedPreferences prefs();
    Gson gson();
}

Application Module

@Module
public class ApplicationModule {
    protected final Application app;

    public ApplicationModule(Application app) {
        this.app = app;
    }

    @Provides
    Application providesApplication() {
        return app;
    }

    @Provides
    @ApplicationContext
    Context providesContext(){
        return app;
    }
}

Config Persistent Component

@ConfigPersistent
@Component(dependencies = ApplicationComponent.class)
public interface ConfigPersistentComponent {

  ActivityComponent plus(ActivityModule activityModule);

}

Activity Component

@PerActivity
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent {

  void inject(LoginActivity loginActivity);

}

Activity Module

@Module
public class ActivityModule {

  private Activity activity;

  public ActivityModule(Activity activity) {
    this.activity = activity;
  }

  @Provides
  @PerActivity
  Activity providesActivity() {
    return activity;
  }

}

Application class

public class App extends Application {

  private ApplicationComponent applicationComponent;

  @Override public void onCreate() {
    super.onCreate();
    Timber.plant(new Timber.DebugTree());
  }

  public static App get(Context context) {
    return (App) context.getApplicationContext();
  }

  public ApplicationComponent getComponent() {
    if (applicationComponent == null) {
      applicationComponent = DaggerApplicationComponent.builder()
              .applicationModule(new ApplicationModule(this))
              .busModule(new BusModule())
              .netModule(new NetModule())
              .prefsModule(new PrefsModule())
              .build();
    }
    return applicationComponent;
  }

}

MainPresenter

@ConfigPersistent
public class LoginActivityPresenter extends BasePresenter<LoginContract.View> {

  Context context;
  Gson gson;

  @Inject
  public LoginActivityPresenter(Activity activity, Gson gson) {
    this.context = activity;
    this.gson = gson;
  }

  @Override public void attachView(LoginContract.View view) {
    super.attachView(view);
    Timber.d("onAttach");
  }

  @Override public void detachView() {
    super.detachView();
    Timber.d("onDettach");
    disposableSubscriber.dispose();
  }
}

Base Presenter Activity

public abstract class BasePresenterActivity extends AppCompatActivity {

    private static final String KEY_ACTIVITY_ID = "KEY_ACTIVITY_ID";
    private static final AtomicLong NEXT_ID = new AtomicLong(0);
    private static final Map<Long, ConfigPersistentComponent> sComponentsMap = new HashMap<>();

    private ActivityComponent activityComponent;
    private long activityId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create the ActivityComponent and reuses cached ConfigPersistentComponent if this is
        // being called after a configuration change.
        activityId = savedInstanceState != null ? savedInstanceState.getLong(KEY_ACTIVITY_ID) : NEXT_ID.getAndIncrement();
        ConfigPersistentComponent configPersistentComponent;
        if (!sComponentsMap.containsKey(activityId)) {
            Timber.i("Creating new ConfigPersistentComponent id=%d", activityId);
            configPersistentComponent = DaggerConfigPersistentComponent.builder()
                    .applicationComponent(App.get(this).getComponent())
                    .build();
            sComponentsMap.put(activityId, configPersistentComponent);
        } else {
            Timber.i("Reusing ConfigPersistentComponent id=%d", activityId);
            configPersistentComponent = sComponentsMap.get(activityId);
        }
        activityComponent = configPersistentComponent.plus(new ActivityModule(this));
    }

Main Activity

public class LoginActivity extends BasePresenterActivity implements LoginContract.View {

  @Inject
  EventBus bus;
  @Inject
  SharedPreferences prefs;
  @Inject
  Activity activity;
  @Inject
  LoginActivityPresenter presenter;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    activityComponent().inject(this);
    setContentView(R.layout.activity_login);
    presenter.attachView(this);
  }

  @Override protected void onDestroy() {
    super.onDestroy();
    presenter.detachView();
  }
}

/********** 编辑 **********/

为了帮助保存演示者的状态并仍然允许传入 activity 上下文,我的解决方案发布在下面。欢迎任何反馈。

     @ActivityContext
    public class LoginActivityPresenter extends BasePresenter<LoginContract.View> {

      Context context;

      @Inject
      LoginStateHolder loginStateHolder;

      @Inject
      public LoginActivityPresenter(Context context, Gson gson) {
        this.context = activity;
        this.gson = gson;
      }
}

@ConfigPersistent
public class LoginStateHolder {

  String title;

  @Inject
  public LoginStateHolder(Context context) {
    title = "Save me";
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getTitle() {
    return title;
  }
}

/*********** 编辑 - 21_5_17 *********/

异常:

Error:(13, 8) error: [<packagename>.injection.component.ActivityComponent.inject(<packagename>.login.ui.activity.LoginActivity)] android.app.Activity cannot be provided without an @Provides-annotated method.
android.app.Activity is injected at
<packagename>.login.presenter.LoginActivityPresenter.<init>(activity, …)
<packagename>.login.presenter.LoginActivityPresenter is injected at
<packagename>.login.ui.activity.LoginActivity.presenter
<packagename>.login.ui.activity.LoginActivity is injected at
<packagename>.injection.component.ActivityComponent.inject(loginActivity)

登录Activity

public class LoginActivity extends BasePresenterActivity implements LoginContract.View {

  @Inject
  EventBus bus;
  @Inject
  SharedPreferences prefs;
  @Inject
  LoginActivityPresenter presenter;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    activityComponent().inject(this);
    setContentView(R.layout.activity_login);
    ButterKnife.bind(this);
    presenter.attachView(this);
    presenter.setupValidation(username, companyId, password);

}

登录Activity主持人

@ActivityContext
public class LoginActivityPresenter extends BasePresenter<LoginContract.View> {

  @Inject LoginPresenterStorage loginPresenterStorage;

  Gson gson;

  @Inject
  public LoginActivityPresenter(Activity activity, Gson gson) {
    this.context = activity;
    this.gson = gson;
  }
}

登录演示者存储

@ConfigPersistent
public class LoginPresenterStorage {

  private String test = "";

    @Inject
  public LoginPresenterStorage(Activity activity) {
    test = "I didn't die";
  }

  public String getTest() {
    return test;
  }
}

应用组件

@Singleton
@Component(modules = {ApplicationModule.class, BusModule.class, PrefsModule.class, NetModule.class})
public interface ApplicationComponent {

    @ApplicationContext Context context();
    Application application();
    EventBus bus();
    SharedPreferences prefs();
    Gson gson();

}

Activity 组件

@ActivityContext
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent {

  void inject(LoginActivity loginActivity);

}

ConfigPersistentComponent

@ConfigPersistent
@Component(dependencies = ApplicationComponent.class)
public interface ConfigPersistentComponent {

  ActivityComponent plus(ActivityModule activityModule);

}

Activity 模块

@Module
public class ActivityModule {

  private Activity activity;

  public ActivityModule(Activity activity) {
    this.activity = activity;
  }

  @Provides
  @ActivityContext
  Activity providesActivity() {
    return activity;
  }

}

编辑

我错误地使用了与 activity 模块相同的 activity 上下文。因此,我无法将 activity 注入演示者。将 activity 模块更改为原始 @peractivity 范围并遵循以下答案将使 activity 上下文可注入。

@ConfigPersistent
public class LoginActivityPresenter extends BasePresenter<LoginContract.View> {

通过使用@ConfigPersistent 范围标记 LoginActivityPresenter,您告诉 Dagger "manage this class's instance in ConfigPersistentComponent"(即始终 return 来自给定 ConfigPersistentComponent 实例的相同实例),这意味着它不应该从像@ActivityScope 这样的狭窄范围访问任何东西。毕竟,ConfigPersistentComponent 的寿命比 ActivityComponent 长,所以用 Activity 注入 LoginPresenter 没有意义:按照你现在的方式,你会得到相同的 LoginPresenter 实例,但 Activity.

消息 "android.app.Activity cannot be provided without an @Inject constructor" 来自 ConfigPersistentComponent 的生成,它没有 Activity 绑定。当然,您的 ActivityComponent 可以,但它不是尝试存储 LoginPresenter 及其当前注释的地方。

将该声明切换到@ActivityScope,一切都会好起来的:对于您创建的每个 Activity,您将获得不同的 LoginActivityPresenter,并且您将还可以访问@Singleton 范围 (ApplicationComponent) 和@ConfigPersistent 范围 (ConfigPersistentComponent) 中的所有内容。

@ActivityScope
public class LoginActivityPresenter extends BasePresenter<LoginContract.View> {