DetailFragment 的 Viewmodel 是否应该在实例之间共享?

问题描述 投票:0回答:1

我写了一个显示商店列表的主片段,称之为 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

  1. 在 StoreFeedFragment 上单击进入商店 1。
  2. StoreDetailFragment 显示商店 1 的详细信息。
  3. 点击返回返回 StoreFeedFragment。
  4. 返回 StoreFeedFragment 后点击 store 2.
  5. StoreDetailFragment 显示商店 1 的详细信息,然后是商店 2。它不应显示商店 1 的详细信息,因为单击了商店 2。
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 之间共享?我需要更改代码中的哪些内容才能遵循此问题的典型解决方案?提前致谢。

android mvvm android-livedata
1个回答
0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.