将参数从片段传递到视图模型函数
Pass arguments from fragment to viewmodel function
你能告诉我我的做法是否正确吗?它有效,但我不知道它是否是正确的架构。我在某处读到,我们应该避免在负责创建 fragments/activities 的函数上调用 viewmodel 函数,主要是因为屏幕方向发生变化,这会调用网络请求,但我确实需要将参数从一个 viewmodel 传递到另一个 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)
}
然后在 ItemDetailsFragment
class 在 onViewCreated
函数中我收到传递的参数,将其保存在 ItemDetailsViewModel
itemId
变量中然后启动 requestItemDetails()
函数使 api 调用哪个结果保存到由 ItemDetailsFragment
观察到的 LiveData
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)
}
}
}
we should avoid calling viewmodel function on function responsible for creating fragments/activities mainly because of screen orientation change which recall network request
是的,在您的示例中,每当创建 ItemDetailsFragment
的视图时都会执行一个请求。
查看 this GitHub issue 了解 Hilt 中的辅助注入支持。辅助注入的要点是在对象创建时传递额外的依赖关系。
这将允许您通过构造函数传递 itemId
,然后允许您在 ViewModel
的 init
块中访问它。
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.
}
}
好消息是,这就是 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)
}
}
你能告诉我我的做法是否正确吗?它有效,但我不知道它是否是正确的架构。我在某处读到,我们应该避免在负责创建 fragments/activities 的函数上调用 viewmodel 函数,主要是因为屏幕方向发生变化,这会调用网络请求,但我确实需要将参数从一个 viewmodel 传递到另一个 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)
}
然后在 ItemDetailsFragment
class 在 onViewCreated
函数中我收到传递的参数,将其保存在 ItemDetailsViewModel
itemId
变量中然后启动 requestItemDetails()
函数使 api 调用哪个结果保存到由 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)
}
}
}
we should avoid calling viewmodel function on function responsible for creating fragments/activities mainly because of screen orientation change which recall network request
是的,在您的示例中,每当创建 ItemDetailsFragment
的视图时都会执行一个请求。
查看 this GitHub issue 了解 Hilt 中的辅助注入支持。辅助注入的要点是在对象创建时传递额外的依赖关系。
这将允许您通过构造函数传递 itemId
,然后允许您在 ViewModel
的 init
块中访问它。
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.
}
}
好消息是,这就是 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)
}
}