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的情况:
- Gson反序列化空字符串返回null:当
json为空字符串时,gson.fromJson("", UserModel::class.java)不会抛出异常,而是直接返回null,这就导致try块返回了null,违反了方法的非空返回约定,触发NPE。 - 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
相关产品推荐
相关产品推荐

