You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ViewModel外使用DataStore存储调用Token?遇初始化异常

问题解决:ViewModel外调用DataStore时的未初始化异常

问题背景

在ViewModel中使用DataStore存储Token功能正常,但在MainRemote.kt中调用DataStore时抛出kotlin.UninitializedPropertyAccessException,提示authDataStorePreferences未初始化。

错误原因

你在MainRemote中直接通过ConnectionReleaseApplication()创建了Application类的新对象,而Android系统中Application实例是由系统统一初始化的,手动new出来的对象不会触发onCreate()方法,导致authDataStorePreferences这个lateinit属性从未被赋值,访问时自然抛出未初始化异常。

解决方案

方案1:通过全局单例获取Application实例

修改Application类,添加静态实例供全局调用,确保拿到的是系统初始化的实例:

private const val AUTH_PREFERENCES_NAME = "auth_preferances_name"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = AUTH_PREFERENCES_NAME)

class ConnectionReleaseApplication : Application() {
    lateinit var authDataStorePreferences: AuthDataStorePreferences
    // 全局实例
    companion object {
        lateinit var instance: ConnectionReleaseApplication
            private set
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
        authDataStorePreferences = AuthDataStorePreferences(dataStore)
    }
}

修改MainRemote,通过全局实例获取authDataStorePreferences:

class MainRemote {
    // 获取系统初始化的Application实例中的DataStore对象
    private val authDataStorePreferences = ConnectionReleaseApplication.instance.authDataStorePreferences

    val client = HttpClient(CIO) {
        defaultRequest {
            url(host = BuildConfig.apiDomain, port = 3000)
            contentType(ContentType.Application.Json)
        }

        install(Auth){
            bearer {
                loadTokens {
                    // 从DataStore获取实际Token(getToken需返回Flow或挂起函数)
                    val token = authDataStorePreferences.getToken().first()
                    BearerTokens(token, "") // 根据实际业务处理refresh token
                }
            }
        }
        install(ContentNegotiation) {
            json(
                Json {
                    prettyPrint = true
                    isLenient = true
                    ignoreUnknownKeys = true
                }
            )
        }
        install(Logging) {
            logger = Logger.ANDROID
            level = LogLevel.BODY
        }
    }
}

方案2:使用依赖注入(推荐,解耦性更好)

用Hilt实现依赖注入,避免全局单例的耦合问题:

  1. 给Application添加Hilt注解:
@HiltAndroidApp
class ConnectionReleaseApplication : Application() {
}

// 修改AuthDataStorePreferences的构造函数,添加@Inject
class AuthDataStorePreferences @Inject constructor(private val dataStore: DataStore<Preferences>) {
    // 你的getToken、saveToken实现
    suspend fun getToken(): String {
        // 示例实现
        val preferences = dataStore.data.first()
        return preferences[TOKEN_KEY] ?: ""
    }
}
  1. 修改MainRemote,通过构造函数注入authDataStorePreferences:
class MainRemote @Inject constructor(private val authDataStorePreferences: AuthDataStorePreferences) {
    val client = HttpClient(CIO) {
        defaultRequest {
            url(host = BuildConfig.apiDomain, port = 3000)
            contentType(ContentType.Application.Json)
        }

        install(Auth){
            bearer {
                loadTokens {
                    val token = authDataStorePreferences.getToken()
                    BearerTokens(token, "")
                }
            }
        }
        install(ContentNegotiation) {
            json(
                Json {
                    prettyPrint = true
                    isLenient = true
                    ignoreUnknownKeys = true
                }
            )
        }
        install(Logging) {
            logger = Logger.ANDROID
            level = LogLevel.BODY
        }
    }
}
  1. 在Repository或ViewModel中通过@Inject注入MainRemote即可使用。

关键注意点

  • 绝对不要手动创建Application实例,必须使用系统初始化的全局实例
  • Ktor的loadTokens是挂起函数,可以直接调用DataStore的挂起方法或Flow的first()获取同步值
  • 依赖注入是更优方案,能减少代码耦合,方便后续维护和测试

内容的提问来源于stack exchange,提问作者299 M Razak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 03:07:08