将参数从片段传递给视图模型函数

时间:2021-02-17 09:13:02

标签: android viewmodel android-livedata

你能告诉我我的方法是否正确吗?它有效,但我不知道它是否是正确的架构。我在某处读到我们应该避免在负责创建片段/活动的函数上调用 viewmodel 函数,这主要是因为屏幕方向更改会调用网络请求,但我确实需要将参数从一个视图模型传递到另一个视图模型。重要的是我正在使用 Dagger Hilt 依赖注入,所以为每个视图模型创建工厂是不合理的?

假设我有项目的 RecyclerView,点击我想启动带有细节的新片段 - 常见的事情。由于这些屏幕的逻辑很复杂,我决定将单个视图模型分成两个 - 一个用于列表片段,一个用于详细信息片段。

items structure

ItemsFragment 具有侦听器并使用以下代码启动详细信息片段:

    fun onItemSelected(item: Item) {
        val args = Bundle().apply {
            putInt(KEY_ITEM_ID, item.id)
        }
        findNavController().navigate(R.id.action_listFragment_to_detailsFragment, args)
    }

然后在 ItemDetailsFragment 函数中的 onViewCreated 类中,我收到传递的参数,将其保存在 ItemDetailsViewModel itemId 变量中,然后启动 requestItemDetails() 函数进行 api 调用将结果保存到 LiveData 中,由 ItemDetailsFragment

观察
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        //...
        val itemId = arguments?.getInt(KEY_ITEM_ID, -1) ?: -1
        viewModel.itemId = itemId
        viewModel.requestItemDetails()
        //...
    }

ItemDetailsViewModel

class ItemDetailsViewModel @ViewModelInject constructor(val repository: Repository) : ViewModel() {

    var itemId: Int = -1

    private val _item = MutableLiveData<Item>()
    val item: LiveData<Item> = _item

    fun requestItemDetails() {
        if (itemId == -1) {
            // return error state
            return
        }

        viewModelScope.launch {
            val response = repository.getItemDetails(itemId)
            //...
            _item.postValue(response.data)
        }
    }
}

2 个答案:

答案 0 :(得分:2)

好消息是这就是 SavedStateHandle 的用途,它会自动接收参数作为其初始映射。

@HiltViewModel
class ItemDetailsViewModel @Inject constructor(
    private val repository: Repository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val itemId = savedStateHandle.getLiveData(KEY_ITEM_ID)

    val item: LiveData<Item> = itemId.switchMap { itemId ->
        liveData(viewModelScope.coroutineContext) {
            emit(repository.getItemDetails(itemId).data)
        }
    }

答案 1 :(得分:0)

<块引用>

我们应该避免在负责创建片段/活动的函数上调用 viewmodel 函数,主要是因为屏幕方向改变会调用网络请求

是的,在您的示例中,每当创建 ItemDetailsFragment 的视图时都会执行请求。

查看 this GitHub issue,了解 Hilt 中的辅助注入支持。辅助注入的要点是在对象创建时传递额外的依赖项。

这将允许您通过构造函数传递 itemId,然后将允许您在 ViewModelinit 块中访问它。

class ItemDetailsViewModel @HiltViewModel constructor(
    private val repository: Repository,
    @Assisted private val itemId: Int
) : ViewModel() {

    init {
        requestItemDetails()
    }

    private fun requestItemDetails() {
        // Do stuff with itemId.
    }
}

这样网络请求将在创建 ItemDetailsViewModel 时只执行一次。


当该功能可用时,您可以尝试使用 GitHub 问题中建议的解决方法或使用标志模拟 init 块:

class ItemDetailsViewModel @ViewModelInject constructor(
    private val repository: Repository
) : ViewModel() {

    private var isInitialized = false

    fun initialize(itemId: Int) {
        if (isInitialized) return
        isInitialized = true

        requestItemDetails(itemId)
    }

    private fun requestItemDetails(itemId: Int) {
        // Do stuff with itemId.
    }
}