diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a8aea8f..765e7bd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -49,8 +49,6 @@ dependencies { // jetpack navi implementation(libs.bundles.jetpack.navi) - // sharedPreference crypto - implementation(libs.security.crypto) // json implementation(libs.kotlinx.serialization.json) // google @@ -63,4 +61,8 @@ dependencies { kapt(libs.dagger.hilt.compiler) // 기초 androidx 라이브러리 ("core-ktx", "constraintlayout", "appcompat", "activity") implementation(libs.bundles.androidx) + + // preferences Datastore + implementation(libs.datastore.preferences) + implementation(libs.datastore.core) } diff --git a/app/src/main/java/com/sopt/now/di/SharedPreferenceModule.kt b/app/src/main/java/com/sopt/now/di/SharedPreferenceModule.kt index b9f0a30..cf926c2 100644 --- a/app/src/main/java/com/sopt/now/di/SharedPreferenceModule.kt +++ b/app/src/main/java/com/sopt/now/di/SharedPreferenceModule.kt @@ -1,9 +1,7 @@ package com.sopt.now.di import android.content.Context -import android.content.SharedPreferences -import androidx.security.crypto.EncryptedSharedPreferences -import androidx.security.crypto.MasterKey +import androidx.datastore.preferences.preferencesDataStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -14,16 +12,14 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object SharedPreferenceModule { + private const val PREFERENCE_NAME = "chanu_dataStore" + private val Context.datastore by preferencesDataStore( + name = PREFERENCE_NAME + ) + @Provides @Singleton fun providesLocalPreferences( @ApplicationContext context: Context - ): SharedPreferences = - EncryptedSharedPreferences.create( - context, - context.packageName, - MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(), - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) + ) = context.datastore } diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 9409943..c357006 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -32,8 +32,6 @@ android { dependencies { implementation(project(":domain")) - // sharedPreference - implementation(libs.core.ktx) // json implementation(libs.kotlinx.serialization.json) // dagger hilt @@ -41,4 +39,7 @@ dependencies { kapt(libs.dagger.hilt.compiler) implementation(libs.retrofit2) + + // preferences Datastore + implementation(libs.datastore.preferences) } \ No newline at end of file diff --git a/data/src/main/java/com/sopt/now/data/datasource/SharedPreferenceDataSource.kt b/data/src/main/java/com/sopt/now/data/datasource/SharedPreferenceDataSource.kt index 304efa1..fba9d79 100644 --- a/data/src/main/java/com/sopt/now/data/datasource/SharedPreferenceDataSource.kt +++ b/data/src/main/java/com/sopt/now/data/datasource/SharedPreferenceDataSource.kt @@ -4,8 +4,7 @@ import com.sopt.now.data.dto.UserDto interface SharedPreferenceDataSource { var checkLogin: Boolean - fun saveUserInfo(userDto: UserDto?) - fun getUserInfo(): UserDto - - fun clear() + suspend fun saveUserInfo(userDto: UserDto?) + suspend fun getUserInfo(): UserDto + suspend fun clear() } diff --git a/data/src/main/java/com/sopt/now/data/datasourceimpl/SharedPreferenceDataSourceImpl.kt b/data/src/main/java/com/sopt/now/data/datasourceimpl/SharedPreferenceDataSourceImpl.kt index 2431739..73118fa 100644 --- a/data/src/main/java/com/sopt/now/data/datasourceimpl/SharedPreferenceDataSourceImpl.kt +++ b/data/src/main/java/com/sopt/now/data/datasourceimpl/SharedPreferenceDataSourceImpl.kt @@ -1,35 +1,75 @@ package com.sopt.now.data.datasourceimpl -import android.content.SharedPreferences -import androidx.core.content.edit +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.emptyPreferences +import androidx.datastore.preferences.core.stringPreferencesKey import com.sopt.now.data.datasource.SharedPreferenceDataSource import com.sopt.now.data.dto.UserDto +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.runBlocking import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import java.io.IOException import javax.inject.Inject class SharedPreferenceDataSourceImpl @Inject constructor( - private val sharedPreferences: SharedPreferences + private val dataStore: DataStore ) : SharedPreferenceDataSource { override var checkLogin: Boolean - get() = sharedPreferences.getBoolean(CHECK_LOGIN, false) - set(value) = sharedPreferences.edit { putBoolean(CHECK_LOGIN, value) } + get() = runBlocking { + dataStore.data + .catch { exception -> + if (exception is IOException) { + emit(emptyPreferences()) + } else { + throw exception + } + } + .map { preference -> + preference[stringPreferencesKey(CHECK_LOGIN)]?.toBoolean() ?: false + }.first() + } + set(value) { + runBlocking { + dataStore.edit { preferences -> + preferences[stringPreferencesKey(CHECK_LOGIN)] = + value.toString() + } + } + } - override fun saveUserInfo(userDto: UserDto?) { + override suspend fun saveUserInfo(userDto: UserDto?) { val json = Json.encodeToString(userDto) - sharedPreferences.edit { putString(USER_INFO, json) } + dataStore.edit { preferences -> + preferences[stringPreferencesKey(USER_INFO)] = json + } } - override fun getUserInfo(): UserDto { - val json = sharedPreferences.getString(USER_INFO, "") - if (json.isNullOrBlank()) return UserDto("", "", "", "") + override suspend fun getUserInfo(): UserDto { + val json = dataStore.data + .catch { exception -> + if (exception is IOException) { + emit(emptyPreferences()) + } else { + throw exception + } + } + .map { preference -> + preference[stringPreferencesKey(USER_INFO)] ?: "" + }.first() + + if (json.isBlank()) return UserDto("", "", "", "") return Json.decodeFromString(json) } - override fun clear() { - sharedPreferences.edit { - remove(USER_INFO) - remove(CHECK_LOGIN) + override suspend fun clear() { + dataStore.edit { preferences -> + preferences.remove(stringPreferencesKey(USER_INFO)) + preferences.remove(stringPreferencesKey(CHECK_LOGIN)) } } diff --git a/data/src/main/java/com/sopt/now/data/repositoryimpl/UserInfoRepositoryImpl.kt b/data/src/main/java/com/sopt/now/data/repositoryimpl/UserInfoRepositoryImpl.kt index 42bef08..78eb05b 100644 --- a/data/src/main/java/com/sopt/now/data/repositoryimpl/UserInfoRepositoryImpl.kt +++ b/data/src/main/java/com/sopt/now/data/repositoryimpl/UserInfoRepositoryImpl.kt @@ -9,7 +9,7 @@ import javax.inject.Inject class UserInfoRepositoryImpl @Inject constructor( private val sharedPreferenceDataSource: SharedPreferenceDataSource ) : UserInfoRepository { - override fun saveUserInfo(user: UserEntity) { + override suspend fun saveUserInfo(user: UserEntity) { sharedPreferenceDataSource.saveUserInfo( userDto = UserDto( id = user.id, @@ -24,7 +24,7 @@ class UserInfoRepositoryImpl @Inject constructor( sharedPreferenceDataSource.checkLogin = isChecked } - override fun getUserInfo(): UserEntity { + override suspend fun getUserInfo(): UserEntity { return sharedPreferenceDataSource.getUserInfo().toUserEntity() } @@ -32,7 +32,7 @@ class UserInfoRepositoryImpl @Inject constructor( return sharedPreferenceDataSource.checkLogin } - override fun clear() { + override suspend fun clear() { sharedPreferenceDataSource.clear() } } diff --git a/domain/src/main/java/com/sopt/now/domain/repository/UserInfoRepository.kt b/domain/src/main/java/com/sopt/now/domain/repository/UserInfoRepository.kt index 87c1ae2..24e44c7 100644 --- a/domain/src/main/java/com/sopt/now/domain/repository/UserInfoRepository.kt +++ b/domain/src/main/java/com/sopt/now/domain/repository/UserInfoRepository.kt @@ -3,9 +3,9 @@ package com.sopt.now.domain.repository import com.sopt.now.domain.entity.UserEntity interface UserInfoRepository { - fun saveUserInfo(user: UserEntity) + suspend fun saveUserInfo(user: UserEntity) fun saveCheckLogin(isChecked: Boolean) - fun getUserInfo(): UserEntity + suspend fun getUserInfo(): UserEntity fun getCheckLogin(): Boolean - fun clear() + suspend fun clear() } diff --git a/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/ClearUserInfoUseCase.kt b/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/ClearUserInfoUseCase.kt index 9d26b90..1a9bde7 100644 --- a/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/ClearUserInfoUseCase.kt +++ b/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/ClearUserInfoUseCase.kt @@ -6,7 +6,7 @@ import javax.inject.Inject class ClearUserInfoUseCase @Inject constructor( private val userInfoRepository: UserInfoRepository ) { - operator fun invoke() { + suspend operator fun invoke() { userInfoRepository.clear() } } diff --git a/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/GetUserInfoUseCase.kt b/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/GetUserInfoUseCase.kt index bc55d8a..65fd322 100644 --- a/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/GetUserInfoUseCase.kt +++ b/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/GetUserInfoUseCase.kt @@ -7,7 +7,7 @@ import javax.inject.Inject class GetUserInfoUseCase @Inject constructor( private val userInfoRepository: UserInfoRepository ) { - operator fun invoke(): UserEntity { + suspend operator fun invoke(): UserEntity { return userInfoRepository.getUserInfo() } } diff --git a/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/SaveUserInfoUseCase.kt b/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/SaveUserInfoUseCase.kt index 515ec3d..4e81721 100644 --- a/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/SaveUserInfoUseCase.kt +++ b/domain/src/main/java/com/sopt/now/domain/usecase/sharedprefusecase/SaveUserInfoUseCase.kt @@ -7,7 +7,7 @@ import javax.inject.Inject class SaveUserInfoUseCase @Inject constructor( private val userInfoRepository: UserInfoRepository ) { - operator fun invoke(user: UserEntity) { + suspend operator fun invoke(user: UserEntity) { userInfoRepository.saveUserInfo(user) } } diff --git a/feature/src/main/java/com/sopt/now/feature/auth/SignUpViewModel.kt b/feature/src/main/java/com/sopt/now/feature/auth/SignUpViewModel.kt index e12c1b4..1cc79ab 100644 --- a/feature/src/main/java/com/sopt/now/feature/auth/SignUpViewModel.kt +++ b/feature/src/main/java/com/sopt/now/feature/auth/SignUpViewModel.kt @@ -41,7 +41,7 @@ class SignUpViewModel @Inject constructor( checkValidateUser() } - fun saveUserInfoSharedPreference(input: UserEntity) { + suspend fun saveUserInfoSharedPreference(input: UserEntity) { saveUserInfoUseCase.invoke(input) } diff --git a/feature/src/main/java/com/sopt/now/feature/mypage/MyPageFragment.kt b/feature/src/main/java/com/sopt/now/feature/mypage/MyPageFragment.kt index fdeb08f..f986c49 100644 --- a/feature/src/main/java/com/sopt/now/feature/mypage/MyPageFragment.kt +++ b/feature/src/main/java/com/sopt/now/feature/mypage/MyPageFragment.kt @@ -1,6 +1,7 @@ package com.sopt.now.feature.mypage import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import com.sopt.now.core.base.BindingFragment import com.sopt.now.core.util.fragment.toast import com.sopt.now.core.util.intent.navigateTo @@ -8,13 +9,16 @@ import com.sopt.now.feature.R import com.sopt.now.feature.auth.LoginActivity import com.sopt.now.feature.databinding.FragmentMyPageBinding import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch @AndroidEntryPoint class MyPageFragment : BindingFragment(R.layout.fragment_my_page) { private val viewModel by viewModels() override fun initView() { - initBtnClickListener() - initUpdateUserDataUI() + lifecycleScope.launch { + initBtnClickListener() + initUpdateUserDataUI() + } } private fun initBtnClickListener() { @@ -32,18 +36,20 @@ class MyPageFragment : BindingFragment(R.layout.fragment_ private fun initClearInfoBtnClickListener() { binding.tvMainClearInfo.setOnClickListener { - viewModel.clearSharedPrefUserInfo() - toast( - getString( - R.string.login_completed, - getString(R.string.main_clear_user_under_bar) + lifecycleScope.launch { + viewModel.clearSharedPrefUserInfo() + toast( + getString( + R.string.login_completed, + getString(R.string.main_clear_user_under_bar) + ) ) - ) - navigateTo(requireContext()) + navigateTo(requireContext()) + } } } - private fun initUpdateUserDataUI() = with(binding) { + private suspend fun initUpdateUserDataUI() = with(binding) { viewModel.getSharedPrefUserInfo().apply { tvMainIdData.text = id tvMainPwdData.text = password diff --git a/feature/src/main/java/com/sopt/now/feature/mypage/MyPageViewModel.kt b/feature/src/main/java/com/sopt/now/feature/mypage/MyPageViewModel.kt index 80d70cf..50ee918 100644 --- a/feature/src/main/java/com/sopt/now/feature/mypage/MyPageViewModel.kt +++ b/feature/src/main/java/com/sopt/now/feature/mypage/MyPageViewModel.kt @@ -15,10 +15,10 @@ class MyPageViewModel @Inject constructor( private val clearUserInfoUseCase: ClearUserInfoUseCase ) : ViewModel() { - fun getSharedPrefUserInfo(): UserEntity = getUserInfoUseCase.invoke() + suspend fun getSharedPrefUserInfo(): UserEntity = getUserInfoUseCase.invoke() fun updateCheckLoginState(isAutoLogin: Boolean) { saveCheckLoginUseCase.invoke(isAutoLogin) } - fun clearSharedPrefUserInfo() = clearUserInfoUseCase.invoke() + suspend fun clearSharedPrefUserInfo() = clearUserInfoUseCase.invoke() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5003b07..dc79f1e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ fragment-ktx = "1.6.2" security-crypto = "1.1.0-alpha06" jetpack-navi = "2.7.7" paging = "3.1.1" +datastore = "1.0.0-alpha07" # kotlin kotlin = "1.9.0" @@ -55,6 +56,8 @@ navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", paging = { module = "androidx.paging:paging-runtime-ktx", version.ref = "paging" } paging-domain = { module = "androidx.paging:paging-common", version.ref = "paging" } kotlin-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } +datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore"} +datastore-core = {group = "androidx.datastore", name = "datastore-core", version.ref = "datastore"} # Google material = { group = "com.google.android.material", name = "material", version.ref = "material" }