I want to build an Android app using the MVP pattern.
I have a fragment (the view) and a presenter class.
What I want is to basically inject the presenter into the fragment, and set the fragment as the presenter's view (via an interface that the view will implement)
How can I easily and correctly connect the 2 using dependency injection (with Dagger2)?
Edit:
In addition, I'd like the presenter to be a singleton, so it will be able to persist data & state across orientation changes
First you need to define a presenter module:
#Module
class SearchPresenterModule {
#NonNull
private final SearchContract.View mView;
SearchPresenterModule(#NonNull SearchContract.View view) {
this.mView = view;
}
#Provides
SearchContract.View provideSearchContractView() {
return mView;
}
}
Here's the example component:
#FragmentScoped
#Component(modules = SearchPresenterModule.class)
interface SearchComponent {
void inject(SearchActivity activity);
}
And inject your presenter:
#Inject
SearchPresenter mSearchPresenter;
DaggerSearchComponent.builder()
.searchPresenterModule(new SearchPresenterModule(searchFragment))
.build()
.inject(this);
Finally inject your presenter's constructor:
#Inject
SearchPresenter(#NonNull SearchContract.View view, #NonNull SearchRepository searchRepository) {
this.mView = view;
mView.setPresenter(this);
}
Extra: Here's fragmentscoped annotation:
#Documented
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface FragmentScoped {
}
You can check my example repo for MVP + DAGGER2
https://github.com/savepopulation/wikilight
so the presenter is like
#Singleton
public class Presenter{
private View mView; ...
the view should be
public class View extends ...{
#Inject
protected Presenter mPresenter ...
Well, you just need a method in your module like
inject(View view)
and Dagger should take care of the rest like the singleton instance and the injection
Related
I have an Android project which is using Dagger 2, Livedata and viewmodel, rxjava and databinding.
I have created an AppComponent and two SubComponents as of now. The Appcomponent is for applicationscope and one Subcomponent that is ControllerComponent is for Activity, fragments and dialogs. This ControllerComponent is injected in the baseactivity and it is working fine.
The other Sub component is RetrofitComponent which is for viewmodel repositories. Now I can't seem to find a way to inject the retrofit component in the base repository since it is a Subcomponent and I don't have the applicationcontext in the repository class.
AppComponent:
#ApplicationScope
#Component(modules = {
ApplicationModule.class,
PrefModule.class
})
public interface AppComponent {
public void inject(AppInstance appInstance);
public AppPreference getAppPref();
public Application getApplication();
public ControllerComponent newControllerComponent(ControllerModule controllerModule);
public RetrofitComponent newRetrofitComponent(RetrofitModule retrofitModule);
}
RetrofitComponent:
#RepositoryScope
#Subcomponent(modules = {RetrofitModule.class})
public interface RetrofitComponent {
public void inject(BaseRepo baseRepo);
public APICall getApiCalls();
}
RetrofitModule:
#Module
public class RetrofitModule {
#Provides
#RepositoryScope
public APICall providePostApi(Retrofit retrofit) {
return retrofit.create(APICall.class);
}
#Provides
#RepositoryScope
public Retrofit provideRetrofitInterface() {
return new RetrofitService().retrofit;
}
}
BaseRepo;
#Inject
public APICall apiCall;
private Disposable subscription;
CompositeDisposable compositeDisposable = new CompositeDisposable();
private RetrofitComponent injector;
public BaseRepo() {
//injector = DaggerRetrofitComponent.builder().retrofitModule(new RetrofitModule()).build();(I tried with dependent component had different problem)
injector = ((AppInstance)getApplication()).getAppComponent().newRetrofitComponent(new RetrofitModule());(Not able to use getApplication())
injector.inject(this);
}
I'm going to use the child repositories extending this base repository from their corresponding viewmodels so I don't intend to pass on the application from the activities or fragments.
What I might be missing? Or doing wrong? Or is my approach wrong?
And one more thing once this is done, should I inject the repository classes in the Viewmodel class or should I create a factory class and call whichever repository I need, which is better practice?
I am trying to understand dagger.android framework that is included in Dagger 2.11. I wrote a sample code that implements some scopes, #Singleton, #ActivityScope and #FragmentScope.
My Activity has a fragment, and fragment has a Toy object. I want that MainFragment belong to Activity Scope and Toy object belong to Fragment scope.
But I have an error, Could you help me please? What is the problem? :
Error:(22, 8) error: [dagger.android.AndroidInjector.inject(T)]
com.example.user.daggerapplication4.Models.Toy cannot be provided
without an #Provides-annotated method.
com.example.user.daggerapplication4.Models.Toy is injected at
com.example.user.daggerapplication4.ui.MainFragment.toy
com.example.user.daggerapplication4.ui.MainFragment is injected at
com.example.user.daggerapplication4.ui.MainActivity.injectedFragment
com.example.user.daggerapplication4.ui.MainActivity is injected at
dagger.android.AndroidInjector.inject(arg0) A binding with matching
key exists in component:
com.example.user.daggerapplication4.ui.MainActivityModule_BindMainFragment.MainFragmentSubcomponent
AppComponent and Module :
#Singleton
#Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
ActivityBuilder.class
})
public interface AppComponent extends AndroidInjector<DaggerSample4Application> {
#Component.Builder
abstract class Builder extends AndroidInjector.Builder<DaggerSample4Application> {}
}
#Module
public class AppModule {
}
#Module
public abstract class ActivityBuilder {
#ActivityScoped
#ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity bindMainActivity();
}
Scopes :
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface FragmentScoped {}
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ActivityScoped {}
ActivityModule and FragmentModule
#Module()
public abstract class MainActivityModule {
#FragmentScoped
#ContributesAndroidInjector(modules = MainFragmentModule.class)
abstract MainFragment bindMainFragment();
}
#Module
public class MainFragmentModule {
#Provides
#FragmentScoped
Toy provideToy()
{
return new Puzzle();
}
}
Model Classes:
public interface Toy {
public String play();
}
public class Puzzle implements Toy {
#Override
public String play() {
Log.v("DaggerSample","Play with Puzzle");
return "Play with Puzzle";
}
}
MainActivity and MainFragment
public class MainActivity extends DaggerAppCompatActivity {
#Inject
MainFragment injectedFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainFragment mainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
// injectedFragment = new MainFragment();
if (mainFragment == null) {
mainFragment = injectedFragment;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.contentFrame, mainFragment);
transaction.commit();
}
}
}
public class MainFragment extends DaggerFragment {
private Button btnBuy;
private TextView textResult;
#Inject
Toy toy;
#Inject
public MainFragment()
{
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_main, container, false);
btnBuy = root.findViewById(R.id.btnBuy);
textResult = root.findViewById(R.id.textRresult);
btnBuy.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
showMessage(toy.play());
}
});
return root;
}
public void showMessage(String message) {
textResult.setText(message);
}
}
If you want to investigate the code, you can access with this link
I think while using interface it is better to use #Binds.
Try the method below.
#Module
public class MainFragmentModule {
#FragmentScoped
#Binds
public abstract Toy bindToy(Puzzle puzzle);
}
public class Puzzle implements Toy {
#Inject
public Puzzle(){
}
#Override
public String play() {
Log.v("DaggerSample","Play with Puzzle");
return "Play with Puzzle";
}
}
You have a few errors to fix in your code to get your project to compile. But first, rule of thumb for efficient Dagger - always prefer making your modules as abstract classes with abstract #Binds methods or if not possible with static #Provides methods. This means you need to make AppModule an abstract class, otherwise your project won't compile as per the code you posted here.
The main reason why your code doesn't compile is because Puzzle doesn't have a constructor that is annotated with #Inject:
public class Puzzle implements Toy {
#Inject // Add this
public Puzzle() {
}
#Override
public String play() {
Log.v("DaggerSample","Play with Puzzle");
return "Play with Puzzle";
}
}
Next, you need to make the following changes to this module:
#Module
public class MainFragmentModule { // Make this class abstract
#Provides // Change this to #Binds instead
#FragmentScoped
Toy provideToy() // Change this method to look like this below method
{
return new Puzzle();
}
#Binds
#FragmentScoped
abstract Toy bindPuzzle(Puzzle puzzle);
}
If you have other classes that implement Toy interface that you want to inject, you'll have to use qualifiers (#Named annotation) to tell Dagger which implementation to inject.
You cannot inject a fragment to the activity that is hosting it. Instead, you must create the fragment and add it using the fragment manager instead.
public class MainActivity extends DaggerAppCompatActivity {
#Inject // Remove this
MainFragment injectedFragment; // And this if you don't use this field
You can't annotate the fragment constructor with #Inject. Fragment is an Android Component and Android Components cannot be injected via constructor injection. The only way you can inject Android Components is via member injection, which is already done for you if your fragment inherits from DaggerFragment. Notice that if you're using support library Fragments, make sure to use DaggerFragment variant which is from the support package.
You haven't included your DaggerSample4Application code so I can't tell if you're doing something wrong there, but the main point is that this class needs to extend DaggerApplication and implement some methods. I have a complete working sample that you can check out:
https://github.com/Nimrodda/dagger-androidinjector
It's the source code for an article I wrote about Dagger Android injection https://android.jlelse.eu/android-and-dagger-2-10-androidinjector-5e9c523679a3
I highly recommend you check it out to get better understanding.
My question is similar to this.
So for instance, I have a LiveData implementation:
public class CustomLiveData extends LiveData<SomeEvent> {
#Inject
public CustomLiveData(#ActivityContext Context context) {
//....
}
}
that I want to inject into a custom view:
public class CustomView extends View {
#Inject
SomeApplicationProvider anyProvider;
#Inject
CustomLiveData dataProvider;
// Getting #com.di.qualifiers.ActivityContext android.content.Context cannot be provided without an #Provides-annotated method.
// #com.di.qualifiers.ActivityContext android.content.Context is injected at com.repositories.CustomLiveData.<init>(context)
// com.repositories.CustomLiveData is injected at com.ui.CustomView.dataProvider com.ui.CustomView is injected at
// com.di.ApplicationComponent.inject(view)
public CustomView(Context context) { this(context, null); }
public CustomView(Context AttributeSet attrs) {
super(context, attrs);
// Works ok for application provider
Application.getComponent(context).inject(this);
}
}
And here is the rest of DI classes:
#ApplicationScope
#Component(
modules = {AndroidInjectionModule.class,
ActivityBuilder.class
})
public interface ApplicationComponent extends AndroidInjector<MyApp> {
void inject(MyApp application);
void inject(CustomView view);
#Component.Builder
abstract class Builder extends AndroidInjector.Builder<MyApp> {
public abstract ApplicationComponent build();
}
}
#ActivityScope
#Module (subcomponents = MainActivitySubcomponent.class)
public abstract class ActivityBuilder {
#Binds
#IntoMap
#ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindActivityInjectorFactory(MainActivitySubcomponent.Builder builder);
//...
}
#Subcomponent(modules = {MainActivityModule.class})
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {
}
}
#ActivityScope
#Module
public class MainActivityModule {
#Provides
#ActivityContext
public Context provideActivityContext(MainActivity activity) {
return activity;
}
// Seems to be wrong or not enough!?
#Provides
public CustomLiveData provideCustomLiveData(#ActivityContext Context context) {
return new CustomLiveData(context);
}
}
#Qualifier
public #interface ActivityContext{
}
Note, that I don't get any compiler complaints if CustomLiveData is injected into MainActivity instead into the view.
Thanks!
tl;dr Don't inject model layer dependencies inside custom View objects
Subclasses of View are not good targets for Dagger 2 injection. View objects are meant to be drawn and not must else, hence the name "view". The constructors for View should make this clear; they are designed for inflating View objects from attributes specified in XML. In other words, a View object should be able to be specified in a layout.xml file, inflated at the appropriate point in the lifecycle, and then obtained using findViewById(int id), Butterknife or data binding. In this way, the best custom View objects take no dependencies.
If you want to link a View and some data from the model layer, the standard pattern is to write an Adapter like those for RecyclerView and ListView. If this is not possible, using a setter (e.g., setData()) is preferable to passing dependencies from the model layer in the constructor or requesting injection from within one of the lifecycle methods of the View.
If instead you inject your LiveData object inside an Activity or Fragment using the AndroidInjector class the correct Context will be provided without you having to do anything. This explains your comment "I don't get any compiler complaints if CustomLiveData is injected into MainActivity instead into the view."
Once you have injected the LiveData object into the Activity, use one of the above methods (an adapter or a setter) to associate the data with your custom View. See the Google Android Architecture example here where elements from the model layer are injected using Dagger 2 and then associated with a ListView using findViewById and setAdapter()
Link to the Dagger 2 issue where injection of View objects is discussed:
https://github.com/google/dagger/issues/720
Your Dagger hierarchy looks like this:
appcomponent -> activitycomponent
You try to inject activity context inside view, that depends on appcomponent directly.
It's not possible since there is no method that could provide activity context in appcomponent. Instead, inside view, you should retrieve activity (for example using getContext), extract activitycomponent from it and only then inject CustomLiveData.
I'm studying Dagger 2 so I would like to understand some basic things. I have the following code:
#Module
public class MainModule {
#Provides
public Presenter provideMainActivityPresenter(Model model){
return new MainPresenter(model);
}
#Provides
public Model provideMainModel(){
return new MainModel();
}
}
and my MainPresenter class looks like this:
public class MainPresenter implements Presenter {
#Nullable
private ViewImpl view;
private Model model;
public MainPresenter(Model model) {
this.model = model;
}
#Override
public void setView(ViewImpl view) {
this.view = view;
}
}
Instead of the above code, could I do the following?
public class MainPresenter implements Presenter {
#Nullable
private ViewImpl view;
#Inject
Model model;
#Override
public void setView(ViewImpl view) {
this.view = view;
}
}
Because the MainPresenter depends on the Model and it is not #Nullable.
Or this is wrong?
I don't understand when I should put a dependency as a constructor argument, or when I should use #Inject
You have basically 3 ways to use Dagger
Constructor injection
Field injection
Providing it from a module yourself
(There is also method injection which calls a method after creating your object)
The following is using a module that provides your class. While not wrong, this is the most overhead to write and maintain. You create the object by passing in the requested dependencies and return it:
// in a module
#Provides
public Presenter provideMainActivityPresenter(Model model){
// you request model and pass it to the constructor yourself
return new MainPresenter(model);
}
This should be used with things that require additional setup, like Gson, OkHttp, or Retrofit so that you can create the object in one place with the required dependencies.
The following would be used to inject objects where you don't have access or don't want to use the constructo. You annotate the field and register a method at the component to inject your object:
#Component class SomeComponent {
void injectPresenter(MainPresenter presenter);
}
public class MainPresenter implements Presenter {
// it's not annotated by #Inject, so it will be ignored
#Nullable
private ViewImpl view;
// will be field injected by calling Component.injectPresenter(presenter)
#Inject
Model model;
// other methods, etc
}
This will also provide you with overhead to register all your classes at a presenter and should be used when you can't use the constructor, like Activities, Fragments, or with Services. That's why all those Dagger samples have those onCreate() { DaggerComponent.inject(this); } methods to inject parts of the Android framework.
Most importantly you can use Constructor Injection. You annotate the constructor with #Inject and let Dagger find out how to create it.
public class MainPresenter implements Presenter {
// not assigned by constructor
#Nullable
private ViewImpl view;
// assigned in the constructor which gets called by dagger and the dependency is passed in
private Model model;
// dagger will call the constructor and pass in the Model
#Inject
public MainPresenter(Model model) {
this.model = model;
}
}
This only requires you to annotated your class constructor and Dagger will know how to handle it, given that all the dependencies (constructor arguments, Model in this example) can be provided.
All of the methods mentioned above will create an object and can / should be used in different circumstances.
All of those methods either pass the dependencies to the constructor, or inject #Inject annotated fields directly. Dependencies should thuse be in the constructor or annotated by #Inject so that Dagger knows about them.
I also wrote a blog post about the basic usage of Dagger 2 with some further details.
Hi i've got a following Problem. I want to write android tests with espresso for the Ui and in order to have tests that are not flaky i want to mock my presenter.
I use Dagger in the App. My Configuration is as Following:
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
//some injections
//some providings
}
I have a Module for the Component
#Module
public class AppModule {
//providings for component
}
then i have also a component for the activities with a module for the component
#PerActivity
#Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
//inject activites
//provide subcomponents for activites
}
then i have subcomponents for my pages
#PerActivity
#Subcomponent(modules = InfoModule.class)
public interface InfoComponent {
void inject(DetailActivity activity);
}
and a module for the subcomponent
#Module
public class InfoModule {
#Provides
public DetailPresenter provideDetailPresenter(ShowDetailsUseCase showDetailsUseCase,
OtherUseCase getPoisUseCase,
AccountManager accountManager, Navigator
navigator) {
return new DetailPresenter(showDetailsUseCase, otherUseCase, accountManager, navigator);
}
}
and then the detail Activity Injects the DetailPresenter
public class DetailActivity extends BaseActivity {
#Inject
DetailPresenter mPresenter;
InfoComponent mComponent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mComponent = getActivityComponent().provideInfoModule(new InfoModule());
mComponent.inject(this);
mPresenter.bindView(this);
mPresenter.onCreate(new PresenterBundle(getIntent().getExtras(), savedInstanceState));
}
//functionality of detailActiviy
}
then i have the presenter which uses constructor injection
public class DetailPresenter extends BasePresenter {
private ShowDetailsUseCase mDetailsUseCase;
private final OtherUseCase getPoisUseCase;
private AccountManager accountManager;
private Navigator navigator;
#Inject
public DetailPresenter(ShowDetailsUseCase getDetailsUseCase, OtherUseCase getPoisUseCase,
AccountManager
accountManager, Navigator navigator) {
this.mDetailsUseCase = getDetailsUseCase;
this.getPoisUseCase = gotherUseCase;
this.accountManager = accountManager;
this.navigator = navigator;
}
#Override
public void onCreate(#Nullable PresenterBundle bundle) {
super.onCreate(bundle);
//other things to do on initialization
((DetailView) getView()).showDetails(getDetailsFromUseCase());
}
}
Now in the test i want to do mock the presenter:
#RunWith(AndroidJUnit4.class)
public class DetailActivityTest {
#Rule
public final ActivityTestRule<DetailActivity> main = new ActivityTestRule<DetailActivity>(DetailActivity.class, true, false);
#Rule
public final DaggerMockRule<AppComponent> rule=new EspressoDaggerMockRule();
#Mock
DetailPresenter presenter; //does not work because #Inject constructor
#Test
public void locationTest() {
Details details = generateDetails();
launchActivity();
doAnswer(answer -> {
activity.showDetails(details);
return null;
}
).when(presenter).onCreate(any());
//espresso verify afterwards
}
}
but if i try to mock the following error shows:
java.lang.RuntimeException: Error while trying to override objects:
a.b.c.ui.mvp.presenter.DetailPresenter
You must define overridden objects using a #Provides annotated method instead of using #Inject annotation
does someone have an idea how I am able to mock the presenter even with #Inject constructor and dependencies.
I do not want to mock the data layer because then I have to mock database, apiClient, cacheData and so on. And some of the datalayer also have inject dependencies so i cannot mock them either.
Thank you in advance
The DetailPresenter class is created in the InfoModule, so you don't need the Inject annotation. The error you get is because using DaggerMock you can replace only the objects created in a module. In your example you are already creating it in a module, you just need to remove the Inject annotation.