Jetpack Compose-在配置更改时保留AndroidView的状态
最有可能是一个新手问题,因为我对Android开发相当陌生-我在@Composable on configuration change/navigation中保存AndroidView的状态时遇到了问题,因为调用了工厂块(如预期的那样),并且我的图表被重新实例化Jetpack Compose-在配置更改时保留AndroidView的状态,android,android-jetpack-compose,Android,Android Jetpack Compose,最有可能是一个新手问题,因为我对Android开发相当陌生-我在@Composable on configuration change/navigation中保存AndroidView的状态时遇到了问题,因为调用了工厂块(如预期的那样),并且我的图表被重新实例化 @Composable fun ChartView(viewModel:ViewModel, modifier:Modifier){ val context = LocalContext.current val char
@Composable
fun ChartView(viewModel:ViewModel, modifier:Modifier){
val context = LocalContext.current
val chart = remember { DataChart(context) }
AndroidView(
modifier = modifier,
factory = { context ->
Log.d("DEBUGLOG", "chart init")
chart
},
update = { chart ->
Log.d("DEBUGLOG", "chart update")
})
}
DataChart
是具有复杂图表的第三方组件,我希望保留缩放/滚动状态。我知道我可以使用ViewModel跨配置更改保存UI状态,但是考虑到保存缩放/滚动状态的复杂性,我想问一下是否有其他更简单的方法来实现这一点
我试图将整个图表实例移动到viewModel,但由于它使用了上下文,我得到了一个关于上下文对象泄漏的警告
任何帮助都将不胜感激 我想说,您将图表实例移动到视图模型中的直觉是正确的,但是,正如您所指出的,当视图以外的对象需要上下文依赖时,上下文依赖可能会成为一个麻烦。对我来说,这变成了依赖项注入的问题,依赖项是上下文,或者在更广泛的意义上是整个数据图表。我很想知道您是如何获取视图模型的,但我假设它依赖于Android视图模型提供程序(通过
by viewModels()
或某种ViewModelProvider.Factory
)
这个问题的一个直接解决方案是将视图模型转换为的子类,该子类通过视图模型的构造函数提供对应用程序上下文的引用。虽然它仍然是一种反模式,应该谨慎使用,但Android团队已经认识到某些用例是有效的。我个人不使用AndroidViewModel
,因为我认为这是一个粗糙的问题解决方案,否则可以通过对依赖关系图的细化来解决。然而,这是官方文件批准的,这只是我个人的意见。根据经验,我必须说它的使用使得测试视图模型成为事后的噩梦。如果您对依赖项注入库感兴趣,我强烈推荐最近刚刚在上个月发布了稳定的1.0.0
版本的新实现
除此之外,我现在将为您的困境提供两种可能的解决方案:一种是利用AndroidViewModel
,另一种是不利用。如果视图模型已经具有上下文之外的其他依赖项,AndroidViewModel
解决方案不会为您节省太多开销,因为您可能已经在某个时候实例化了ViewModelProvider.Factory
。这些解决方案将考虑Android片段的范围,但可以很容易地在活动
或对话框片段
中实现,并对生命周期挂钩等进行一些调整
带有AndroidViewModel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
class MyViewModel(application: Application) : AndroidViewModel(application) {
val dataChart: DataChart
init {
dataChart = DataChart(application.applicationContext)
}
}
import androidx.lifecycle.ViewModel
class MyViewModel(args: Args) : ViewModel() {
data class Args(
val dataChart: DataChart
)
val dataChart: DataChart = args.dataChart
}
碎片可能在哪里
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View { ... }
}
class MyFragment : Fragment() {
private lateinit var viewModel: MyViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val applicationContext: Context = requireContext().applicationContext
val dataChart = DataChart(applicationContext)
val viewModel: MyViewModel by viewModels {
ArgsViewModelFactory(
args = MyViewModel.Args(
dataChart = dataChart,
),
argsClass = MyViewModel.Args::class.java,
)
}
this.viewModel = viewModel
...
}
}
不带AndroidViewModel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
class MyViewModel(application: Application) : AndroidViewModel(application) {
val dataChart: DataChart
init {
dataChart = DataChart(application.applicationContext)
}
}
import androidx.lifecycle.ViewModel
class MyViewModel(args: Args) : ViewModel() {
data class Args(
val dataChart: DataChart
)
val dataChart: DataChart = args.dataChart
}
碎片可能在哪里
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View { ... }
}
class MyFragment : Fragment() {
private lateinit var viewModel: MyViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val applicationContext: Context = requireContext().applicationContext
val dataChart = DataChart(applicationContext)
val viewModel: MyViewModel by viewModels {
ArgsViewModelFactory(
args = MyViewModel.Args(
dataChart = dataChart,
),
argsClass = MyViewModel.Args::class.java,
)
}
this.viewModel = viewModel
...
}
}
其中,ArgsViewModelFactory
是我自己创建的,如下所示
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class ArgsViewModelFactory<T>(
private val args: T,
private val argsClass: Class<T>,
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = modelClass.getConstructor(
argsClass,
).newInstance(
args,
)
}
如果要在配置更改时保留AndroidView的状态,请使用rememberSaveable
而不是rememberSaveable
记住有助于在整个重组过程中保持状态,而
不会在配置更改期间保留。为此,您必须使用
记忆可移动RemembersAvable自动保存任何
可以保存在一个包中。对于其他值,可以传入自定义值
保存对象
如果DataChart是可打包、可序列化或其他可存储在捆绑包中的数据类型:
val chart = rememberSaveable { DataChart(context) }
如果上述方法不起作用,则创建MapSave以保存数据、缩放/滚动状态。。。我假设DataChart具有zoomIndex、scrollingIndex和您需要保存的值属性:
fun getDataChartSaver(context: Context) = run {
val zoomKey = "ZoomState"
val scrollingKey = "ScrollingState"
val dataKey = "DataState"
mapSaver(
save = { mapOf(zoomKey to it.zoomIndex, scrollingKey to it.scrollingIndex, dataKey to it.values) },
restore = {
DataChart(context).apply{
zoomIndex = it[zoomKey]
scrollingIndex = it[scrollingKey]
values = it[dataKey]
}
}
)
}
使用:
查看更多信息我刚刚更新了答案,请检查并给我反馈:)感谢您的广泛回复和宝贵见解。我正在使用剑柄(匕首)进行DI和无碎片(带有嵌套导航的纯活动组合应用程序),我将尝试应用您建议的一些概念。哦,酷!如果您使用的是Hilt,请签出@ApplicationContext注释文档。您应该能够直接注入DataChart的实例,因为Hilt提供了一种引用应用程序上下文的简单方法。我将编辑我的答案,以表明它可能是什么样子。非常感谢。我通常担心这种方法,因为通常不建议在viewmodel中注入上下文,因为它可能导致内存泄漏(android studio本身也指出了这一点),它引用的是应用程序上下文,它是一个生命周期与应用程序相同的单例。这与传递活动或某种形式的活动上下文不同。据我所知,这永远不会导致内存泄漏,这就是为什么Android团队自己推出了AndroidViewModel
。在功能上,使用Hilt的@ApplicationContext注释与使用AndroidViewModel
是相同的。虽然检查泄漏是一个好主意,但我相信这是一个预期的用例,并且我在过去使用了这种方法将AppUpdateManager
注入到片段中而没有泄漏。感谢您的建议。DataChart可以根据数据(支持多个数据集等)增长得很大,因此我不确定通过RemembersAvable将其保存在Bundle()中是否是一种好方法。。我将研究自定义保护程序,在我的情况下,这可能是一个更好的选择。