Skip to content

Conversation

@HI-JIN2
Copy link
Member

@HI-JIN2 HI-JIN2 commented Oct 16, 2025

Summary

SharedPreference에서 datastore로 마이그레이션 합니다

Describe your changes

  • data 레이어 안에 local과 remote를 나누어 패키징 했습니다. (이때문에 files chagned가 좀 많습니다..)

스크린샷 2025-10-16 오후 7 25 55.

  • 이전에 SharedPreference에서 context를 요구해서 usecase에서 불필요하게 넘겨줬던 부분도 수정했습니다.
  • 토큰은 따로 EncryptedSharedPreferences를 사용했습니다.

Issue

@HI-JIN2 HI-JIN2 self-assigned this Oct 16, 2025
@HI-JIN2 HI-JIN2 force-pushed the refactor/datastore branch from 09f8863 to 2f07816 Compare October 16, 2025 10:17
@HI-JIN2 HI-JIN2 requested a review from kangyuri1114 October 16, 2025 10:28
@HI-JIN2 HI-JIN2 marked this pull request as ready for review October 16, 2025 10:28
@HI-JIN2 HI-JIN2 requested review from PeraSite and Copilot October 16, 2025 10:29
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

SharedPreferences → DataStore/EncryptedSharedPreferences migration, plus repository/package restructuring under data.local and data.remote. Also removes context plumbing from use cases and aligns DTO/service package names.

  • Introduce AccountDataStore, SettingDataStore, WidgetDataStore, and TokenStore
  • Migrate repositories/services/DTOs to data.remote.* and adjust DI
  • Replace SharedPreferences (MySharedPreferences) usage across view/presentation/domain with DataStore/TokenStore

Reviewed Changes

Copilot reviewed 108 out of 108 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
gradle/libs.versions.toml Adds security-crypto version and library alias; groups datastore deps
app/src/main/java/com/eatssu/android/presentation/mypage/userinfo/UserInfoViewModel.kt Handles nullable college/department and provides defaults
app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewViewModel.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewAdapter.kt Removes MySharedPreferences; clears nickname display
app/src/main/java/com/eatssu/android/presentation/mypage/MyPageViewModel.kt Switches PreferencesRepository to SettingDataStore
app/src/main/java/com/eatssu/android/presentation/map/MapViewModel.kt Uses College/Department models in MapState
app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt Removes MySharedPreferences, adjusts permission/toast formatting, logs with DataStore-backed IDs
app/src/main/java/com/eatssu/android/presentation/login/LoginViewModel.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/presentation/common/VersionViewModelFactory.kt Updates FirebaseRemoteConfigRepository package
app/src/main/java/com/eatssu/android/presentation/common/VersionViewModel.kt Updates FirebaseRemoteConfigRepository package
app/src/main/java/com/eatssu/android/presentation/common/MyReviewBottomSheetFragment.kt Removes App.appContext; changes negative-button toast string
app/src/main/java/com/eatssu/android/presentation/cafeteria/review/write/menu/VariableMenuViewModel.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/presentation/cafeteria/review/write/ReviewWriteViewModel.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/presentation/cafeteria/review/write/ReviewWriteRateActivity.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/presentation/cafeteria/review/report/ReportViewModel.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/presentation/cafeteria/review/modify/ModifyViewModel.kt Updates DTO import paths; replaces string resources with literals
app/src/main/java/com/eatssu/android/presentation/cafeteria/review/modify/ModifyReviewActivity.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewViewModel.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/presentation/cafeteria/menu/MenuViewModel.kt Updates DTO/service import paths
app/src/main/java/com/eatssu/android/presentation/cafeteria/menu/MenuFragment.kt Updates DTO mapping import paths
app/src/main/java/com/eatssu/android/presentation/cafeteria/info/InfoViewModel.kt Updates FirebaseRemoteConfigRepository package
app/src/main/java/com/eatssu/android/presentation/base/BaseActivity.kt Updates FirebaseRemoteConfigRepository package
app/src/main/java/com/eatssu/android/presentation/MainViewModel.kt Makes department nullable; adjusts Timber usage
app/src/main/java/com/eatssu/android/domain/usecase/widget/SaveRestaurantByFileKeyUseCase.kt Switches to WidgetDataStore
app/src/main/java/com/eatssu/android/domain/usecase/widget/LoadRestaurantByFileKeyUseCase.kt Switches to WidgetDataStore
app/src/main/java/com/eatssu/android/domain/usecase/user/ValidateUserNameUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/user/SetUserNicknameUseCase.kt Updates DTO import path; writes back to AccountDataStore
app/src/main/java/com/eatssu/android/domain/usecase/user/SetUserEmailUseCase.kt Writes email to AccountDataStore
app/src/main/java/com/eatssu/android/domain/usecase/user/SetUserCollegeDepartmentUseCase.kt Writes college/department to AccountDataStore
app/src/main/java/com/eatssu/android/domain/usecase/user/GetUserNickNameUseCase.kt Reads from AccountDataStore first, falls back to remote
app/src/main/java/com/eatssu/android/domain/usecase/user/GetUserEmailUseCase.kt Removes SharedPreferences-based use case
app/src/main/java/com/eatssu/android/domain/usecase/user/GetUserCollegeDepartmentUseCase.kt Reads user info from AccountDataStore
app/src/main/java/com/eatssu/android/domain/usecase/review/WriteReviewUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/review/PostReportUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/review/ModifyReviewUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/review/GetMyReviewsUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/review/GetMenuReviewListUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/review/GetMenuReviewInfoUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/review/GetMealReviewListUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/review/GetMealReviewInfoUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/review/GetImageUrlUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/review/DeleteReviewUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/menu/GetMenuNameListOfMealUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/auth/SetRefreshTokenUseCase.kt Uses TokenStore (non-suspend)
app/src/main/java/com/eatssu/android/domain/usecase/auth/SetAccessTokenUseCase.kt Uses TokenStore (non-suspend)
app/src/main/java/com/eatssu/android/domain/usecase/auth/LogoutUseCase.kt Clears AccountDataStore/TokenStore/SettingDataStore
app/src/main/java/com/eatssu/android/domain/usecase/auth/LoginUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/auth/GetRefreshTokenUseCase.kt Reads from TokenStore (non-suspend)
app/src/main/java/com/eatssu/android/domain/usecase/auth/GetIsAccessTokenValidUseCase.kt Updates DTO import paths
app/src/main/java/com/eatssu/android/domain/usecase/auth/GetAccessTokenUseCase.kt Reads from TokenStore (non-suspend)
app/src/main/java/com/eatssu/android/domain/usecase/alarm/SetDailyNotificationStatusUseCase.kt Uses SettingDataStore
app/src/main/java/com/eatssu/android/domain/usecase/alarm/GetDailyNotificationStatusUseCase.kt Uses SettingDataStore
app/src/main/java/com/eatssu/android/domain/usecase/alarm/AlarmUsecase.kt Injects @ApplicationContext; retains AlarmUseCase
app/src/main/java/com/eatssu/android/domain/repository/UserRepository.kt DTO imports moved to data.remote.dto
app/src/main/java/com/eatssu/android/domain/repository/ReviewRepository.kt DTO imports moved to data.remote.dto
app/src/main/java/com/eatssu/android/domain/repository/ReportRepository.kt DTO imports moved to data.remote.dto
app/src/main/java/com/eatssu/android/domain/repository/OauthRepository.kt DTO imports moved to data.remote.dto
app/src/main/java/com/eatssu/android/domain/repository/MealRepository.kt DTO imports moved to data.remote.dto
app/src/main/java/com/eatssu/android/domain/model/UserInfo.kt Makes college/department nullable
app/src/main/java/com/eatssu/android/di/ServiceModule.kt Switches service packages to data.remote.service
app/src/main/java/com/eatssu/android/di/SecurePrefsModule.kt Provides EncryptedSharedPreferences via MasterKey
app/src/main/java/com/eatssu/android/di/DataModule.kt Binds repos from data.remote.repository
app/src/main/java/com/eatssu/android/di/AppModule.kt Drops legacy prefs providers; provides FirebaseRemoteConfigRepository
app/src/main/java/com/eatssu/android/data/remote/service/UserService.kt Moves service to data.remote.service and DTO imports
app/src/main/java/com/eatssu/android/data/remote/service/ReviewService.kt Moves service to data.remote.service and DTO imports
app/src/main/java/com/eatssu/android/data/remote/service/ReportService.kt Moves service to data.remote.service and DTO imports
app/src/main/java/com/eatssu/android/data/remote/service/PartnershipService.kt Moves service to data.remote.service and DTO imports
app/src/main/java/com/eatssu/android/data/remote/service/OauthService.kt Moves service to data.remote.service and DTO imports
app/src/main/java/com/eatssu/android/data/remote/service/MenuService.kt Moves service to data.remote.service and DTO imports
app/src/main/java/com/eatssu/android/data/remote/service/MealService.kt Moves service to data.remote.service and DTO imports
app/src/main/java/com/eatssu/android/data/remote/repository/UserRepositoryImpl.kt Moves to data.remote.repository
app/src/main/java/com/eatssu/android/data/remote/repository/ReviewRepositoryImpl.kt Moves to data.remote.repository
app/src/main/java/com/eatssu/android/data/remote/repository/ReportRepositoryImpl.kt Moves to data.remote.repository
app/src/main/java/com/eatssu/android/data/remote/repository/PartnershipRepositoryImpl.kt Moves to data.remote.repository
app/src/main/java/com/eatssu/android/data/remote/repository/OauthRepositoryImpl.kt Moves to data.remote.repository
app/src/main/java/com/eatssu/android/data/remote/repository/MealRepositoryImpl.kt Moves to data.remote.repository
app/src/main/java/com/eatssu/android/data/remote/repository/FirebaseRemoteConfigRepository.kt Moves to data.remote.repository
app/src/main/java/com/eatssu/android/data/remote/dto/response/*.kt Moves DTOs to data.remote.dto.response
app/src/main/java/com/eatssu/android/data/remote/dto/request/*.kt Moves DTOs to data.remote.dto.request
app/src/main/java/com/eatssu/android/data/local/WidgetDataStore.kt Introduces DataStore for widget prefs (replaces WidgetPreferencesRepository)
app/src/main/java/com/eatssu/android/data/local/TokenStore.kt Introduces EncryptedSharedPreferences-backed token store
app/src/main/java/com/eatssu/android/data/local/SettingDataStore.kt Introduces DataStore for app settings (alarm)
app/src/main/java/com/eatssu/android/data/local/AccountDataStore.kt Introduces DataStore for account (email/name/college/department)
app/src/main/java/com/eatssu/android/data/MySharedPreferences.kt Removes legacy SharedPreferences helper
app/src/main/java/com/eatssu/android/App.kt Removes global appContext and related usage
app/build.gradle.kts Adds androidx.security:security-crypto dependency

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


fun bind(data: Review) {
binding.tvWriterNickname.text = MySharedPreferences.getUserName(binding.root.context)
binding.tvWriterNickname.text = ""
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This renders the writer nickname as an empty string for every item. Replace this with the actual user nickname (e.g., pass the nickname into the adapter or bind from a ViewModel/AccountDataStore), and fall back to a sensible default when unavailable.

Suggested change
binding.tvWriterNickname.text = ""
binding.tvWriterNickname.text = if (!data.writerNickname.isNullOrBlank()) data.writerNickname else "익명"

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분 반영되면 좋겟네요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

옹 전에 유저라고 디폴트있는거 별로라고 했던 것 같아서 공백으로 한건데 유저로 바꿀까염?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 그런가요? 기획에서 결정된 부분이면 안바꿔주셔도 될 것 같아요! 몰랐네요 ㅠ

Comment on lines 197 to 199
val mapState = uiState as UiState.Success<MapState>
val departmentId = mapState.data?.currentDepartment?.departmentId?.toLong()
val collegeId = mapState.data?.currentCollege?.collegeId?.toLong()
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsafe cast to UiState.Success can throw at runtime and the local mapState variable also shadows the outer mapState. Derive IDs from the existing mapState or use a safe cast/guard, e.g. use the already-derived mapState: val departmentId = mapState.currentDepartment?.departmentId?.toLong(); val collegeId = mapState.currentCollege?.collegeId?.toLong().

Suggested change
val mapState = uiState as UiState.Success<MapState>
val departmentId = mapState.data?.currentDepartment?.departmentId?.toLong()
val collegeId = mapState.data?.currentCollege?.collegeId?.toLong()
val successState = uiState as? UiState.Success<MapState>
val departmentId = successState?.data?.currentDepartment?.departmentId?.toLong()
val collegeId = successState?.data?.currentCollege?.collegeId?.toLong()

Copilot uses AI. Check for mistakes.
setMessage(R.string.delete_description)
setNegativeButton("취소") { _, _ ->
activity?.showToast(App.appContext.getString(R.string.delete_undo))
activity?.showToast("리뷰 삭제를 취소하시겠습니까?")
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid hardcoded user-facing strings; use string resources for localization and consistency. Consider reverting to the existing resource (e.g., getString(R.string.delete_undo)) or adding a new resource if the wording changes.

Suggested change
activity?.showToast("리뷰 삭제를 취소하시겠습니까?")
activity?.showToast(getString(R.string.review_delete_cancel_message))

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

viewmodel에서 string 처리하는 부분도 다음에 같이 리팩토링 해보면 좋을 거같네요
전부터 계속 고민됐던 부분..
activity에서 문자열을 가져오는 방식이나 문자열을 가져오는 인터페이스를 만들거나 하면 될 거 같네요!

Copy link
Member Author

@HI-JIN2 HI-JIN2 Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

흐음..... xml이 아닌 kt 파일로 문자열 util 만드는건 어떨까요..?

error = false,
isDone = true,
toastMessage = App.appContext.getString(R.string.modify_not)
toastMessage = "리뷰 수정이 실패하였습니다."
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid hardcoded strings in ViewModels; use string resources so UI text is centralized and localizable.

Copilot uses AI. Check for mistakes.
error = false,
isDone = true,
toastMessage = App.appContext.getString(R.string.modify_done)
toastMessage = "리뷰가 수정되었습니다."
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid hardcoded strings in ViewModels; prefer getString(R.string.modify_done) or a UI-layer mapping so messages are localized and maintainable.

Suggested change
toastMessage = "리뷰가 수정되었습니다."
toastMessage = "modify_done" // UI should map this key to a localized string resource

Copilot uses AI. Check for mistakes.
import javax.inject.Inject
import javax.inject.Singleton

private val Context.widgetPrefsDataStore: DataStore<Preferences> by preferencesDataStore(name = "widget_prefs")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위젯용 data store을 건들이지는 않아서 기존 바탕화면에 적재한 위젯에 영향은 없을듯합니다!

Comment on lines -24 to 25
class App : Application(), Configuration.Provider {
companion object{
lateinit var appContext: Context //todo 이거 빼기
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

드디어 이걸 없앨 수 있다니!!

val nickname = accountDataStore.name.first()
val college = accountDataStore.college.first()
val department = accountDataStore.department.first()
return UserInfo(nickname, department, college)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserInfo의 colleage와 department가 nullable인 상황을 쭉 유지해서 UserInfo 사용처가 null에 대응하는 것보다는 여기 UseCase에서 기본값으로 대체한 뒤, non-nullable UserInfo 를 반환하는 것이 더 깔끔하지 않을까요!?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 이 의견에 동의합니다~-!!!

const val KEY_REFRESH = "REFRESH_TOKEN"
}

var accessToken: String
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그인, 세션 관련 처리할 때 token이 null인지가 아니라 empty인지를 확인하는 부분 볼 때마다 ""인지 null인지 사람이 개발하다 헷갈려서 미래에 문제가 생길 수도 있겠다 생각했는데, 이번 기회에 바꾸면 좋을 것 같아요. 어떻게 생각하시나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullorempty를 검사하면 되지 않을까요??

제훈님 말씀은 ""을 저장하지 않게 하자 그리고 null 아니면 토큰만 반환하도록 하자인가용?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullOrEmpty를 검사하면 되긴 하지만 ""라는 빈 공백 값이 가능한 상황은 최대한 줄이면 좋다고 생각했어요! 말씀해주신대로 Token 아니면 null을 반환해 ""를 쓰지 말자가 맞습니다!

Copy link
Member

@kangyuri1114 kangyuri1114 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

몇개의 유즈케이스에서 domain → data 로 향하고 있는 점들이 고민되긴 하네요..! 이번 PR은 DataStore 마이그레이션에 초점을 두고 있어서, 구조 개선은 차차 리팩토링하면서 정리해도 될 것 같습니다
Context 를 외부에서 주입하던 방식이 모두 리팩된거같아서 너무 좋네요!

제훈님 리뷰에 저도 동의한다는 의견 달았는데요, 그 부분만 합의 or 수정 되면 머지해도 될 것 같네요!

Comment on lines +18 to +27
fun provideSecurePrefs(@ApplicationContext context: Context): SharedPreferences {
val masterKey = androidx.security.crypto.MasterKey.Builder(context)
.setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme.AES256_GCM)
.build()
return androidx.security.crypto.EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋네욤 👍

val nickname = accountDataStore.name.first()
val college = accountDataStore.college.first()
val department = accountDataStore.department.first()
return UserInfo(nickname, department, college)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 이 의견에 동의합니다~-!!!

setMessage(R.string.delete_description)
setNegativeButton("취소") { _, _ ->
activity?.showToast(App.appContext.getString(R.string.delete_undo))
activity?.showToast("리뷰 삭제를 취소하시겠습니까?")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

viewmodel에서 string 처리하는 부분도 다음에 같이 리팩토링 해보면 좋을 거같네요
전부터 계속 고민됐던 부분..
activity에서 문자열을 가져오는 방식이나 문자열을 가져오는 인터페이스를 만들거나 하면 될 거 같네요!


fun bind(data: Review) {
binding.tvWriterNickname.text = MySharedPreferences.getUserName(binding.root.context)
binding.tvWriterNickname.text = ""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분 반영되면 좋겟네요!

@kangyuri1114
Copy link
Member

conflict.. ㅠ ㅠ

@HI-JIN2
Copy link
Member Author

HI-JIN2 commented Nov 16, 2025

@kangyuri1114 @PeraSite GetUserCollegeDepartmentUseCase가 non-nullable한 값을 항상 반환할 수 있도록 했습니다. 4a18a2c 말씀하신 구조가 맞는지 검토 부탁드려요!

말씀해주신 의견 바탕으로 생각해보니 UI 단에서 빈값에 대한 예외처리를 하는 것보다 도메인 단에서 (디폴트값을) 채워서 보내는게 좋을 것 같네요!
UI는 UI만 신경 쓰고, 데이터에 대한 걱정은 안할 수 있도록 하는게 좋은 것 같슴다

어프루브 주시면 #398 PR 닫히면 리베이스 후 머지하겠습니다~

Copy link
Member

@PeraSite PeraSite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨플릭트 3억개... 오래된 브랜치 진짜 말 안되긴 하네요
언급했던 그 부분 해결된 것 맞습니다!! 고생하셨어요 👍

Copy link
Member

@kangyuri1114 kangyuri1114 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

94132f8
확인했습니다 수고하셨습니다 👍

@HI-JIN2 HI-JIN2 merged commit ea0e41b into develop Nov 19, 2025
1 check passed
@HI-JIN2 HI-JIN2 deleted the refactor/datastore branch November 19, 2025 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

레거시 SharedPreference를 DataStore로 마이그레이션 하기

4 participants