What is the best way to implement onClick inside an Epoxy Controller when using Dagger

3.8k Views Asked by At

I am trying to implement Click functionality on an item inside a RecyclerView using airbnb.epoxy. The problem is that i need context in order to navigate to another activity upon click.

What i have done: Following Epoxy's sample app I implemented an interface inside the EpoxyController that contains the function to be called when clicking an item in the recycler view. I then make my main activity implement this interface and method, and instantiate the controller using its constructor inside the main activity and passing it a reference to the activity:

    public class MortgageController extends TypedEpoxyController<List<Mortgage>> {

    private final MortgageCallBacks callBacks;

    public MortgageController(MortgageCallBacks callBacks) {
        this.callBacks = callBacks;
    }

    @Override
    protected void buildModels(List<Mortgage> mortgages) {
        Observable.from(mortgages)
                .subscribe(mortgage -> new MortgageModel_().id(mortgage.id())
                        .mortgage(mortgage)
                        .clicks(callBacks::onMortgageClicked)
                        .addTo(this));
    }


    public interface MortgageCallBacks {
        void onMortgageClicked(Mortgage mortgage);
    }
}

main activity's onCreate and onMortgageClick:

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

     controller = new MortgageController(this);
     initRecycler();
     controller.setData(mortgages);}
 
 @Override
 public void onMortgageClicked(Mortgage mortgage) {
     DetailActivity.start(this, mortgage);
 }

what i want to do while the above work, i am using Dagger2 in my project and would like to inject the controller into the MainActivity, injecting it is not the problem rather supplying the activity context, after a little bit of research i found epoxy allow activity injection, so i thought i could inject the main activity to the controller, but i am not sure this is the best way to go, and couldn't find example projects that implement this.

please enlighten me in what is the best way to do this

2

There are 2 best solutions below

0
On

After A lot of research, I found an answer. It may help others so here it is.

*I'm assuming you already have the basic features of Dagger2 working (if not look at Google's user guide) The latest dagger versions (above 2.10) introduced dagger.android and allow you to inject an activity directly with a few simple steps: 1.Create a module that has a method annotated with @ContributesAndroidInjector 2.Add module to app component 3.make App implement HasActivityInjector

1.You can create an ActivityBindingModule, that has a method annotated by @ContributesAndroidInjector this auto generates a Subcomponent that we would otherwise have to write ourselves. You can specify the modules this subComponent will have.

@Module public abstract class ActivityBindingModule {

@PerActivity
@ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity mainActivityInjector();

}

2.add Module to your AppComponent:

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

3.make app implement HasActivityInjector and override AndroidInjector activityInjector():

public class App implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> activityInjector;

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

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

}

Another way is to extend DaggerApplication that implement all of the above

Now i can just inject MainActivity into the Mortgage Controller

   private final MainActivity mainActivity;


@Inject
public MortgageController(MainActivity mainActivity) {
    this.mainActivity = mainActivity;
}

For more information see this amazing article that helped me better understand this not so simple subject, and Google's Android-Architecture sample app an example project that among other things show how to implement Dagger2 properly.

0
On

It is not really relevant to this topic but I have just figure out how to navigate from one activity to another from an Epoxy Controller.

Let's say we need to implement the onSelected().

To moving between activities, we need an intent.But it requires a Context for the constructor. Unfortunately, we can't achieve that in Epoxy Controller. As a result, we need to implement the onSelected listener on an Activity or a Fragment, then pass it back to the Epoxy Controller.

Below is my epoxy controller:

class TaskController(private val listener: TaskSelectListener) : TypedEpoxyController<List<Task>>() {
    override fun buildModels(tasks: List<Task>?) {
        tasks?.forEach { task ->
            TaskBindingModel_()
                .id(task.id)
                .taskToShow(task)
                .listener { model, _, _, _ ->
                    listener.onSelected(model.taskToShow())
                }.addTo(this)
        }

    }

    interface TaskSelectListener {
        fun onSelected(suggestion: Task)
    }

}

It is just a simple controller one, just only two things to notify in here. First is the interface. Another class which has context will inherit this Interface class and implement the onSelected method we desires.

The other thing is to pass that interface class to the Controller as a callback.

In my MainActivity file, I implement like this:

class MainActivity : AppCompatActivity(), TaskController.TaskSelectListener {
    // pass `this` MainActivity because it is a TaskSelectListener
    private val taskController : TaskController by lazy { TaskController(this) }

     override fun onSelected(task: Task) {
        val intent = Intent(this, TomatoActivity::class.java)
        startActivity(intent)
    }
}