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

mik*_*e_t 7 android android-jetpack-compose

很可能是一个新手问题,因为我对 Android 开发相当陌生 - 我在配置更改/导航时在 @Composable 中保留 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")
        })
}
Run Code Online (Sandbox Code Playgroud)

DataChart是一个带有复杂图表的第三方组件,我想保留缩放/滚动状态。我知道我可以使用 ViewModel 来保留 conf 中的 UI 状态。更改,但考虑到保存缩放/滚动状态的复杂性,我想问是否有其他更简单的方法来实现这一点?

我尝试将整个图表实例移动到 viewModel,但由于它使用上下文,我收到有关上下文对象泄漏的警告。

任何帮助,将不胜感激!

bug*_*ily 1

我想说,将图表实例移动到视图模型中的直觉是正确的,但是,正如您所指出的,当视图以外的对象需要上下文依赖关系时,它们可能会变得麻烦。对我来说,这变成了依赖注入的问题,其中依赖是上下文,或者从更广泛的意义上来说,是整个数据图表。我有兴趣知道如何获取视图模型,但我假设它依赖于 Android 视图模型提供程序(通过by viewModels()或某种方式ViewModelProvider.Factory)。

此问题的直接解决方案是将视图模型转换为 的子类,该子类AndroidViewModel通过视图模型的构造函数提供对应用程序上下文的引用。虽然它仍然是一种反模式并且应该谨慎使用,但 Android 团队已经认识到某些用例是有效的。我个人不使用它,AndroidViewModel因为我相信它是一个问题的粗略解决方案,否则可以通过依赖关系图的改进来解决。不过,这是官方文档认可的,这只是我个人的意见。根据经验,我必须说它的使用使得事后测试视图模型成为一场噩梦。如果您对依赖注入库感兴趣,我强烈推荐新的实现,该实现最近在上个月Hilt发布了稳定版本。1.0.0

除此之外,我现在将为您的困境提供两种可能的解决方案:一种使用AndroidViewModel,另一种不使用。如果您的视图模型已经具有上下文之外的其他依赖项,则该AndroidViewModel解决方案不会为您节省太多开销,因为您可能已经ViewModelProvider.Factory在某个时刻实例化了 a 。这些解决方案将考虑 Android 的范围,但可以通过对生命周期挂钩等进行一些调整来Fragment轻松实现。ActivityDialogFragment

AndroidViewModel

import android.app.Application
import androidx.lifecycle.AndroidViewModel

class MyViewModel(application: Application) : AndroidViewModel(application) {

    val dataChart: DataChart

    init {
        dataChart = DataChart(application.applicationContext)
    }
}
Run Code Online (Sandbox Code Playgroud)

片段可能在哪里

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View { ... }
}
Run Code Online (Sandbox Code Playgroud)

没有AndroidViewModel

import androidx.lifecycle.ViewModel

class MyViewModel(args: Args) : ViewModel() {

    data class Args(
        val dataChart: DataChart
    )

    val dataChart: DataChart = args.dataChart
}
Run Code Online (Sandbox Code Playgroud)

片段可能在哪里

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

        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

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,
    )
}
Run Code Online (Sandbox Code Playgroud)

编辑(通过 Hilt 模块):

@Module
@InstallIn(...)
object DataChartModule {

    @Provides
    fun provideDataChart(
        @ApplicationContext context: Context,
    ): DataChart = DataChart(context)
}
Run Code Online (Sandbox Code Playgroud)