Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/225.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Jetpack Compose-在配置更改时保留AndroidView的状态_Android_Android Jetpack Compose - Fatal编程技术网

Jetpack Compose-在配置更改时保留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

最有可能是一个新手问题,因为我对Android开发相当陌生-我在@Composable on configuration change/navigation中保存AndroidView的状态时遇到了问题,因为调用了工厂块(如预期的那样),并且我的图表被重新实例化

@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()中是否是一种好方法。。我将研究自定义保护程序,在我的情况下,这可能是一个更好的选择。