我目前正在迁移 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
}
}
解决方案:
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())
不确定是否是同样的问题,但我已经通过在适配器中添加它来解决它:
override fun onViewRecycled(holder: MyComposeViewHolder) {
// Dispose the underlying Composition of the ComposeView
// when RecyclerView has recycled this ViewHolder
holder.composeView.disposeComposition()
}
如文档中所述此处
我的猜测是,不知何故,当旧的可组合项挂在那里而不是被丢弃时,就会产生异常。
我可以通过将 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() //..
}
// ...
}
我面临着同样的问题,但到目前为止,所有答案都没有帮助。 当
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.)
*/
composeView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
setContent {
HomeItemView()
}
}
这为我解决了问题。