Реализация модели представления с использованием dagger2 в BaseFragment - PullRequest
0 голосов
/ 21 января 2020

Я новичок в Dagger и все еще учусь этому. Согласно учебникам и блогам, которые я читаю, в настоящее время Android не имеет способа ввода dependencies в ViewModels, поэтому нам нужно использовать пользовательский ViewModelProvider.Factory, в котором говорится, что мне удалось заполучить этот

public class ViewModelProviderFactory implements ViewModelProvider.Factory {
private static final String TAG = ViewModelProviderFactory.class.getSimpleName();
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

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

@Override
public <T extends ViewModel> T create(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);
    }
 }
}

Это работает, это работало для многих моих вариантов использования до сих пор. Первоначально у меня был экземпляр ViewModel с чем-то вроде этого

public AFragment extends BaseFragment{

    @Inject
    ViewModelProviderFactory providerFactory;
    private MyViewModel viewModel;

    MyViewModel getViewModel(){
       return ViewModelProviders.of(this, providerFactory).get(MyViewModel.class);
    }


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      viewModel = getViewModel();
      tokenAuthenticator.setAuthenticatorListener(this);
    }


 }

Но по мере роста проекта я понял, что это не изящно, мне пришлось делать это во всех моих фрагментах, поэтому я выбрал другой подход Я вместо этого хотел создать экземпляр моей ViewModel в BaseFragment и сделал это

public abstract class BaseFragment<T extends BaseViewModel, E extends ViewDataBinding> extends DaggerFragment implements TokenAuthenticator.AuthenticatorListener {
    private static final String TAG = BaseFragment.class.getSimpleName();
    public E binding;
    public final CompositeDisposable disposables = new CompositeDisposable();
    public T viewModel;
    @Inject
    ViewModelProviderFactory providerFactory;
    private int layoutId;


    /**
     * @return view model instance
     */


    public T getViewModel() {
        final Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
        return ViewModelProviders.of(this, providerFactory).get((Class<T>)types[0]);
    }

}

Это дает мне ошибку компиляции

  A binding with matching key exists in component: xxx.xxx.core.base.dagger.builders.FragmentBuilderModule_ContributeDeliveryPlanFragment.DeliveryPlanFragmentSubcomponent
  .
  .
  .
  A binding with matching key exists in component: xxx.xxx.core.base.dagger.builders.FragmentBuilderModule_ContributePayWithMtmMobileMoneyFragment.PayWithMtmMobileMoneyFragmentSubcomponent
      java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
         xxx.xxx.core.base.ViewModelProviderFactory(creators)
      xxx.xxx.core.base.ViewModelProviderFactory is injected at
          mika.e.mikaexpressstore.core.base.BaseFragment.providerFactory
      xxx.xxx.xxx.xxx.cashondelivery.CashOnDeliveryFragment is injected at
          dagger.android.AndroidInjector.inject(T) [xxx.xxx.core.base.dagger.component.AppComponent → xxx.xxx.core.base.dagger.builders.FragmentBuilderModule_ContributeCashOnDeliveryFragment.CashOnDeliveryFragmentSubcomponent]
2 errors

Из-за ошибки Я могу сказать, что Dagger жалуется, что ViewModelProviderFactory вводится в base, но используется в child, мне нужна помощь, есть ли способ заставить эту работу работать? Конечно, я хочу сократить на шаблон и повторяющийся код.

1 Ответ

0 голосов
/ 21 января 2020

Я наконец исправил это, не так, как я хотел, но лучше, чем необходимость создания экземпляров каждой модели представления из дочернего класса. Прочитав этот ответ , я понял, что это невозможно, поэтому вместо этого я удалил аннотацию @Inject из ViewModelProviderFactory в моем BaseFragment, и это выглядело как

public abstract class BaseFragment<T extends BaseViewModel, E extends ViewDataBinding> extends DaggerFragment implements TokenAuthenticator.AuthenticatorListener {
    private static final String TAG = BaseFragment.class.getSimpleName();
    public E binding;
    public final CompositeDisposable disposables = new CompositeDisposable();
    public T viewModel;
    @Inject
    private ViewModelProviderFactory providerFactory;
    private int layoutId;


    /**
     * @return view model instance
     */


    public T getViewModel() {
        final Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
        return ViewModelProviders.of(this, providerFactory).get((Class<T>)types[0]);
    }


    @MainThread
    protected final void setProviderFactory(ViewModelProviderFactory providerFactory) {
        this.providerFactory = providerFactory;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     viewModel = getViewModel();
   }



}

И вместо этого вставил provider из дочерних фрагментов, затем вызвал сеттер из BaseFragment

public AFragment extends BaseFragment{

    @Inject
    ViewModelProviderFactory providerFactory;
    private MyViewModel viewModel;


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        setLayoutId(R.layout.layout_layout);
        setProviderFactory(providerFactory);
        super.onCreate(savedInstanceState);
    }

}

Ключ здесь заключается в том, чтобы вызвать setProviderFactory(provider) до super.onCreate(savedInstanceState), потому что когда super onCreate вызывается провайдером не должен быть нулевым, он должен быть установлен и готов к созданию ViewModel

...