【问题标题】:Pass arguments from fragment to viewmodel function将参数从片段传递到视图模型函数
【发布时间】:2021-02-17 09:13:02
【问题描述】:

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

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

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

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

然后在onViewCreated函数的ItemDetailsFragment类中,我收到传递的参数,将其保存在ItemDetailsViewModelitemId变量中,然后启动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)
        }
    }
}

【问题讨论】:

    标签: android viewmodel android-livedata


    【解决方案1】:

    好消息是这就是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)
            }
        }
    

    【讨论】:

    • 如何从片段中传递参数?
    • 已保存状态句柄为空!
    • @HussienFahmy 如果配置正确,它就不是空的
    • 这就是我要找的。为什么文档中没有提到 SavedStateHandle 也处理 fragment.arguments?
    • @VolodymyrZakharov 您在导航调用期间传递的键值包可以在 savedStateHandle 中检索。例如:findNavController().navigate(R.id.destination, Bundle().apply { putString("key", "value") })
    【解决方案2】:

    我们应该避免在负责创建片段/活动的函数上调用 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.
        }
    }
    

    【讨论】:

    • 感谢您的建议。我只是想使用某种标志。同时我意识到我想要实现不可能,因为我想将参数传递给函数而不传递它......据我所知,关于在 onCreateView 中传递参数的部分是否正确?我只需要注意避免在 viewmodel 类中多次不必要的 api 调用?
    • 一般来说,这是正确的。虽然我猜有一些特定的用例你想在配置更改时重新加载,例如为横向模式下载更详细的图像。您可以将我的建议视为rule of thumb
    猜你喜欢
    • 1970-01-01
    • 2017-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多