konmik / nucleus

Nucleus is an Android library, which utilizes the Model-View-Presenter pattern to properly connect background tasks with visual parts of an application.
MIT License
1.97k stars 253 forks source link

How to get dagger to inject upon onCreate in Presenter #130

Closed fredagsfys closed 7 years ago

fredagsfys commented 7 years ago

I've got a Activity with corresponding Presenter. I'm using Dagger 2 to inject a component which calls various API methods with Retrofit.

However, in my presenter I can't figure out how to make Dagger 2 inject the component directly onCreate().

I've done something like this below, but it isn't good enough as i get a null reference because I don't got a reference to the view(Activity) before onTakeView()

Yes I'm new to this, both RxJava and MVP pattern. Please help me figure out how to solve this. I want the data to get fetched directly upon onCreate(), as soon as the activity loads.

Activity

@RequiresPresenter(AssignmentPresenter.class)
public class AssignmentActivity extends NucleusAppCompatActivity<AssignmentPresenter>     implements NavigationView.OnNavigationItemSelectedListener{

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_assignment);
    ButterKnife.bind(this);

    if (savedInstanceState == null)
      getPresenter().request("some-token-xx");
}

Presenter

public class AssignmentPresenter extends RxPresenter<AssignmentActivity> {

    public static final int REQUEST_ASSIGNMENTS = 1;
    private String mAccessToken;

    @Inject Retrofit mRetrofit;

    private AssignmentActivity mView;

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

        restartableLatestCache(REQUEST_ASSIGNMENTS,
                new Func0<Observable<List<Assignment>>>() {
                    @Override
                    public Observable<List<Assignment>> call() {
                        return mRetrofit.create(AssignmentService.class)
                                .GET(mAccessToken)
                                .subscribeOn(Schedulers.newThread())
                                .observeOn(mainThread());
                    }
                },
                new Action2<AssignmentActivity, List<Assignment>>() {
                    @Override
                    public void call(AssignmentActivity assignmentActivity, List<Assignment> response) {
                        assignmentActivity.onSuccess(response);
                    }
                }, new Action2<AssignmentActivity, Throwable>() {
                    @Override
                    public void call(AssignmentActivity assignmentActivity, Throwable throwable) {
                        assignmentActivity.onError(throwable);
                    }
                }
        );
    }

    @Override
    protected void onTakeView(AssignmentActivity view) {
        super.onTakeView(view);
        mView = view;

        ((App) mView.getApplication()).getRestApiComponent().inject(this);
    }

    void request(String accessToken) {
        mAccessToken = accessToken;
        start(REQUEST_ASSIGNMENTS);
    }
}
yankeppey commented 7 years ago

You don't need to have a view reference to access global dependency graph. The simplest solution is to call App.getInstance().getRestApiComponent().inject(this) instead of your current call. You just need to implement static getInstance method in your App (Application is a natural singleton, so don't worry to store a static reference to App inside the App itself). The disadvantage of this is that your code is still coupled with god object App.

Another option is here: https://github.com/konmik/nucleus/issues/32 . Instead of Presenter self-injection, you can make View (Activity, Fragment or Layout) to inject all the dependencies needed. The advantage is that Presenter does not even know who'll provide these dependencies for him (Hollywood Principle: Don't call us, we'll call you). The disadvantage is that you need to remember not call getPresenter() before onCreate() (no field initializations like clickListener = getPresenter()::doSomething), or you'll got crash otherwise.

fredagsfys commented 7 years ago

Awesomeeee!! Thank you so much. I wish I could buy you a beer 😄 Cheers mate, here's a virtual one 🍺

fredagsfys commented 7 years ago

@yankeppey Would you suggest to make a base Activity to do as the second suggestion you gave and inherit from it on each Activity? Maybe even for fragments...

fredagsfys commented 7 years ago

I tried to make a BaseActivity to prevent repeating the presenter injection onCreate() everywhere. It doesn't seem to work, altho when doing the same with a BaseFragment it works pretty well.

Looking like this below

BaseActivity

public class BaseActivity<P extends Presenter> extends NucleusAppCompatActivity<P> {
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
    final PresenterFactory<P> superFactory = super.getPresenterFactory();
    setPresenterFactory(new PresenterFactory<P>() {
        @Override
        public P createPresenter() {
            P presenter = superFactory.createPresenter();
            ((Injector) getApplication()).inject(presenter);
            return presenter;
        }
    });
    super.onCreate(savedInstanceState, persistentState);
    Icepick.saveInstanceState(this, savedInstanceState);
}
}

Activity

@RequiresPresenter(AssignmentPresenter.class)
public class AssignmentActivity extends BaseActivity<AssignmentPresenter> {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_assignment);
    ButterKnife.bind(this);
 }
}

If I do this, it works

@RequiresPresenter(AssignmentPresenter.class)
public class AssignmentActivity extends NucleusAppCompatActivity<AssignmentPresenter> {
@Override
protected void onCreate(Bundle savedInstanceState) {
    final PresenterFactory<AssignmentPresenter> superFactory = super.getPresenterFactory();
    setPresenterFactory(new PresenterFactory<AssignmentPresenter>() {
        @Override
        public AssignmentPresenter createPresenter() {
            AssignmentPresenter presenter = superFactory.createPresenter();
            ((Injector) getApplication()).inject(presenter);
            return presenter;
        }
    });

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_assignment);
    ButterKnife.bind(this);
 }
}
konmik commented 7 years ago

What does mean "does not work?" How can it be? :) Can you use debugger to trace code execution and see what exactly is wrong?

yankeppey commented 7 years ago

@devharis you use onCreate(Bundle savedInstanceState, PersistableBundle persistentState) in your BaseActivity which is not normally called (docs tell something about persistable mode but honestly I've never used it). You should use onCreate(Bundle savedInstanceState), with one parameter.

fredagsfys commented 7 years ago

Yes, it was totally correct. I use the wrong onCreate() 👍