简体   繁体   中英

What is the best way to inject subclasses using Dagger 2?

I recently started using Dagger 2 to manage the depency injections of my app. In order to make some classes testable, I started creating many classes that I need to inject. I was able to do so, however, the process to get these new classes injected look a little bit complicaded to me. Lets take an example:

I would like to test, for instance, the method onCreateViewHolder() from my RecyclerViewAdapter. To do so, I've created a Factory that returns the ViewHolder based on the given LayoutType:

public class ViewHolderFactor {

    public RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = this.getLayoutInflater(parent.getContext());
        View view;
        switch (LayoutType.fromInteger(viewType)) {
            case SMALL_VERTICAL:
                view = inflater.inflate(R.layout.rsc_util_item_small, parent, false);
                return new ViewHolder.ItemViewHolder(view);
            case LARGE_VERTICAL:
                view = inflater.inflate(R.layout.rsc_util_item_large, parent, false);
                return new ViewHolder.ItemViewHolder(view);
        }
        return null;
    }

    private LayoutInflater getLayoutInflater(Context context) {
        return LayoutInflater.from(context);
    }

}

By moving the above code to a separated class, I'm able to perform my unit test using:

@RunWith(JUnit4.class)
public class TestViewHolderFactor extends TestCase {

    ViewHolderFactor viewHolderFactor;

    @Test
    public void testGetViewHolder () {
        this.viewHolderFactor = Mockito.mock(ViewHolderFactor.class);
        ViewGroup viewGroup = Mockito.mock(ViewGroup.class);
        Mockito.when(viewHolderFactor.getViewHolder(viewGroup, LayoutType.SMALL_VERTICAL.toInteger())).thenCallRealMethod();
        Context context = Mockito.mock(Context.class);
        Mockito.when(viewGroup.getContext()).thenReturn(context);
        LayoutInflater layoutInflater = Mockito.mock(LayoutInflater.class);
        Mockito.when(viewHolderFactor.getLayoutInflater(context)).thenReturn(layoutInflater);
        Mockito.when(layoutInflater.inflate(R.layout.rsc_util_item_small, viewGroup, false)).thenReturn(Mockito.mock(View.class));
        RecyclerView.ViewHolder result = viewHolderFactor.getViewHolder(viewGroup, LayoutType.SMALL_VERTICAL.toInteger());
        assertNotNull(result);
    }

}

Problem is: now, to make the app work, I will also have to inject the outter class that holds an instance to the factor (something that I wasn't doing before creating ViewHolderFactor). At end, I will have a Dagger configuration like that:

@Module
public class ModuleBusiness {
    @Provides
    public CharacterUIService provideCharacterService(Picasso picasso, NotificationUtil notificationUtil, ViewHolderFactor viewHolderFactor) {
        return new CharacterUIService(picasso, notificationUtil, viewHolderFactor);
    }
}

Where CharacterUIService is the class that creates a new instance of the RecyclerViewAdapter that contains the ViewHolderFactory.

public class 
    private ViewHolderFactor 
    @Inject
    public CharacterUIService(ViewHolderFactor viewHolderFactor) {
        this.mViewHolderFactor = viewHolderFactor;
    }

    // ...
}

I need a member from ViewHolderFactor in order to inject it.

My worries regards the need to create new global variables and inscrease the number of parameters being passed in the constructor. Is there a better way to inject these subclasses or can I consider it as a good practice in order to allow Unit Tests?

can I consider it as a good practice in order to allow Unit Tests?

First of all—in my personal opinion—I think that you are overdoing the testing. In the test case that you provided you are basically testing the android framework itself . If you want to test the factory, you should test whether a small or large item is returned, rather than the view being not null.

But yes, yours seems like a reasonable approach to allow for unit testing.

Where CharacterUIService is the class that creates a new instance of the RecyclerViewAdapter

You might want to overthink the role of CharacterUIService . "that creates a new instance" sounds like something that should rather be handled by dagger, since you are already using it.

will also have to inject the outter class that holds an instance to the factory

Why? ViewHolderFactor (sic!) has no dependencies itself. And even if it did, what is stopping you from just creating and injecting it with dagger?

class ViewHolderFactor {
    @Inject
    ViewHolderFactor() { // allow for constructor injection
    }
}

class YourAdapter {
    ViewHolderFactor mFactor;

    @Inject // allow for constructor injection
    YourAdapter (ViewHolderFactor factor) {/**/}
}
// now dagger knows how to create that adapter

And just create let dagger create that adapter?


And generally, if you support constructor injection, then you can remove your @Provides providesSomething() methods. Just remove the whole method. You have an @Inject annotation on the constructor, so make use of that and let dagger write that code for you.

// CharacterUIService can be constructor injected.
@Provides // DAGGER WILL DO THIS FOR YOU
public CharacterUIService provideCharacterService(Picasso picasso, NotificationUtil notificationUtil, ViewHolderFactor viewHolderFactor) {
    // REMOVE ALL OF THIS.
    return new CharacterUIService(picasso, notificationUtil, viewHolderFactor);
}

So while I think you do a good job by making things testable, you should have again a good look on dagger, since you are obviously mixing many things up. Have a good look on constructor injection and make use of it, it saves you a whole lot of work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM