Внедрение ViewModels с зависимостью контекста Activity - PullRequest
0 голосов
/ 27 августа 2018

Я использую dagger.android и хочу ввести ViewModels.С другой стороны, у меня есть ApiModule, который зависит от контекста активности,

Вот AppComponent

@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        AppModule.class,
        ActivityBindingModule.class,
        ViewModelModule.class
})
public interface AppComponent extends AndroidInjector<MyApp> {
    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<MyApp> {}
}

ViewModelModule предоставляет ViewModelProvider.Factory

@Module
abstract class ViewModelModule {
    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory(MyAppViewModelFactory factory);
}

Вот ActivityBindingModule:

@Module
abstract class ActivityBindingModule {
    //MainActivity
    @ActivityScoped
    @ContributesAndroidInjector(modules = {
            MainActivityModule.class,
            ApiModule.class
    })
    abstract MainActivity contributeMainActivityInjector();

    @Module
    abstract class MainActivityModule {
        @ActivityScoped
        @Binds
        abstract Activity bindMainActivity(MainActivity activity);

        @Binds
        @IntoMap
        @ViewModelKey(UserViewModel.class)
        abstract ViewModel bindUserViewModel(UserViewModel userViewModel);
    }

    //SecondActivity
    @ActivityScoped
    @ContributesAndroidInjector(modules = {
            SecondActivityModule.class,
            ApiModule.class
    })
    abstract SecondActivity contributeSecondActivityInjector();

    @Module
    abstract class SecondActivityModule {
        @ActivityScoped
        @Binds
        abstract Activity bindSecondActivity(SecondActivity activity);

        @Binds
        @IntoMap
        @ViewModelKey(DetailViewModel.class)
        abstract ViewModel bindDetailViewModel(DetailViewModel detailViewModel);
    }
}

У меня есть следующие иерархии инъекций:

  • MainActivity --inject--> ViewModelProvider.Factory --inject--> MyAppViewModelFactory --inject--> UserViewModel --inject--> UserRepository --inject--> ApiService --inject--> Activity context

  • SecondActivity --inject--> ViewModelProvider.Factory --inject--> MyAppViewModelFactory --inject--> DetailViewModel --inject--> DetailRepository --inject--> ApiService --inject--> Activity context

Мы предоставляем:

  • Activity контекст MainActivityModuleSecondActivityModule)
  • ApiService в ApiModule (добавляется в модули каждого действия в ActivityBindingModule)
  • UserRepository и DetailRepository @Singleton
  • UserViewModel в MainActivityModuleи DetailViewModel on SecondActivityModule
  • MyAppViewModelFactory is @ Singleton

Вот MyAppViewModelFactory:

@Singleton
public class MyAppViewModelFactoryimplements ViewModelProvider.Factory {
    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

    @Inject
    MyAppViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }

    @SuppressWarnings("unchecked")
    @Override
    @NonNull
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        Provider<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("unknown model class " + modelClass);
        }
        try {
            return (T) creator.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Я получил эту ошибку:

error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> cannot be provided without an @Provides-annotated method.
java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> is injected at
com.example.viewmodels.MyAppViewModelFactory.<init>(creators)
com.example.viewmodels.MyAppViewModelFactory is injected at
com.example.di.ViewModelModule.bindViewModelFactory(factory)
android.arch.lifecycle.ViewModelProvider.Factory is injected at
com.example.activities.MainActivity.viewModelFactory
com.example.activities.MainActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.example.di.AppComponent ? com.example.di.ActivityBindingModule_ContributeMainActivityInjector.MainActivitySubcomponent

Я пытался переместить bindUserViewModelMainActivityModule) и bindDetailViewModelSecondActivityModule) на ViewModelModule, и я получил эту ошибку:

error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] tk.medlynk.medlynk.http.ApiService cannot be provided without an @Provides-annotated method.
com.example.http.ApiService is injected at
com.example.repositories.UserRepository.<init>(apiService)
com.example.repositories.UserRepository is injected at
com.example.viewmodels.UserViewModel.<init>(userRepo)
com.example.viewmodels.UserViewModel is injected at
com.example.di.ViewModelModule.bindUserViewModel(userViewModel)
java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> is injected at
com.example.viewmodels.MyAppViewModelFactory.<init>(creators)
com.example.viewmodels.MyAppViewModelFactory is injected at
com.example.di.ViewModelModule.bindViewModelFactory(factory)
android.arch.lifecycle.ViewModelProvider.Factory is injected at
com.example.activities.MainActivity.viewModelFactory
com.example.activities.MainActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.example.di.AppComponent ? com.example.di.ActivityBindingModule_ContributeMainActivityInjector.MainActivitySubcomponent

Что мне здесь не хватает? iosched проект делает то же самое без проблем

Ответы [ 2 ]

0 голосов
/ 27 августа 2018

Все ваши настройки звучат правильно. Основная причина ApiService не указана.

Ошибка: [Dagger / MissingBinding] [dagger.android.AndroidInjector.inject (T)] tk.medlynk.medlynk.http.ApiService не может быть предоставлен без аннотированного метода @ Provides.

Чтобы разрешить его, просто предоставьте его в каком-то модуле.

class AppModule {
    @Singleton
    @Provides
    fun provideApiService(): ApiService {
        ....
    }
}
0 голосов
/ 27 августа 2018

Во-первых, вам нужно реструктурировать настройки Dagger2 на уровнях Application и Activity.Во-вторых, ваш ViewModelProviderFactory класс несколько сложен.Я реструктурирую ваш код для вас сейчас.

Уровень приложения

AppComponent

@Singleton
@Component(modules = {
    AndroidSupportInjectionModule.class,
    AppModule.class,
    ActivityBindingModule.class
})
public interface AppComponent extends AndroidInjector<MyApp> {
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<MyApp> {}
}

ActivityBindingModule

@Module
abstract class ActivityBindingModule {

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

  //This goes for other activities
}

AppModule Этот класс предоставляет только классы, которые являются общими и могут использоваться в любых действиях в приложении.

@Module
class AppModule {

  @Singleton
  @Provides
  //Your ApiService interface comes here
  //Retrofit
  //Database
}

App Класс должен расширять DaggerApplication для простоты.При этом автоматически вводятся члены

  class MyApp extends DaggerApplication

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

      @Override
      protected AndroidInjector<? extends DaggerApplication> 
      applicationInjector() {
        return DaggerHorizonMainComponent.builder().create(this);
   }

Уровень активности

MainActivityModule Здесь у вас есть ViewModel и ViewModelProviderFactory

@Module
class MainACtivityModule {

@Provides
MainActivityViewModel provideMainActivityViewModel(){
    return new MainActivityViewModel();
}

@Provides
ViewModelProvider.Factory provideViewModelProvider(MainActivityViewModel 
viewModel){
    return new ViewModelProviderFactory<>(viewModel);
}

//Your repository comes here too
}

ViewModel class

 class MyViewModel extends ViewModel {

 @Inject
 public MyViewModel(){
 }
 }

ViewModelProviderFactory Просто скопируйте этот код

 public class ViewModelProviderFactory<V> implements 
      ViewModelProvider.Factory {
        private V viewModel;

        public ViewModelProviderFactory(V viewModel) {
           this.viewModel = viewModel;
        }

        @Override
        public <T extends ViewModel> T create(Class<T> modelClass) {
            if (modelClass.isAssignableFrom(viewModel.getClass())) {
                 return (T) viewModel;
            }
            throw new IllegalArgumentException("Unknown class name");
        }
    }

Наконец, в своем классе MainActivity вы внедрите ViewModelProviderFactory.Вам не нужно вводить Dagger здесь, поскольку вы расширяете его до DaggerAppCompatActivity

   public class Mainactivity extends DaggerAppCompatActivity{

       @Inject
       ViewModelProvider.Factory factory;
       private MyViewModel viewModel;

       @Override
       protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       //You set your view here
       viewmodel = 
       ViewModelProviders.of(this,factory).get(MyViewModel.class);

       }
     }
...