如何在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实现依赖注入,避免全局单例的耦合问题:
- 给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] ?: "" } }
- 修改
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 } } }
- 在Repository或ViewModel中通过
@Inject注入MainRemote即可使用。
关键注意点
- 绝对不要手动创建Application实例,必须使用系统初始化的全局实例
- Ktor的
loadTokens是挂起函数,可以直接调用DataStore的挂起方法或Flow的first()获取同步值 - 依赖注入是更优方案,能减少代码耦合,方便后续维护和测试
内容的提问来源于stack exchange,提问作者299 M Razak
相关产品推荐
相关产品推荐

