我正在使用MVVM尝试Dagger 2。这是我到目前为止所拥有的,
我的ViewModel绑定模块:
@Module
public abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(MedicationsViewModel.class)
@Singleton
abstract ViewModel bindMedicationsViewModel(MedicationsViewModel viewModel);
@Binds
@Singleton
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
}
我的片段:
class MedicationsFragment : DaggerFragment() {
private lateinit var binding : FragmentMedicationsBinding
private lateinit var viewModel: MedicationsViewModel
@Inject lateinit var viewModelFactory : ViewModelFactory
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentMedicationsBinding.inflate(inflater, container, false)
viewModel = ViewModelProviders.of(this, viewModelFactory).get(MedicationsViewModel::class.java)
val adapter = MedicationAdapter()
binding.rvMedications.adapter = adapter
viewModel.getMedications().observe(viewLifecycleOwner, Observer { items ->
if (items != null) {adapter.submitList(items); adapter.notifyDataSetChanged()}
})
return binding.root
}
}
我的ViewModel是这样的:
class MedicationsViewModel @Inject constructor(
private val repository: MedicationRepository,
): ViewModel() {
private var medications : MutableLiveData<MutableList<Medication>> = MutableLiveData()
private val disposable: CompositeDisposable = CompositeDisposable()
fun getMedications() : MutableLiveData<MutableList<Medication>>{
getMockMedications()
return medications }
private fun getMockMedications(){
val medication1 = Medication(1,"Mock Med 1", "2018-01-01","once per day",true,null)
val medication2 = Medication(2,"Mock Med 2", "2018-01-02","once per day",false,"before 15 minutes")
val mockList: MutableList<Medication> = mutableListOf()
mockList.add(medication1)
mockList.add(medication2)
medications.postValue(mockList)
}
fun addMedication(medication: Medication){
medications.value?.add(medication)
medications.notifyObserver()
}
fun <T> MutableLiveData<T>.notifyObserver() {
this.postValue(this.value)
}
}
通过片段中的一个按钮,我正在打开一个活动,并将另一个Medication项目添加到我的视图模型中。从活动结束()后,我可以在RecyclerView中看到片段中新添加的药物项目。但是,当我旋转屏幕时,我丢失了新添加的项目,并再次离开了我的模拟药物项目。这是我认为正在创建我的视图模型的新实例的地方。
问题可能是Dagger每当重新创建我的片段(和容器活动)时都创建我的视图模型的新实例的原因。但是我认为使用@Singleton范围注释ViewModelFactory可以解决此问题。
谁能看到我所缺少的东西?
谢谢。
答案 0 :(得分:2)
您在这里混合了至少4种不同的东西。
问题可能是由于Dagger每当重新创建我的片段(和容器活动)时都为视图模型创建新实例的原因。
在注入片段时,您声明viewModel
将被字段注入(因为在属性上添加了@Inject
注释...)
@Inject lateinit var viewModelFactory : ViewModelFactory
这意味着Dagger将在您调用AndroidInjection.inject(..)
时注入字段。由于您使用构造函数注入(在@Inject
的构造函数上使用MedicationsViewModel
),Dagger每次都会创建一个 new 对象(您没有限制类的作用),但是您会绝对不要使用该对象,因为...
在 发生后,您立即使用ViewModelFactory
viewModel = ViewModelProviders.of(this, viewModelFactory).get(MedicationsViewModel::class.java)
由于ViewModelFactory使用@Binds @IntoMap @Singleton bindMedicationsViewModel
,因此应该重用相同的对象,并且受@Singleton
限制。您没有共享实现,所以如果它不是同一对象,则可能无法正确处理@Singleton
组件。
但是我认为使用@Singleton范围注释ViewModelFactory可以解决此问题。
通常,您应该将作用域放在类上而不是绑定上。
@Singleton // scope the class!
class MedicationsViewModel @Inject constructor()
这是因为通常是一个实现细节,它包含了什么范围。但是无论如何,您完全不应该在这里使用@Singleton
,因为您试图使用ViewModelthing 。如果要将@Singleton
用于ViewModel,则不需要整个ViewModelProviders
。如您所见,这只会导致错误和混乱,因为两者的作用域不同且处理方式不同。
假设您要使用ViewModel,请不用Dagger限制ViewModel的范围。您让Android Arch组件处理事情。您仅使用Dagger删除对象创建的样板,以便ViewModel.Factory
可以在需要时轻松创建新的ViewModel 。您可以通过将ViewModel绑定到传递给工厂的集合(带有<Class, Provider<ViewModel>>
的集合)中来进行操作,因此,调用provider.get()
将总是按预期方式创建一个新对象。生命周期毕竟是由arch组件处理的。
@Binds
@IntoMap
@ViewModelKey(MedicationsViewModel.class) // no scope here!
abstract ViewModel bindMedicationsViewModel(MedicationsViewModel viewModel);
// neither here!
class MedicationsViewModel @Inject constructor()
设置正确后,ViewModelStoreOwner(ViewModelProviders.of(this)
)将管理ViewModels生命周期,并决定何时重新使用或重新创建模型。如果要在ViewModel上引入Dagger范围,则它要么什么都不做(如果范围较短,则可能会导致错误或意外行为(使用@Singleton
或寿命较长的范围时)
另外,ViewModel.Factory
也应该不受作用域
@Binds // no scope here either!
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
这样一来,您还可以在Fragment / Activity范围内的组件上添加ViewModels,并使您能够注入非单例范围内的类。这将大大提高灵活性。该类的作用域也没有任何好处,因为ViewModels不会在配置更改期间存储在其中,并且不保留任何状态。
最后但并非最不重要的一点是,您没有使用Dagger注入字段,而是使用ViewModelProviders
对其进行了初始化。尽管它不应该做任何事情,但由于稍后会再次覆盖它,因此它仍然是难闻的气味,只会导致混乱。
// in your fragment / acitvity
lateinit var viewModelFactory : ViewModelFactory // no @Inject for the viewmodel here!
// it will be assigned in onCreate!
viewModel = ViewModelProviders.of(this, viewModelFactory).get(MedicationsViewModel::class.java)
我希望这可以解决一些问题!
答案 1 :(得分:0)
如果这对其他人有帮助,则可能需要将viewModel实例移动到onActivityCreated
,以确保已创建viewModelStore。
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
viewModel = ViewModelProviders.of(this, viewModelProviderFactory).get(MedicationsViewModel.class);
...
}