-
Notifications
You must be signed in to change notification settings - Fork 0
[Refactor] SharedPreference에서 datastore로 마이그레이션 #396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
09f8863 to
2f07816
Compare
There was a problem hiding this 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 = "" |
Copilot
AI
Oct 18, 2025
There was a problem hiding this comment.
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.
| binding.tvWriterNickname.text = "" | |
| binding.tvWriterNickname.text = if (!data.writerNickname.isNullOrBlank()) data.writerNickname else "익명" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이부분 반영되면 좋겟네요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
옹 전에 유저라고 디폴트있는거 별로라고 했던 것 같아서 공백으로 한건데 유저로 바꿀까염?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 그런가요? 기획에서 결정된 부분이면 안바꿔주셔도 될 것 같아요! 몰랐네요 ㅠ
| val mapState = uiState as UiState.Success<MapState> | ||
| val departmentId = mapState.data?.currentDepartment?.departmentId?.toLong() | ||
| val collegeId = mapState.data?.currentCollege?.collegeId?.toLong() |
Copilot
AI
Oct 18, 2025
There was a problem hiding this comment.
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().
| 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() |
| setMessage(R.string.delete_description) | ||
| setNegativeButton("취소") { _, _ -> | ||
| activity?.showToast(App.appContext.getString(R.string.delete_undo)) | ||
| activity?.showToast("리뷰 삭제를 취소하시겠습니까?") |
Copilot
AI
Oct 18, 2025
There was a problem hiding this comment.
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.
| activity?.showToast("리뷰 삭제를 취소하시겠습니까?") | |
| activity?.showToast(getString(R.string.review_delete_cancel_message)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
viewmodel에서 string 처리하는 부분도 다음에 같이 리팩토링 해보면 좋을 거같네요
전부터 계속 고민됐던 부분..
activity에서 문자열을 가져오는 방식이나 문자열을 가져오는 인터페이스를 만들거나 하면 될 거 같네요!
There was a problem hiding this comment.
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 = "리뷰 수정이 실패하였습니다." |
Copilot
AI
Oct 18, 2025
There was a problem hiding this comment.
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.
| error = false, | ||
| isDone = true, | ||
| toastMessage = App.appContext.getString(R.string.modify_done) | ||
| toastMessage = "리뷰가 수정되었습니다." |
Copilot
AI
Oct 18, 2025
There was a problem hiding this comment.
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.
| toastMessage = "리뷰가 수정되었습니다." | |
| toastMessage = "modify_done" // UI should map this key to a localized string resource |
| import javax.inject.Inject | ||
| import javax.inject.Singleton | ||
|
|
||
| private val Context.widgetPrefsDataStore: DataStore<Preferences> by preferencesDataStore(name = "widget_prefs") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위젯용 data store을 건들이지는 않아서 기존 바탕화면에 적재한 위젯에 영향은 없을듯합니다!
| class App : Application(), Configuration.Provider { | ||
| companion object{ | ||
| lateinit var appContext: Context //todo 이거 빼기 | ||
| } | ||
|
|
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 를 반환하는 것이 더 깔끔하지 않을까요!?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로그인, 세션 관련 처리할 때 token이 null인지가 아니라 empty인지를 확인하는 부분 볼 때마다 ""인지 null인지 사람이 개발하다 헷갈려서 미래에 문제가 생길 수도 있겠다 생각했는데, 이번 기회에 바꾸면 좋을 것 같아요. 어떻게 생각하시나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nullorempty를 검사하면 되지 않을까요??
제훈님 말씀은 ""을 저장하지 않게 하자 그리고 null 아니면 토큰만 반환하도록 하자인가용?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nullOrEmpty를 검사하면 되긴 하지만 ""라는 빈 공백 값이 가능한 상황은 최대한 줄이면 좋다고 생각했어요! 말씀해주신대로 Token 아니면 null을 반환해 ""를 쓰지 말자가 맞습니다!
kangyuri1114
left a comment
There was a problem hiding this 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 수정 되면 머지해도 될 것 같네요!
| 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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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("리뷰 삭제를 취소하시겠습니까?") |
There was a problem hiding this comment.
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 = "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이부분 반영되면 좋겟네요!
|
conflict.. ㅠ ㅠ |
|
@kangyuri1114 @PeraSite GetUserCollegeDepartmentUseCase가 non-nullable한 값을 항상 반환할 수 있도록 했습니다. 4a18a2c 말씀하신 구조가 맞는지 검토 부탁드려요! 말씀해주신 의견 바탕으로 생각해보니 UI 단에서 빈값에 대한 예외처리를 하는 것보다 도메인 단에서 (디폴트값을) 채워서 보내는게 좋을 것 같네요! 어프루브 주시면 #398 PR 닫히면 리베이스 후 머지하겠습니다~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
컨플릭트 3억개... 오래된 브랜치 진짜 말 안되긴 하네요
언급했던 그 부분 해결된 것 맞습니다!! 고생하셨어요 👍
4a18a2c to
6af60a6
Compare
kangyuri1114
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
94132f8
확인했습니다 수고하셨습니다 👍
빈 값일시, 디폴트 값을 담도록 수정
0941770 to
26ddc0a
Compare
Summary
SharedPreference에서 datastore로 마이그레이션 합니다
Describe your changes
Issue