如何等待 Preferences DataStore 中的数据加载完毕?
How do I wait until data from Preferences DataStore is loaded?
我正在尝试制作 LibGDX 动态壁纸。但我认为这与我目前遇到的问题无关。基本上,我将墙纸的设置存储在 Preferences DataStore 中。现在,如果您需要从 DataStore 检索数据,则需要检索 Kotlin Flow
并使用它来获取首选项。在 LibGDX 的 ApplicationListener
的 onCreate()
方法中,我需要创建一个 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) {
//...
}
}
我正在尝试制作 LibGDX 动态壁纸。但我认为这与我目前遇到的问题无关。基本上,我将墙纸的设置存储在 Preferences DataStore 中。现在,如果您需要从 DataStore 检索数据,则需要检索 Kotlin Flow
并使用它来获取首选项。在 LibGDX 的 ApplicationListener
的 onCreate()
方法中,我需要创建一个 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) {
//...
}
}