So i am new to Dagger 2 dependency injection. I have created a custom ViewModelFactory class which returns my ViewModel.
#Singleton
public class CustomViewModelFactory implements ViewModelProvider.Factory {
private final MyCatchesRepository repository;
#Inject
public CustomViewModelFactory(MyCatchesRepository repository) {
this.repository = repository;
}
#NonNull
#Override
#SuppressWarnings("unchecked")
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(MyCatchViewModel.class)) {
return (T) new MyCatchViewModel(repository);
} else {
throw new IllegalArgumentException("ViewModel Not Found");
}
}
}
The CustomViewModel takes a MyCatchesRepository in the constructor and then creates the MyCatchViewModel.
How could i change this class so that i can use this ViewModelFactory to create different ViewModels with different constructor arguments (repositories)
This is the Module where the CustomViewModelFactory is created
#Module
public class RoomModule {
private final MyDatabase myDatabase;
public RoomModule(Application application) {
this.myDatabase = Room.databaseBuilder(application,
MyDatabase.class, AppConstants.DATABASE_NAME)
.build();
}
#Provides
#Singleton
MyCatchesRepository provideCatchesRepository(MyCatchDao myCatchDao) {
return new MyCatchesRepository(myCatchDao);
}
#Provides
#Singleton
MyCatchDao providesCatchDao(MyDatabase myDatabase) {
return myDatabase.myCatchDao();
}
#Provides
#Singleton
LuresRepository provideLureRepository(LureDao lureDao) {
return new LuresRepository(lureDao);
}
#Provides
#Singleton
LureDao provideLureDao(MyDatabase myDatabase) {
return myDatabase.lureDao();
}
#Provides
#Singleton
MyDatabase provideDatabase(Application application) {
return myDatabase;
}
#Provides
#Singleton
ViewModelProvider.Factory provideCatchesViewModelFactory(MyCatchesRepository catchesRepository) {
return new CustomViewModelFactory(catchesRepository);
}
}
ViewModelModule
#Module
public abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(MyCatchViewModel.class)
abstract ViewModel myCatchViewModel(MyCatchViewModel myCatchViewModel);
#Binds
#IntoMap
#ViewModelKey(FishingSpotViewModel.class)
abstract ViewModel fishingSpotViewModel(FishingSpotViewModel fishingSpotViewModel);
#Binds
abstract ViewModelProvider.Factory bindCustomViewModelFactory(CustomViewModelFactory customViewModelFactory);
}
The approach the Google team came up with in the architecture components samples is to use a custom annotation in order to provide ViewModel classes through dagger.
In Java the Annotation looks as follows.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import androidx.lifecycle.ViewModel;
import dagger.MapKey;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#MapKey
#interface ViewModelKey {
Class<? extends ViewModel> value();
}
This uses MapKey from Dagger, where any annotated ViewModel will be composed into a Map which can then be used in your ViewModelFactory.
In the Google samples the ViewModelFactory looks as follows, where using constructor injection you can access the map of ViewModel providers.
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels;
#Inject
public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels) {
this.viewModels = viewModels;
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
Provider<ViewModel> viewModelProvider = viewModels.get(modelClass);
if (viewModelProvider == null) {
throw new IllegalArgumentException("model class " + modelClass + " not found");
}
//noinspection unchecked
return (T) viewModelProvider.get();
}
}
In your example your would end up with the following in order to provide the MyCatchViewModel. Other ViewModels could then be provided by following the same pattern.
#Module
public abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(MyCatchViewModel.class)
abstract ViewModel myCatchViewModel(MyCatchViewModel myCatchViewModel);
}
For a complete example you can check out the GithubBrowserSample sample from Google. https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/di/ViewModelModule.kt
Related
I have the following scenario:
A declarative Client
#Client("/pets")
public interface PetClient extends PetOperations {
#Override
Single<Pet> save(String name, int age);
}
A PetDummy class, which #Replaces the PetClient during for testing
#Primary
#Replaces(PetClient.class)
#Singleton
public class PetDummy implements PetOperations {
#Override
public Single<Pet> save(String name, int age) {
// do something
return Single.just(pet);
}
}
And a PetService class that has the declarative client as dependency
PetService {
#Inject
PetClient client;
buyFood(){
//...
Single<Pet> pet = client.save("Hoppie", 1));
}
}
When I test my PetService, my goal is that the test calls the PetDummy class instead of the PetClient
I assume it has to do with the applicationContext, you will see
PetService:
PetService {
#Inject
PetClient client;
buyFood(){
//...
Single<Pet> pet = client.save("Hoppie", 1));
}
}
PerService Test:
class PetServiceTest extends ApplicationContextSpecification {
#Subject
#Shared
PetService petService = applicationContext.getBean(PetService)
PetOperations client = applicationContext.getBean(PetOperations.class) //somehow i need to tell the application context to use the PetDummy class
def 'test' (){
given:
when:
petService.buyFood()
then:
noExceptionThrown()
}
}
I think that I need to "get into" the applicationContext from the PetService. However, the ApplicationContextSpecification belongs to another module, so it won't recognise the PetDummy implementation
The ApplicationContextSpecification is:
abstract class ApplicationContextSpecification extends Specification implements ConfigurationFixture {
#AutoCleanup
#Shared
ApplicationContext applicationContext = ApplicationContext.run(configuration)
}
The ConfigurationFixture contains the properties for the database(hibernate)
I am learning Java, but found the following piece of code. I am confused. What is bind(X.class).to(X.class); for?
import org.glassfish.hk2.utilities.binding.AbstractBinder;
public class ApplicationBinder extends AbstractBinder {
#Override
protected void configure() {
bind(X.class).to(X.class);
}
}
Thanks
You're configuring how you want your services to be discovered in the DI (dependency injection) system. bind(Service).to(Contract) is basically saying that you want to provide the Service as an injectable service, and want to "advertise" it as Contract. By "advertise", I mean what you want to be able to inject it as. For instance Service can be UserRepositoryImpl, while Contract can be UserRepository (interface). With this you would only be able #Inject UserRepository as that's what you advertise. The benefit of this is all the benefits that come with programming to an interface.
Example
interface UserRepository {
List<User> findAll();
}
class UserRepositoryImpl implements UserRepository {
#Override
public List<User> findAll() {
return Arrays.asList(new User("username"));
}
}
#Path("users")
class UserResource {
#Inject
private UserRepository repository;
#GET
public List<User> getUsers() {
return repository.findAll();
}
}
class JerseyApp extends ResourceConfig {
public JerseyApp() {
register(UserResource.class);
register(new AbstractBinder() {
#Override
public void configure() {
bind(UserRepositoryImpl.class)
.to(UserRepository.class);
}
});
}
}
Here the UserRepository is injected into the UserResource. When the DI system injects it, it will actually be the UserRepositoryImpl instance.
By doing that you are actually binding a new contract to a service.
bind(Service.class).to(Contract.class);
OR (binding a new contract to a service in Singleton)
bind(Service.class).to(Contract.class)..in(Singleton.class);
I am developing a music application. I want to load artist's image from LastFM so i do this this way
1. I created a class ArtistImageLoader extends BaseGlideUrlLoader.
2. In the getUrl method i used retrofit2 to get the artist's image url from LastFM via getArtistInfo method.
My problem is i didn't know how to inject the service of retrofit to make the request in ArtistImageLoader. I did this way but i got a NOP exception. lastFmService wasn't be not injected.
// GlideModule
glide.register(MLocalArtist.class, InputStream.class, new ArtistImageLoader.Factory());
// Use it in onCreate method of ArtistsFragment
DaggerLastFmComponent.builder().activityModule(new ActivityModule(getActivity()))
.netComponent(getNetComponent())
.build().inject(this);
// use this code in onBindViewHolder method of artists recycler adapter
Glide.with(getContext())
.from(MLocalArtist.class)
.load(localArtist)
.into(localArtistViewHolder.ivArtwork);
ArtistImageLoader
public class ArtistImageLoader extends BaseGlideUrlLoader<MLocalArtist> {
#Inject
LastfmService lastfmService;
public ArtistImageLoader(Context context) {
super(context);
}
#Override
protected String getUrl(MLocalArtist model, int width, int height) {
Call<List<MArtist>> call = lastfmService.getArtistInfo(model.artistName);
try {
List<MArtist> artists = call.execute().body();
if (artists != null && artists.size() > 0) {
Timber.e(artists.get(0).toString());
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static class Factory implements ModelLoaderFactory<MLocalArtist, InputStream> {
#Override public ModelLoader<MLocalArtist, InputStream> build(Context context, GenericLoaderFactory factories) {
return new ArtistImageLoader(context);
}
#Override public void teardown() {
}
}
}
Can you help me to do it? Thank you so much!
Glide Version: 3.7.0
Integration libraries: OkHttp3 + Dagger2
Device/Android Version: Android Emulator + Asus zenfone 5
EDIT 1
ActivityComponent.java
#PerActivity
#Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
Context context();
}
AppComponent.java
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
App app();
}
NetComponent.java
#Singleton
#Component(modules = {NetModule.class, AppModule.class})
public interface NetComponent {
#Named("chartSoundCloud")
Retrofit getSoundcloudChartRetrofit();
#Named("searchSoundCloud")
Retrofit getSoundcloudSearchRetrofit();
#Named("lastFM")
Retrofit getLastFmRetrofit();
}
LastFmComponent.java
#PerActivity
#Component(dependencies = NetComponent.class, modules = {LastFmModule.class, ActivityModule.class})
public interface LastFmComponent extends ActivityComponent {
void inject(ArtistsFragment artistsFragment);
}
ActivityModule.java
#Module
public class ActivityModule {
private final Context mContext;
public ActivityModule(Context mContext) {
this.mContext = mContext;
}
#Provides
#PerActivity
Context provideActivityContext() {
return mContext;
}
}
AppModule.java
#Module
public class AppModule {
private App app;
public AppModule(App app){
this.app = app;
}
#Singleton
#Provides
App provideApplication() {
return app;
}
#Singleton
#Provides #Named("applicationContext")
Context provideApplicationContext(){
return app;
}
}
LastFmModule.java
#Module
public class LastFmModule {
#Provides
#PerActivity
LastfmService provideLastFmService(#Named("lastFM") Retrofit retrofit) {
return retrofit.create(LastfmService.class);
}
}
NetModule.java
#Module
public class NetModule {
static final int DISK_CACHE_SIZE = (int) MEGABYTES.toBytes(50);
#Provides
#Singleton
Cache provideOkHttpCache(#Named("applicationContext") Context application) {
Cache cache = new Cache(application.getCacheDir(), DISK_CACHE_SIZE);
return cache;
}
#Provides
#Singleton
ScdClientIdInterceptor provideScdClientIdInterceptor() {
return new ScdClientIdInterceptor();
}
#Provides
#Singleton
LastFMInterceptor provideLastFmInterceptor() {
return new LastFMInterceptor();
}
#Provides
#Singleton
HttpLoggingInterceptor provideHttpLoggingInterceptor() {
return new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
}
#Provides
#Singleton
#Named("soundcloud-Http")
OkHttpClient provideOkHttpSoundCloudClient(Cache cache, ScdClientIdInterceptor clientIdInterceptor, HttpLoggingInterceptor httpLoggingInterceptor) {
return createOkHttpClient(cache, clientIdInterceptor, httpLoggingInterceptor);
}
#Provides
#Singleton
#Named("lastFM-Http")
OkHttpClient provideOkHttpLastFmClient(Cache cache, LastFMInterceptor clientIdInterceptor, HttpLoggingInterceptor httpLoggingInterceptor) {
return createOkHttpClient(cache, clientIdInterceptor, httpLoggingInterceptor);
}
private OkHttpClient createOkHttpClient(Cache cache, Interceptor clientIdInterceptor, HttpLoggingInterceptor httpLoggingInterceptor) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(clientIdInterceptor)
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
return okHttpClient;
}
#Provides
#Singleton
Gson provideGson() {
return GsonFactory.create();
}
#Provides
#Singleton
#Named("searchSoundCloud")
Retrofit provideSearchSoundCloudRetrofit(Gson gson, #Named("soundcloud-Http") OkHttpClient okHttpClient) {
Retrofit searchRetrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_SOUNDCLOUD_API_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return searchRetrofit;
}
#Provides
#Singleton
#Named("chartSoundCloud")
Retrofit provideChartSoundCloudRetrofit(Gson gson, #Named("soundcloud-Http") OkHttpClient okHttpClient) {
Retrofit chartRetrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_SOUNDCLOUD_API_V2_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return chartRetrofit;
}
#Provides
#Singleton
#Named("lastFM")
Retrofit provideLastFmRetrofit(Gson gson, #Named("lastFM-Http") OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.LASTFM_API_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return retrofit;
}
}
My assumption is that your ArtistImageLoader is defined in an separate class. The reason for the your problem is the way dagger works. It only injects fields annotated with #Inject on the class you specified as parameter of the inject method. Therefore nothing inside your ArtistImageLoader annotated with #Inject will be injected, but only the annotated fields, which are defined inside your ArtistsFragment.
I would recommend to define an LastfmService field with the #Inject annotation in your fragment and pass the instance to your Glide LoaderFactory. The factory can provide it to the instances of the loader. It's not the nicest solution, but since you can't directly pass it to the instances this seems to viable workaround.
Another approach would be to build your dependency tree inside your custom Application. This allows you access the dependencies from anywhere without being dependent on the activity lifecycle.
Hello i use spring boot 1.3.2 version. I have a custom argument resolver which's name is ActiveCustomerArgumentResolver. Everything is great, resolveArgument method works fine but i can't initialize my service component which is of my custom arg. resolver. Is there a problem with lifecycle process? Here is my code:
import org.springframework.beans.factory.annotation.Autowired;
//other import statements
public class ActiveCustomerArgumentResolver implements HandlerMethodArgumentResolver {
#Autowired
private CustomerService customerService;
#Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(ActiveCustomer.class) && parameter.getParameterType().equals(Customer.class))
return true;
else
return false;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Principal userPrincipal = webRequest.getUserPrincipal();
if (userPrincipal != null) {
Long customerId = Long.parseLong(userPrincipal.getName());
return customerService.getCustomerById(customerId).orNull(); //customerService is still NULL here, it keeps me getting NullPointerEx.
} else {
throw new IllegalArgumentException("No user principal is associated with the current request, yet parameter is annotated with #ActiveUser");
}
}
}
Let the Spring create the resolver for you by making it a Component:
#Component
public class ActiveCustomerArgumentResolver implements HandlerMethodArgumentResolver {...}
Then inject the resolver into your WebConfig instead of simply using the new, like following:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Autowired private ActiveCustomerArgumentResolver activeCustomerArgumentResolver;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(activeCustomerArgumentResolver);
}
}
This is how i've solved the problem, not a generic one but helps me a lot:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application extends WebMvcConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(activeCustomerArgumentResolver());
}
#Bean
public ActiveCustomerArgumentResolver activeCustomerArgumentResolver() {
return new ActiveCustomerArgumentResolver();
}
}
At the moment I have a Base class that contains a member I would like to inject. However, I would like the concrete type of this member to depend on the Subclass being instantiated. What I am aiming for is something along these lines:
public interface StringInterface {
public String getString();
}
public class HelloStringConcrete implements StringInterface {
public String getString() {
return "Hello";
}
}
public class WorldStringConcrete implements StringInterface {
public String getString() {
return "World";
}
}
public abstract class Base {
#Inject StringInterface member;
public Base() {
// Assume access to object graph
MyObjectGraph.get().inject(this);
}
public void printSomething() {
System.out.println(member.getString());
}
}
public class SubclassHello extends Base {}
public class SubclassWorld extends Base {}
#Module(injects = {SubclassHello.class})
public class HelloModule {
#Provides StringInterface provideStringInterface() {
return new HelloStringConcrete();
}
}
#Module(injects = {SubclassWorld.class})
public class WorldModule {
#Provides StringInterface provideStringInterface() {
return new WorldStringConcrete();
}
}
So now what I would like to do is something along the lines of:
#Module(
includes = {
HelloModule.class,
WorldModule.class
}
)
public class BigModule {}
// Somewhere in another piece of code...
objectGraph = ObjectGraph.create(new BigModule());
// In yet another piece of code...
SubclassHello hello = new SubclassHello();
SubclassWorld world = new SubclassWorld();
hello.printSomething();
world.printSomething();
// Hopefully would result in :
// Hello
// World
This type of setup won't work though, because including two modules with the same provider will result in a duplicate provider error at compile time. It would be cool to see a solution to this problem without introducing #Named or #Qualifer annotations, or using scoped graph extensions via graph.plus() because these strategies necessarily introduce coupling to the subclasses
This is possible but I think the code I've attached below is more coupled than using scoped graphs or annotations. Basically you can use constructor injection to inject concrete dependencies to your
SubclassHello and SubclassWorld.
public abstract class Base {
private final StringInterface member;
public Base(StringInterface member) {
this.member = member;
}
...
}
#Module(injects = {SubclassWorld.class})
public class WorldModule {
#Provides
WorldStringConcrete provideStringInterface() {
return new WorldStringConcrete();
}
}
public class SubclassWorld extends Base {
#Inject
public SubclassWorld(WorldStringConcrete worldStringConcrete) {
super(worldStringConcrete);
}
}
#Module(injects = {SubclassHello.class})
public class HelloModule {
#Provides
HelloStringConcrete provideStringInterface() {
return new HelloStringConcrete();
}
}
public class SubclassHello extends Base {
#Inject
public SubclassHello(HelloStringConcrete helloStringConcrete) {
super(helloStringConcrete);
}
}
// Somewhere in another piece of code...
ObjectGraph objectGraph = ObjectGraph.create(new BigModule());
// In yet another piece of code...
SubclassHello hello = objectGraph.get(SubclassHello.class);
SubclassWorld world = objectGraph.get(SubclassWorld.class);
I don't think there are other solutions. How could Dagger find out which StringInterface implementations should be injected to the concrete classes?