如何等待 Preferences DataStore 中的数据加载完毕?

How do I wait until data from Preferences DataStore is loaded?

我正在尝试制作 LibGDX 动态壁纸。但我认为这与我目前遇到的问题无关。基本上,我将墙纸的设置存储在 Preferences DataStore 中。现在,如果您需要从 DataStore 检索数据,则需要检索 Kotlin Flow 并使用它来获取首选项。在 LibGDX 的 ApplicationListeneronCreate() 方法中,我需要创建一个 KtxScreen 对象。我决定将所有首选项传递到 Screen 的构造函数中。但是,由于 Flow 的数据只能在协同程序中获取,因此我无法使用用户首选项作为构造函数参数来构造 KtxScreen 对象,因为我需要等到 Flow发出值。我用一段非常非常肮脏的 while (userPreferences == null) Unit 代码解决了这个问题,该代码基本上会阻塞整个应用程序,直到发出首选项,这样我才能构建我的 KtxScreen。解决这个问题的好方法是什么?

在 libGDX 中阻塞渲染线程是可以接受的。这是一个独立于 Android 主线程的线程,因此它不会冻结 Android UI 或使您面临 ANR(应用程序未响应错误)的风险。它会在阻塞时冻结任何 game/wallpaper 渲染,但是当您仍在加载场景时这没关系。

所以,在create()render()中使用runBlocking等待第一批设置是可以接受的。由于 KtxScreen 不使用 create(),您可以直接在 属性 初始化时阻塞。您可以在 Flow 上调用 first() 以仅从中获取一项,这对于 KtxScreen class.

的初始设置来说很好

如果您使用来自 libKtx 的 ktx-async 库,您可以在渲染线程上收集您的流量,因此如果用户在您的壁纸 运行 时更新设置,它将安全地更新您的设置。

这是您如何实现这一目标的一个示例(我没有对其进行测试),但是有许多可接受的不同方式可以处理它。注意:这是假设您在渲染线程而不是 Android 主线程上实例化此屏幕。渲染线程用于在游戏 class 上调用 create(),这是实例化第一个屏幕的典型位置。

class MyScreen(
    val settingsFlow: Flow<UserPreferences>
): KtxScreen {

    var prefs: UserPreferences = runBlocking { settingsFlow.first() }

    init {
        settingsFlow
            .drop(1) // the first one should be the value we already got above
                     // unless the user changed them impossibly fast
            .onEach { prefs = it }
            .launchIn(KtxAsync)
    }

    //...
}

如果您想在壁纸中显示某种初始加载图像,您可以像这样在 render 函数中使用 if/else。但我认为这不是必需的,因为加载偏好可能需要不到半秒的时间。

class MyScreen(
    val settingsFlow: Flow<UserPreferences>
): KtxScreen {

    var _prefs: UserPreferences? = null

    init {
        settingsFlow
            .onEach { _prefs = it }
            .launchIn(KtxAsync)
    }

    override fun render() {
        val prefs = _prefs // for the sake of smart-casting
        if (prefs == null) {
            renderLoadingImage()
        } else {
            renderScene(prefs)
        }
    }

    //...

    private fun renderScene(prefs: UserPreferences) {
        //...
    }
}