简体   繁体   English

在 BaseFragment 中使用 dagger2 实例化视图模型

[英]Intantiating a viewmodel using dagger2 in BaseFragment

I am sort of new to Dagger and still learning it.我对Dagger有点Dagger并且仍在学习它。 According to the tutorials and blogs I read, currently Android does not have a way of injecting dependencies into ViewModels hence we need to use a custom ViewModelProvider.Factory , that said, I managed to get my hands on this one根据我阅读的教程和博客,目前 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);
    }
 }
}

It works, it has worked for many of my use cases until now.它有效,直到现在它对我的许多用例都有效。 Originally I had to an instance of a ViewModel with something like this最初我不得不使用类似这样的 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);
    }


 }

But as the project grew I realized this was not neat, I had to do this in all my fragments so I opted for a different approach, I wanted to instantiate my ViewModel in my BaseFragment instead and I did this但是随着项目的发展,我意识到这并不整洁,我必须在所有片段中都这样做,所以我选择了不同的方法,我想在BaseFragment实例化我的 ViewModel,然后我做到了

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]);
    }

} }

This gives me compile error这给了我编译错误

  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

From the error message I can tell Dagger is complaining the ViewModelProviderFactory is being injected in the base but used in the child , I need help, is there a way to make this work?从错误消息中,我可以告诉Dagger抱怨ViewModelProviderFactory正在注入base但在child ,我需要帮助,有没有办法使这项工作? surely I want to reduce on boilerplate and repetitive code.我当然想减少样板和重复代码。

I finally fixed it, not how I wanted but better than having to instantiate each viewmodel from the child class.我终于修复了它,不是我想要的,但比必须从子类实例化每个视图模型要好。 After reading this answer I came to a realization that this was not possible, so instead I removed @Inject annotation from ViewModelProviderFactory in my BaseFragment and it looked like阅读此答案后,我意识到这是不可能的,因此我从BaseFragment ViewModelProviderFactory中删除了@Inject注释,它看起来像

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();
   }



}

And injected the provider from the child fragments instead then called the setter from the BaseFragment并从子片段注入provider ,然后从BaseFragment调用 setter

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);
    }

}

Key here is to call setProviderFactory(provider) before super.onCreate(savedInstanceState) because when super onCreate is called the provider should not be null, it should be set and ready to create the ViewModel这里的关键是在super.onCreate(savedInstanceState) setProviderFactory(provider)之前调用setProviderFactory(provider)因为当调用 super onCreate 时提供者不应该为空,它应该被设置并准备好创建ViewModel

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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