我写了一个显示商店列表的主片段,称之为 StoreFeedFragment。如果您单击商店,它会调用 StoreDetailFragment 的 newInstance 以提供所单击商店的详细信息。这两个片段都存在于使用 replace() 交换 2 个片段的 MainActivity 中。
class MainActivity : AppCompatActivity() {
@Inject
lateinit var storeItemViewModelFactory: ViewModelFactory<StoreItemViewModel>
private val storeItemViewModel: StoreItemViewModel by lazy {
storeItemViewModelFactory.get<StoreItemViewModel>(
this
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
TCApplication.getAppComponent().inject(this)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
val storeFeedFragment = StoreFeedFragment()
supportFragmentManager.beginTransaction()
.replace(
R.id.container, storeFeedFragment,
StoreFeedFragment.TAG
)
.commit()
}
storeItemViewModel.selectedItem.observe(this) { event ->
event.getContentIfNotHandled()?.apply {
val fragment = StoreDetailFragment.newInstance(id)
supportFragmentManager.beginTransaction()
.replace(
R.id.container, fragment
).addToBackStack("feed_to_item_tag")
.commit()
}
}
}
}
Event 只是一个事件包装器,用于防止在 StoreDetailFragment 的回压后再次观察所选商店:
class Event<T>(private val content: T) {
var hasBeenHandled = false
private set
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) null
else {
hasBeenHandled = true
content
}
}
}
最近发现一个bug
class StoreDetailFragment : Fragment() {
@Inject
lateinit var viewModelFactory: ViewModelFactory<StoreDetailViewModel>
private val viewmodel: StoreDetailViewModel by lazy {
viewModelFactory.get<StoreDetailViewModel>(
requireActivity()
)
}
companion object {
private const val SELECTED_ID = "selected"
fun newInstance(storeId: String): StoreDetailFragment {
val fragment = StoreDetailFragment().also {
it.arguments = bundleOf(Pair(SELECTED_ID, storeId))
}
return fragment
}
}
override fun onCreate(savedInstanceState: Bundle?) {
TCApplication.getAppComponent().inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_store_detail, container, false)
viewmodel.storeDetailResult.observe(viewLifecycleOwner) { resultState ->
when (resultState) {
is ResultState.Loading -> { }
is ResultState.Success -> { showDetails(resultState.value) }
is ResultState.Failure -> {
Snackbar.make(
view,
"Error getting store details ${resultState.ex.message}",
500
).show() }
}
}
arguments?.getString(SELECTED_ID)?.let { storeId -> viewmodel.loadStoreDetails(storeId) }
return view
}
private fun showDetails(storeDetails: StoreDetail) {
view?.apply {
findViewById<TextView>(R.id.name).text = storeDetails.name
findViewById<TextView>(R.id.phoneNo).text = storeDetails.phoneNo
}
}
}
class StoreDetailViewModel @Inject constructor(
private val storeDetailRepository: StoreDetailRepository
): ViewModel(), CoroutineScope by MainScope() {
private val _storeDetailResult = MutableLiveData<ResultState<StoreDetail>>()
val storeDetailResult: LiveData<ResultState<StoreDetail>> = _storeDetailResult
fun loadStoreDetails(storeId: String) {
viewModelScope.launch {
try {
storeDetailRepository.getStoreDetail(storeId)
.collect { storeDetail ->
_storeDetailResult.postValue(
ResultState.Success(
storeDetail
)
)
}
} catch (e: Exception) {
_storeDetailResult.postValue(
ResultState.Failure(
"getStoreDetail($storeId)", e
)
)
}
}
}
}
从日志语句可以清楚地看出,这是因为 ViewModelFactory 仅创建了一个 StoreDetailViewModel 实例,该实例在商店 1 的 StoreDetailFragment 实例和商店 2 的 StoreDetailFragment 实例之间共享。商店 2 的 StoreDetailFragment 正在观察 StoreDetailViewModel 的最新发射(商店信息1), 然后调用 store2 的 loadStoreDetails
class ViewModelFactory<T: ViewModel>
@Inject constructor(private val viewModel: Lazy<T>) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return viewModel.get() as T
}
/**
* Returns an instance of a defined ViewModel class.
*/
inline fun <reified R: T> get(viewModelStoreOwner: ViewModelStoreOwner): T {
return ViewModelProvider(viewModelStoreOwner, this)[R::class.java]
}
}
StoreDetailViewModel 是否应该在 StoreDetailFragment 的实例 1 和实例 2 之间共享?我需要更改代码中的哪些内容才能遵循此问题的典型解决方案?提前致谢。
StoreDetailViewModel 是否应该在 StoreDetailFragment 的实例 1 和实例 2 之间共享?
是的,因为您将 ViewModel 限定在 Activity 范围内。
@Inject
lateinit var viewModelFactory: ViewModelFactory<StoreDetailViewModel>
private val viewmodel: StoreDetailViewModel by lazy {
viewModelFactory.get<StoreDetailViewModel>(
requireActivity()
)
}
我需要在代码中更改什么才能遵循此问题的典型解决方案?
Scope 你的 ViewModel 到片段。
@Inject
lateinit var viewModelFactory: ViewModelFactory<StoreDetailViewModel>
private val viewmodel: StoreDetailViewModel by lazy {
viewModelFactory.get<StoreDetailViewModel>(
this
)
}
参见:https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-apis