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

Kotlin使用DataStore和Gson读数据时抛出指向方法名的NPE

问题

使用Kotlin和Android Jetpack Compose开发移动端应用,通过Repository模式用DataStore存储从接口获取的UserModel数据。保存数据后读取正常,但未保存过数据时读取会抛出NullPointerException,错误指向readUserCache方法定义行而非具体代码行。


相关代码

DataStoreRepository.kt

interface DataStoreRepository {
    fun saveUserCache(user: UserModel)
    fun readUserCache(): UserModel
}

DataStoreRepositoryImpl.kt

const val DATASTORE_NAME = "settings"
const val USER_CACHE = "USER_CACHE"

private val Context.dataStore : DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)

class DataStoreRepositoryImpl @Inject constructor(
    private val context : Context,
    private val gson: Gson
): DataStoreRepository {

    override fun saveUserCache(user: UserModel) = runBlocking {
        val json = gson.toJson(user)
        saveString(USER_CACHE, json)
    }

    override fun readUserCache(): UserModel = runBlocking {
        val json = readString(USER_CACHE) ?: "" // Android Studio显示json为String -> 正常

        // 曾尝试以下代码但无效,奇怪的是json显示为String?
        //val json = try {
        //    readString(USER_CACHE)
        //} catch (_: Exception) {
        //    ""
        //}

        return@runBlocking try {
            gson.fromJson(json, UserModel::class.java)
        } catch (_: Exception) {
            UserModel()
        }
    }
    
    private suspend fun saveString(key: String, value: String) {
        val prefsKey = stringPreferencesKey(key)
        context.dataStore.edit {
            it[prefsKey] = value
        }
    }
    
    private suspend fun readString(key: String): String? {
        return try {
            val prefsKey = stringPreferencesKey(key)
            val prefs = context.dataStore.data.first()
            prefs[prefsKey]
        } catch (e: Exception) {
            Timber.e(e)
            return null
        }
    }
}

AppModule.kt

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    // GSON
    @Provides
    @Singleton
    fun provideGson(): Gson {
        return GsonBuilder()
            .registerTypeAdapter(LocalDate::class.java, LocalDateAdapter())
            .registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeAdapter())
            .create()
    }

    @Provides
    @Singleton
    fun provideDatstoreRepository(
        @ApplicationContext context: Context,
        gson: Gson
    ): DataStoreRepository {
        return DataStoreRepositoryImpl(context, gson)
    }
}

错误日志

java.lang.NullPointerException: override fun readUserCac…erModel()
    }
} must not be null
at REMOVED_MY_PACKAGE_NAME.data.repository.DataStoreRepositoryImpl.readUserCache(DataStoreRepositoryImpl.kt:170)
at REMOVED_MY_PACKAGE_NAME.presentation.components.PwNavigationViewModel.<init>(PwNavigation.kt:132)
at REMOVED_MY_PACKAGE_NAME.DaggerPwApplication_HiltComponents_SingletonC$ViewModelCImpl$SwitchingProvider.get(DaggerPwApplication_HiltComponents_SingletonC.java:712)
at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory$1.create(HiltViewModelFactory.java:102)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.kt:90)
at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:114)

问题原因与修复方案

原因分析

错误指向方法定义行,本质是readUserCache承诺返回非空的UserModel,但实际执行中出现了返回null的情况:

  1. Gson反序列化空字符串返回null:当json为空字符串时,gson.fromJson("", UserModel::class.java)不会抛出异常,而是直接返回null,这就导致try块返回了null,违反了方法的非空返回约定,触发NPE。
  2. try-catch未覆盖该场景:因为Gson反序列化空字符串不抛异常,所以不会走到catch块的UserModel()逻辑。

修复步骤

方案一:显式处理反序列化的null结果

修改readUserCache中的try块,确保返回结果非空:

return@runBlocking try {
    gson.fromJson(json, UserModel::class.java) ?: UserModel()
} catch (_: Exception) {
    UserModel()
}

方案二:提前跳过空json的反序列化

在获取json后直接判断是否为空,为空则直接返回空实例:

override fun readUserCache(): UserModel = runBlocking {
    val json = readString(USER_CACHE) ?: ""
    if (json.isEmpty()) {
        return@runBlocking UserModel()
    }
    return@runBlocking try {
        gson.fromJson(json, UserModel::class.java) ?: UserModel()
    } catch (_: Exception) {
        UserModel()
    }
}

额外优化建议

  • 避免在普通函数中使用runBlocking:readUserCache和saveUserCache是普通函数,内部用runBlocking会阻塞线程,建议改为挂起函数:
    // 接口修改
    interface DataStoreRepository {
        suspend fun saveUserCache(user: UserModel)
        suspend fun readUserCache(): UserModel
    }
    
    // 实现类修改
    override suspend fun saveUserCache(user: UserModel) {
        val json = gson.toJson(user)
        saveString(USER_CACHE, json)
    }
    
    override suspend fun readUserCache(): UserModel {
        val json = readString(USER_CACHE) ?: ""
        return if (json.isEmpty()) {
            UserModel()
        } else {
            try {
                gson.fromJson(json, UserModel::class.java) ?: UserModel()
            } catch (_: Exception) {
                UserModel()
            }
        }
    }
    

内容的提问来源于stack exchange,提问作者PiotrW

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 13:52:37