1

I have sample project in which I am using dagger-android AndroidInjector to Inject dependencies. In this sample app I have a class Navigator which does all the Activity to Activity navigation and it requires the instance of current Activity in it's Constructor.

I have a AppModule class which provides the Instances to the other component, which is also responsible for providing the instance of Navigator to my presenter, but the Navigator constructor takes Activity as parameter from the AppModule. How can I access the activity instance to the Navigator constructor. I have tried Injecting the instance of Activity directly in Navigator by annotating the Activity member variable with @Inject annotation but that throws error while building the project.

error: [dagger.android.AndroidInjector.inject(T)] android.app.Activity cannot be provided without an @Inject constructor or from an @Provides-annotated method. android.app.Activity is injected at

I have referred other SA post but that doesn't help with my query or may be I am failed to understand the solution given there. Below are the post I followed

How to inject current activity in a collaborator using Dagger 2.11 Android injector

How to get MainActivity inside a module using AndroidInjector

Below is the code which I have implemented:

Application.Java

public class MyApplication extends Application implements HasActivityInjector {

    @Inject
    DispatchingAndroidInjector<Activity> androidInjector;

    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent
                .builder()
                .application(this)
                .build()
                .inject(this);
    }

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return androidInjector;
    }
}

MainActivity.java

public class MainActivity extends BaseActivity implements BaseView {

    @Inject
    Context application;

    @Inject
    MainPresenter mainPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }
}

MyPresenter.Java

public class MyPresenter extends BasePresenter<IActivityView> {

    private final NavigationRouter mNavigationRouter;

    @Inject
    public MyPresenter(Navigator navigatr) {
        mNavigationRouter = navigationRouter;
    }

}

Navigator.Java

//This is how my Navigator is currently.
public class Navigator{

    Activity mActivity;

    @Inject
    public Navigator(Activity activity){
    mActivity = activity;
    }

}

ActivityBuilder.java

@Module
public abstract class ActivityBuilder {

    @ContributesAndroidInjector
    abstract MainActivity bindMainActivity();

    @ContributesAndroidInjector
    abstract LoginActivity bindLoginActivity();
}

AppModule.Java

@Module
public class AppModule {
//How can i have the instance of current activity in this module class.
    @Provides
    @Singleton
    NavigationRouter providesNavigationRouter(Activity activity) {
        //Here I want the instance of current activity
        return new NavigationRouter(activity);
    }
}

AppComponent.Java

@Component(modules = {
        AndroidInjectionModule.class,
        AppModule.class,
        ActivityBuilder.class})
@Singleton
public interface AppComponent {

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }

    void inject(MyApplication application);
}

Any help on this will be much appreciated.

I have update the code snippet for more clarity

Tushar Purohit
  • 524
  • 7
  • 25

1 Answers1

1

In this sample app I have a class Navigator which does all the Activity to Activity navigation and it requires the instance of current Activity in it's Constructor

I'm a little confused on how this is expected to work. Does this mean that each Activity creates its own Navigator? If the Activity is supplied via the constructor then I can't think of any other way this would be feasible.

Anyway, assuming you do want to create a Navigator for each of your Activities, then your Dagger setup would look like this:

MainActivityModule.java

@Module
public abstract class MainActivityModule {
    @Binds @Named(MainActivity.class.getSimpleName()) abstract Navigator navigator(MainActivity ma);
}

ActivityBuilder.java

@Module
public abstract class ActivityBuilder {
    @ContributesAndroidInjector(modules = MainActivityModule.class) 
    abstract MainActivity bindMainActivity();
}

MainActivity.java

public class MainActivity extends BaseActivity implements BaseView {

    @Inject Navigator navigator; //This will hold a reference to MainActivity

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this); //Or AndroidSupportInjection...
    }
}

Edit: I made a couple of points the comments that show a "Navigator" class that takes a single Activity in via its constructor isn't feasible. You could instead try this:

@Singleton
public final class Navigator {

    private WeakReference<BaseActivity> ref = null;

    @Inject
    Navigator(){}

    void setForegroundActivity(BaseActivity base) {
        this.ref = new WeakReference<>(base);
    }

    void transitionTo(Class<? extends BaseActivity> next) {
        if(ref == null) return;
        final BaseActivity current = ref.get();
        if(current != null) {
            current.transitionTo(next);
        }
    }
}
public class MyApp extends Application implements Application.ActivityLifecycleCallbacks {

    @Inject Navigator navigate;

//...
   void onActivityResumed(Activity activity) {
        if(activity instanceof BaseActivity) {
            navigate.setForegroundActivity((BaseActivity) activitiy);
        }
    }

}
public class AnExampleClass {

    private final Navigator navigate;

    @Inject
    AnExampleClass(Navigator navigate) {
        this.navigate = navigate;
    }

    //...
    void someMethodRequiringATransition() {
        navigator.transitionTo(AnotherActivity.class);
    }

}
public class AnExampleActivity extends BaseActivity {

    @Override
    public void transitionTo(Class<? extends BaseActivity> next) {
        if(AnotherExampleActivity.class.equals(next)) {
            //Do stuff specific to transitioning to "Another Example Activity"
        }
    }    

}

I use WeakReferences because storing an Activity in a singleton is dangerous, and effectively begs memory leaks. However, you could use the ActivityLifecycleCallbacks to remove Activities as they move into "onPause" as well.

PPartisan
  • 8,173
  • 4
  • 29
  • 48
  • @PParitisan: No Each activity does not creates its Navigator, Navigator is common class responsible for the activity transition, Also in your above solution you have injected the Navigator in Activity where I want the instance in my presenter, I missed the code snippet of presenter earlier. I have updated the code kindly have a look and thanks for your quick response. – Tushar Purohit Jul 01 '19 at 07:07
  • Then you wouldn't be able to supply your Activity to the constructor of this singleton, because you would only ever have a reference to one Activity (which may or may not even exist). Also, there's the question of how you define what counts as the "current" Activity - if you have three Activities in your stack and all of them are in the background/stopped, then which one is the "current" Activity? You'd need a `Supplier` or something, and a dynamic map your Acitivities add themselves to in the Navigator as they come into/out of the foreground. – PPartisan Jul 01 '19 at 07:10
  • "Current" Activity refers to the activity on top of the stack which is on foreground, Also each activity extends one BaseActivity so instance of BaseActvity will also work for me here in Navigator. I have updated the snippet let me know if there's something wrong – Tushar Purohit Jul 01 '19 at 07:19
  • I've updated my answer with an example of how you could make this work – PPartisan Jul 02 '19 at 07:27
  • Thanks @PPartisan, With the approach suggested, It is working, but still I have question "Can't we inject the Activity instance to Navigator instead of passing it through `Application` class? Aren't we failing the concept of DI here?". The reason i have this question is because we are injecting the `Activity` in `AndroidInjector` from `onCreate` of each activity, so there should be another way to get the instance of activity through Dagger itself. Please suggest me if there is any other work around. – Tushar Purohit Jul 02 '19 at 12:47
  • Possibly...you *may* be able to create a map of your activities (in fact, I have a hunch Dagger does this internally), and inject that Map into your constructor. The issue I can see with this is twofold (1) Some of your Activities wont be created when your application starts, so how could you supply them? and (2) Will this affect how garbage collection works, because that would lead to all sorts of problems. – PPartisan Jul 02 '19 at 12:50