[英]Jetpack Compose - preserve state of AndroidView on configuration change
很可能是一个新手问题,因为我对 Android 开发人员还很陌生 - 我在配置更改/导航时在 @Composable 中保留 AndroidView 的 state 时遇到了麻烦,因为调用了工厂块(如预期的那样)并且我的图表被重新实例化。
@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
是具有复杂图表的第 3 方组件,我想保留缩放/滚动 state。 我知道我可以使用 ViewModel 在 conf 中保留 UI state。 更改,但考虑到保存缩放/滚动 state 的复杂性,我想问是否还有其他更简单的方法来实现这一点?
我试图将整个图表实例移动到 viewModel,但是当它使用上下文时,我收到有关上下文 object 泄漏的警告。
任何帮助,将不胜感激!
如果要在配置更改时保留 AndroidView 的 state,请使用rememberSaveable
代替remember
。
虽然记住可以帮助您在重新组合时保留 state,但不会在配置更改时保留 state。 为此,您必须使用rememberSaveable 。 rememberSaveable自动保存任何可以保存在 Bundle 中的值。 对于其他值,您可以传入自定义保护程序 object。
如果 DataChart 是 Parcelable、Serializable 或其他可以存储在 bundle 中的数据类型:
val chart = rememberSaveable { DataChart(context) }
如果上述方式不起作用,则创建一个 MapSave 来保存数据、缩放/滚动状态...我假设 DataChart 具有您需要保存的 zoomIndex、scrollingIndex、values 属性:
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]
}
}
)
}
利用:
val chart = rememberSaveable(stateSaver = getDataChartSaver(context)){ DataChart(context) }
查看更多存储方式 state
我想说你的直觉是正确地将图表实例移动到视图 model,但是,正如你所指出的,当视图以外的对象需要上下文依赖关系时,它们可能会变得很麻烦。 对我来说,这变成了依赖注入的问题,其中依赖是上下文,或者更广泛地说,是整个数据图表。 我很想知道您如何获取您的视图 model,但我假设它依赖于 Android 视图 model 提供程序(通过ViewModelProvider.Factory
by viewModels()
或某种排序提供程序。
此问题的直接解决方案是将视图 model 转换为AndroidViewModel
的子类,该子类通过视图模型的构造函数提供对应用程序上下文的引用。 虽然它仍然是一种反模式并且应该谨慎使用,但 Android 团队已经认识到某些用例是有效的。 我个人不使用AndroidViewModel
,因为我认为它是一个粗略的解决方案,否则可以通过改进依赖图来解决。 但是,它是由官方文档批准的,这只是我的个人意见。 根据经验,我必须说它的使用使得测试视图 model 成为事后的噩梦。 如果你对依赖注入库感兴趣,我强烈推荐新的Hilt
实现,它最近在上个月发布了一个稳定的1.0.0
版本。
除此之外,我现在将为您的困境提供两种可能的解决方案:一种使用AndroidViewModel
,另一种不使用。 如果您的视图 model 已经具有上下文之外的其他依赖项,则AndroidViewModel
解决方案不会为您节省太多开销,因为您可能已经在某个时候实例化了ViewModelProvider.Factory
。 这些解决方案将考虑 Android Fragment
的 scope,但也可以很容易地在Activity
或DialogFragment
中实现,并对生命周期挂钩等进行一些调整。
使用AndroidViewModel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
class MyViewModel(application: Application) : AndroidViewModel(application) {
val dataChart: DataChart
init {
dataChart = DataChart(application.applicationContext)
}
}
片段可能在哪里
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View { ... }
}
没有AndroidViewModel
import androidx.lifecycle.ViewModel
class MyViewModel(args: Args) : ViewModel() {
data class Args(
val dataChart: DataChart
)
val dataChart: DataChart = args.dataChart
}
片段可能在哪里
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,
)
}
编辑(通过 Hilt 模块):
@Module
@InstallIn(...)
object DataChartModule {
@Provides
fun provideDataChart(
@ApplicationContext context: Context,
): DataChart = DataChart(context)
}
这是我所知道的最简单的方法。 这会保留 state 并且不会在我旋转手机时触发 WebView 的重新加载。 它应该适用于每个视图。
首先创建一个应用程序 class:
class MainApplication : Application() {
private var view: WebView? = null
override fun onCreate() {
super.onCreate()
LOG("started application")
}
fun loadView(context: Context): WebView {
if (view == null) {
view = WebView(context)
//init your view here
}
return view!!
}
}
然后将应用程序 class 添加到manifest.xml
<manifest>
...
<application
android:name=".MainApplication"
...
最后将其添加到可组合
AndroidView(modifier = Modifier.fillMaxSize(), factory = { context ->
val application = context.applicationContext as MainApplication
application.loadView(context = context)
})
而已。 我不确定这是否会导致 memory 泄漏,但我还没有遇到问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.