Android

DataStore 적용하기

그란. 2022. 7. 27. 17:27

 

-> PrefrencesDatastore 를 사용하는 방법을 알아보자.

( 단순히 유저ID등을 저장하는 용도라 ProtoDatastore까지 사용할 필요는 없음 )

 

 

기존 SharedPreference 를 사용하는 경우에는 + Rxjava

override fun fetchUserId(): Flowable<Optional<Long>> {
    return Flowable.create({ emitter ->
        val listener =
            SharedPreferences.OnSharedPreferenceChangeListener { sharedPref, key ->
                if (!emitter.isCancelled && key == KEY_CURRENT_USER_ID) {
                    emitter.onNext(sharedPref.getLong(KEY_CURRENT_USER_ID, 0L).let {
                        if (it == 0L) Optional.empty() else Optional.of(it)
                    })
                }
            }
        sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
        emitter.setCancellable {
            sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
        }
    }, BackpressureStrategy.LATEST)

이렇게 Flowable을 만들어서 OnSharedPreferenceChangeListener로 변경사항을 확인할수있었다. 

이 방식을 개선하여 코루틴 Flow로 변환 예정.

 

SharedPreferences 문제점 

1. 위의 표처럼 안전하지 못할 가능성

2. 위의 코드처럼 Flowable를 직접 만들어야한다는 점. (코드가 길어진다..)

3. null 값을 전달하지 못하기에 Optional로 랩핑해야 한다. (Rx 특징) 

 


샘플 기능 내용

1. 버튼을 누를때마다 랜덤 유저Id가 저장된다 

2. 유저Id가 변할때마다 Flow형태로 바라본다.

3. 멀티모듈, Hilt 적용

 

implementation("androidx.datastore:datastore-preferences:1.0.0")

 

LocalModule 

@Singleton
@Provides
fun provideContext(application: Application): Context {
    return application.applicationContext
}

@Singleton
@Provides
fun providePreferencesDataStore(@ApplicationContext context: Context): DataStore<Preferences> {
    return PreferenceDataStoreFactory.create(
        corruptionHandler = ReplaceFileCorruptionHandler {
            it.printStackTrace()
            emptyPreferences()
        },
        scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
        produceFile = { context.preferencesDataStoreFile(BuildConfig.LIBRARY_PACKAGE_NAME) }
    )
}

 

internal class SessionLocalDataSourceImpl @Inject constructor(
    private val dataStore: DataStore<Preferences>
) : SessionLocalDataSource {

    override suspend fun saveUserId(userId: Long) {
        dataStore.edit {
            it[USER_ID] = userId
        }
    }

    override fun fetchUserId(): Flow<Long?> = dataStore.data.map {
        it[USER_ID]
    }

    override suspend fun clearAll() {
        dataStore.edit {
            it.clear()
        }
    }

    companion object {
        val USER_ID = longPreferencesKey("userId")
        val COOKIE_SID = stringPreferencesKey("cookieSid")
    }
}

로그아웃 = (userId = null) 

 

internal class SessionRepositoryImpl @Inject constructor(
    private val local: SessionLocalDataSource
) : SessionRepository {
    override fun fetchUserId(): Flow<Long?> = local.fetchUserId()

    override suspend fun save(userId: Long) {
        local.saveUserId(userId)
    }

    override suspend fun clearAll() {
        local.clearAll()
    }
}

 

@Singleton
class FetchUserIdUseCase @Inject constructor(
    private val repository: SessionRepository
) {
    operator fun invoke(): Flow<Long?> = repository.fetchUserId()
}


@Singleton
class SetUserIdUseCase @Inject constructor(
    private val repository: SessionRepository
) {
    suspend operator fun invoke(userId: Long) = repository.save(userId)
}

 

//뷰모델
class ViewModel : ViewModel(){
     val userId = fetchUserIdUseCase.invoke()
      
      fun setUserId(userId: Long) = viewModelScope.launch {
        setUserIdUseCase.invoke(userId)
    }
}
//액티비티
lifeCycleScope.launch{
    viewModel.userId.collect {
        Log.e("ERROR", "user: $it")
    }
}

btnOk.setOnClickListner{
    viewModel.setUserId(Random.nextInt())
}

 

버튼을 누를때마다 유저Id가 변하게 되고 바로 collect에서 확인할 수 있다

'Android' 카테고리의 다른 글

transition animation 적용  (0) 2022.08.14
Room With Coroutine Flow  (0) 2022.07.30
코루틴 공부  (0) 2022.07.18
Facebook Login Trouble Shooting  (0) 2022.07.18
Fragment 공부  (0) 2022.06.24