我想使用 saveStateHandle 将一些数据从活动直接传递到片段的视图模型。
在我的活动中我有:
navController.addOnDestinationChangedListener { controller, _, _ ->
controller.currentBackStackEntry?.savedStateHandle?.set(
"foo",
"bar"
)
}
Run Code Online (Sandbox Code Playgroud)
所以viewModel中的代码如下所示:
MyViewModel(state: SavedStateHandle) : ViewModel() {
init {
state
?.getStateFlow("foo", "")
?.onEach { /* do something */ }
?.launchIn(viewModelScope)
}
Run Code Online (Sandbox Code Playgroud)
由于某种原因,期望值bar永远不会被发出。
我已经检查了Fragment本身,数据就在那里:
val handle = findNavController().currentBackStackEntry?.savedStateHandle
handle?.getLiveData<String>("foo")?.observe(viewLifecycleOwner) {
// here it is
}
Run Code Online (Sandbox Code Playgroud)
但是是否有可能将数据直接传递到 viewModel 的 savingStateHandle ?我相信应该是这样,因为导航器以某种方式通过了。
android android-savedstate android-viewmodel android-jetpack-navigation viewmodel-savedstate
我的视图模型工厂:
class ViewModelFactory @Inject constructor(
private val viewModelMap: MutableMap<Class<out ViewModel>, ViewModelAssistedFactory<out ViewModel>>,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle?
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
@Throws(IllegalStateException::class)
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
return viewModelMap[modelClass]?.create(handle) as? T ?: throw IllegalStateException("Unknown ViewModel class")
}
}
Run Code Online (Sandbox Code Playgroud)
活动:
@Inject
lateinit var viewModelFactory: ViewModelFactory
protected val viewModel: ViewModel by lazy { ViewModelProvider(this, viewModelFactory).get(getViewModelClass()) }
Run Code Online (Sandbox Code Playgroud)
视图模型:
@AssistedInject.Factory
interface Factory : ViewModelAssistedFactory<SplashViewModel>
Run Code Online (Sandbox Code Playgroud)
我想知道如何动态提供 defaultArgs 而不是:
活动模块
@Module
companion object …Run Code Online (Sandbox Code Playgroud) android android-lifecycle dagger-2 android-viewmodel viewmodel-savedstate
我有一个 ViewModel 类如下(简化以演示我面临的问题)
class MyViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
init {
savedStateHandle.set(KEY, "Something")
}
}
Run Code Online (Sandbox Code Playgroud)
我有一个 MockK 测试类,如下所示
@Test
fun `test something simple`() {
val savedStateHandle = mockk<SavedStateHandle>()
val viewModel = MyViewModel(savedStateHandle)
verify { savedStateHandle.set(MyViewModel.KEY, "Something") }
}
Run Code Online (Sandbox Code Playgroud)
通过运行测试,我得到以下错误
io.mockk.MockKException: no answer found for: SavedStateHandle(#1).set(Key, Something)
Run Code Online (Sandbox Code Playgroud)
我想我也需要处理setfor SavedStateHandle。我怎么能这样做MockK呢?
我有一个屏幕,可以加载一堆请求并从同一屏幕上的用户和外部 WebView 收集一些数据。因此,我有一个包含这些复杂请求对象(+用户输入数据)的 ViewModel。我需要通过系统启动的进程死亡来持久保存这些数据,这SavedStateHandle是设计的目的。但我不想将这些数据保留在数据库中,因为它只与当前的用户体验相关。
我已将我的 ViewModel 与 Hilt 集成并收到了SaveStateHandle. 因为我有一些复杂的对象,可以在代码中的多个位置访问/修改它们,所以我无法“随时随地”保存它们。我让它们实现Parcelable,只是想立即保存它们。不幸的是,ViewModel 没有像onSaveInstanceState().
现在,我尝试使用onCleared()听起来像是写入句柄的好地方。但事实证明,.set()我在那里执行的所有操作都会丢失(我正在使用开发人员选项“不保留活动”进行测试。当我.set()在其他地方使用时,它确实有效)。因为 ViewModel 不依赖于单个片段/活动的生命周期,而是依赖于 NavGraph,所以我无法从它们的onSaveInstanceState().
我如何/在哪里可以正确地保留我的状态SaveStateHandle?
我正在尝试使用以下代码:
suspend fun <T> SavedStateHandle.getStateFlow(
key: String,
initialValue: T? = get(key)
): MutableStateFlow<T?> = this.let { handle ->
withContext(Dispatchers.Main.immediate) {
val liveData = handle.getLiveData<T?>(key, initialValue).also { liveData ->
if (liveData.value === initialValue) {
liveData.value = initialValue
}
}
val mutableStateFlow = MutableStateFlow(liveData.value)
val observer: Observer<T?> = Observer { value ->
if (value != mutableStateFlow.value) {
mutableStateFlow.value = value
}
}
liveData.observeForever(observer)
mutableStateFlow.also { flow ->
flow.onCompletion {
withContext(Dispatchers.Main.immediate) {
liveData.removeObserver(observer)
}
}.onEach { value ->
withContext(Dispatchers.Main.immediate) {
if (liveData.value != …Run Code Online (Sandbox Code Playgroud) android kotlin android-jetpack viewmodel-savedstate kotlin-flow
使用 SavedStateHandle 发送回结果不适用于 ViewModel 中注入的 SavedStateHandle。
使用navController.currentBackStackEntry?.savedStateHandle?它可以得到结果!
fun CreatePostScreen(
navController: NavController,
coroutineScope: CoroutineScope,
snackbarState: SnackbarHostState,
viewModel: CreatePostViewModel = hiltViewModel(),
) {
LaunchedEffect(key1 = Unit) {
navController.currentBackStackEntry?.savedStateHandle?.getStateFlow(
"result", ""
)?.collect { result ->
Timber.d("Result -> $result")
}
}
}
Run Code Online (Sandbox Code Playgroud)
在 ViewModel 中使用saveStateHandleHilt 注入不会得到结果!
@HiltViewModel
class CreatePostViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
init {
viewModelScope.launch {
savedStateHandle.getStateFlow("result", "").collect {
Timber.d("Result -> $it")
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这就是我将结果发送回前一屏幕的方式!
navController.previousBackStackEntry?.savedStateHandle?.set("result", "this is result")
Run Code Online (Sandbox Code Playgroud) android android-jetpack-navigation android-jetpack-compose viewmodel-savedstate dagger-hilt
我按照下一页中的说明创建了一个 viewModel,但当SavedStateHandle我关闭应用程序并再次打开它时,它不起作用。
这是页面:
这是我的视图模型类:
class UserViewModel(private val state : SavedStateHandle) : ViewModel(){
val userId: LiveData<String> by lazy {
state.getLiveData("userId")
}
fun setUserId(userId : String) {
state["userId"] = userId
}
val user : User by lazy {
User("")
} }
Run Code Online (Sandbox Code Playgroud)
以下是我在活动中使用 viewModel 的方式。
val userViewModel : UserViewModel by viewModels()
Run Code Online (Sandbox Code Playgroud)
我什至在我的活动中尝试过这个,但这也不起作用!
val userViewModel: UserViewModel by viewModels {
SavedStateViewModelFactory(
application,
this
)
Run Code Online (Sandbox Code Playgroud)
我应该怎么做才能将数据持久化SavedStateHandle?我的意思是,打开应用程序后state仍然是空的。
在这个问题之后,我在我的应用程序中做了一些简单的更改,但它没有按我的预期工作。
我有一个计时器,当计时器完成时会发送通知。单击此通知将重新启动活动,删除所有计时器信息,这些信息主要存储在viewModel. 出于这个原因,我决定使用保存的状态viewModel。
这是我的viewModel:
class TimerViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
private val _secondsRemaining = savedStateHandle.getLiveData<Long>(SECONDS_REMAINING)
val secondsRemaining : LiveData<Long>
get() = _secondsRemaining
Run Code Online (Sandbox Code Playgroud)
viewModel这是我在我的中使用的方法Fragment:
private val timerViewModel by viewModels<TimerViewModel>()
Run Code Online (Sandbox Code Playgroud)
当我启动计时器时,我会在时钟LiveData的每个滴答声中保存剩余秒数的值。当计时器结束时,应用程序发送通知,计时器再次启动,计算一个新的周期:
timer = object : CountDownTimer(timerLengthSeconds * 1000, 1000){
override fun onFinish(){
(....)
}
override fun onTick(millisUntilFinished: Long) {
var secondsRemainingInCountdown = millisUntilFinished / 1000
(...)
_secondsRemaining.value = secondsRemainingInCountdown
}
}.start()
}
Run Code Online (Sandbox Code Playgroud)
因此,当计时器完成时,应用程序会发送通知,但计时器已重新启动,并且剩余的秒数正在更新(我已通过日志检查了这一点)。当用户单击通知时,通知会activity被终止并重新启动,并且期望会看到计时器以及保存在LiveData. 但是当 …
android ×6
kotlin ×3
viewmodel ×2
dagger-2 ×1
dagger-hilt ×1
kotlin-flow ×1
mockk ×1
persistence ×1