没有通过 LocalViewModelStoreOwner 提供 ViewModelStoreOwner

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

我目前正在迁移 RecyclerView 以使用 Compose。我们决定采用自下而上的方法,通过单独转换视图持有者项目视图以使用

ComposeView
,其中包含可组合项。

我目前正在将一个相当复杂的视图迁移到可组合项,它需要一个视图模型。我们使用 Dagger2 进行依赖注入,因为我们相当大的代码库使得迁移到 Hilt 的范围更大,所以这被搁置了。

无论如何,当我构建并运行时,recyclerview 渲染得很好,并且可组合项在 recyclerview 中渲染。但是,当我滚动时,当屏幕上有多个

DataItemComposables
时,应用程序会崩溃并显示以下堆栈跟踪:

java.lang.IllegalStateException: No ViewModelStoreOwner was provided via LocalViewModelStoreOwner
        at com.****.****.viewHolders.DataItemViewHolder$bindData$1$1$1.invoke(DataItemViewHolder.kt:42)
        at com.****.****.viewHolders.DataItemViewHolder$bindData$1$1$1.invoke(DataItemViewHolder.kt:41)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
        at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
        at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:140)
        at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2156)
        at androidx.compose.runtime.ComposerImpl.skipToGroupEnd(Composer.kt:2422)
        at com.****.****.compose.theme.ThemeKt.MaterialTheme(Theme.kt:142)

星号刚刚被擦掉了包名称。

我的代码如下:

DataItemViewHolder.kt

override fun bindData(dataItem: DataItem, position: Int) {
        binding.root.apply {
            // Dispose the Composition when the view's LifecycleOwner
            // is destroyed
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                MaterialTheme {
                     DataItemComposable(
                        repository = repository,
                        postId = dataItem.id,
                    )
                }
            }
        }
    }

DataItemComposable.kt

@Composable
fun DataItemComposable(
    repository: DataRepository,
    postId: String,
) {
    val viewModel: DataItemViewModel = viewModel(
        factory = DataItemViewModel.DataItemViewModelFactory(
            repository,
            itemId
        )
    )

    var state by remember { viewModel.itemState }
    Box {
        // Content using state
    }

}

DataItemViewModel.kt

class DataItemViewModel(
    private val repository: DataRepository,
    private val itemId: String
) : ViewModel() {
    val itemState = mutableStateOf("")

    // ...DO some stuff with the parameters
    ...
    ...
    ...



    /* ===== Factory ===== */
    public class DataItemViewModelFactory(
        val repository: DataRepository,
        val postId: String
    ) :
        ViewModelProvider.NewInstanceFactory() {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T = 
        DataItemViewModel(
            repository,
            postId
        ) as T
    }   
}
android kotlin android-recyclerview android-jetpack-compose android-jetpack
5个回答
3
投票

解决方案:

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
         val view = ComposeView()

         view.setViewTreeViewModelStoreOwner(parent.findViewTreeViewModelStoreOwner())

        return ViewHolder(view)
    }

说明:

我们在获取 viewModel 时也遇到了同样的崩溃,这是由于以下代码导致的:

public inline fun <reified VM : ViewModel> viewModel(
viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
    "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
}

如您所见 LocalViewModelStoreOwner.current 正在变为 null,因此我们收到此错误消息。

发生这种情况是因为 Default ViewCompositionStrategy 目前是 ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool

现在让我们看看我们目前有哪些选择:

解决方案1:

我们可以使用 DisposeOnDetachedFromWindow 但这样会降低性能。

解决方案2:

我们可以从外部传递 viewModel,但是将其传递给所有 Items 很麻烦。这样我们就会失去 viewModel() 方法的好处

**以上两个解决方案不是我的,我只是写它们,因为我想告诉大家为什么我没有在我的案例中使用它们。所以我只是为了比较而提到它们。

我的解决方案:

首先我们将获取 ViewTreeViewModelStoreOwner ,然后将其传递给 onCreate 中的 ComposeView 。简单又容易。

view.setViewTreeViewModelStoreOwner(parent.findViewTreeViewModelStoreOwner())

1
投票

不确定是否是同样的问题,但我已经通过在适配器中添加它来解决它:


override fun onViewRecycled(holder: MyComposeViewHolder) {
    // Dispose the underlying Composition of the ComposeView
    // when RecyclerView has recycled this ViewHolder
    holder.composeView.disposeComposition()
}

如文档中所述此处

我的猜测是,不知何故,当旧的可组合项挂在那里而不是被丢弃时,就会产生异常。


1
投票

我可以通过将 viewModel 访问从可组合项移动到父级来解决此错误

Fragment
。然后将实例传递给
RecyclerView
s 适配器。

class FooBarFragment: Fragment() {
    // Access the ViewModel from the fragment scope
    private val viewModel by viewModels<FooBarViewModel>()

    private val rvAdapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            val composeView = holder.itemView as? ComposeView ?: return
            // Now you can use the ViewModel in your composable
            composeView.setContent { Text(text = viewModel.getText(position)) }
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) //..

        override fun getItemCount() //..
    }

    // ...
}

1
投票

我面临着同样的问题,但到目前为止,所有答案都没有帮助。 当

Composable
RecyclerView
的一部分时,崩溃发生在我身上,这似乎也是 OP 的情况。然后在视图模型加载状态期间滚动导致应用程序崩溃。

修复:

解决方法就像在

ComposeView
上设置不同的合成策略一样简单:

setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)

解释

如果你检查 kDocs,这个修复实际上是有意义的,我和 OP 使用了

DisposeOnViewTreeLifecycleDestroyed
,但是这个策略并不适合
RecyclerView
使用:

 /**
 * [ViewCompositionStrategy] that disposes the composition when the [ViewTreeLifecycleOwner]
 * of the next window the view is attached to is [destroyed][Lifecycle.Event.ON_DESTROY].
 * This strategy is appropriate for Compose UI views that share a 1-1 relationship with
 * their closest [ViewTreeLifecycleOwner], such as a Fragment view.
 */

这里是

DisposeOnDetachedFromWindow
的 kDocs:

 /**
 * [ViewCompositionStrategy] that disposes the composition whenever the view becomes detached
 * from a window. If the user of a Compose UI view never explicitly calls
 * [AbstractComposeView.createComposition], this strategy is always safe and will always
 * clean up composition resources with no explicit action required - just use the view like
 * any other View and let garbage collection do the rest. (If
 * [AbstractComposeView.createComposition] is called while the view is detached from a window,
 * [AbstractComposeView.disposeComposition] must be called manually if the view is not later
 * attached to a window.)
 */

0
投票
composeView.apply {
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
            setContent {
                HomeItemView()
            }
        }

这为我解决了问题。

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