繁体   English   中英

Jetpack Compose - 在配置更改时保留 AndroidView 的 state

[英]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,但也可以很容易地在ActivityDialogFragment中实现,并对生命周期挂钩等进行一些调整。

使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM