-
Notifications
You must be signed in to change notification settings - Fork 0
[Fix] 강제 업데이트 확인 Intro에서 하게 수정 #403
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,13 +5,16 @@ import com.eatssu.android.domain.model.RestaurantInfo | |||||||||||||||
| import com.eatssu.common.enums.Restaurant | ||||||||||||||||
| import com.google.firebase.remoteconfig.FirebaseRemoteConfig | ||||||||||||||||
| import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings | ||||||||||||||||
| import kotlinx.coroutines.suspendCancellableCoroutine | ||||||||||||||||
| import org.json.JSONArray | ||||||||||||||||
| import timber.log.Timber | ||||||||||||||||
| import kotlin.coroutines.resume | ||||||||||||||||
|
|
||||||||||||||||
| class FirebaseRemoteConfigRepository { | ||||||||||||||||
| private val instance = FirebaseRemoteConfig.getInstance() | ||||||||||||||||
|
|
||||||||||||||||
| fun init() { | ||||||||||||||||
| private val firebaseRemoteConfig = FirebaseRemoteConfig.getInstance() | ||||||||||||||||
|
|
||||||||||||||||
| init { | ||||||||||||||||
| /** | ||||||||||||||||
| * Firebase Remote Config 초기화 설정 | ||||||||||||||||
| * | ||||||||||||||||
|
|
@@ -20,51 +23,34 @@ class FirebaseRemoteConfigRepository { | |||||||||||||||
| * 변경 사유: 사용자가 앱에 머무는 시간이 되게 짦다. | ||||||||||||||||
| */ | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| val configSettings = FirebaseRemoteConfigSettings.Builder() | ||||||||||||||||
| .setMinimumFetchIntervalInSeconds(600) | ||||||||||||||||
| .build() | ||||||||||||||||
| instance.setConfigSettingsAsync(configSettings) | ||||||||||||||||
| instance.setDefaultsAsync(R.xml.firebase_remote_config) | ||||||||||||||||
|
|
||||||||||||||||
| instance.fetchAndActivate().addOnCompleteListener { task -> | ||||||||||||||||
| if (task.isSuccessful) { | ||||||||||||||||
| Timber.d("fetchAndActivate 성공") | ||||||||||||||||
| } else { | ||||||||||||||||
| // Handle error | ||||||||||||||||
| Timber.d("fetchAndActivate error") | ||||||||||||||||
| instance.setDefaultsAsync(R.xml.firebase_remote_config) | ||||||||||||||||
| // throw RuntimeException("fetchAndActivate 실패") | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| firebaseRemoteConfig.setConfigSettingsAsync(configSettings) | ||||||||||||||||
| firebaseRemoteConfig.setDefaultsAsync(R.xml.firebase_remote_config) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // fun getAndroidMessage(): AndroidMessage { | ||||||||||||||||
| // | ||||||||||||||||
| // // Gson을 사용하여 JSON 문자열을 DTO로 파싱 | ||||||||||||||||
| // val serverStatus: AndroidMessage = Gson().fromJson(instance.getString("android_message"), AndroidMessage::class.java) | ||||||||||||||||
| // | ||||||||||||||||
| // // 파싱된 결과 확인 | ||||||||||||||||
| // println("Dialog: ${serverStatus.dialog}") | ||||||||||||||||
| // println("Message: ${serverStatus.message}") | ||||||||||||||||
| // | ||||||||||||||||
| // return serverStatus | ||||||||||||||||
| // } | ||||||||||||||||
| suspend fun getVersionCode() = useFirebaseConfig { | ||||||||||||||||
| getLong("android_version_code") | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // fun getForceUpdate(): Boolean { | ||||||||||||||||
| // return instance.getBoolean("force_update") | ||||||||||||||||
| // } | ||||||||||||||||
| // | ||||||||||||||||
| // fun getAppVersion(): String { | ||||||||||||||||
| // return instance.getString("app_version") | ||||||||||||||||
| // } | ||||||||||||||||
| suspend fun getCafeteriaInfo() = useFirebaseConfig { | ||||||||||||||||
| parsingJson(getString("cafeteria_information")) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| fun getVersionCode(): Long { | ||||||||||||||||
| return instance.getLong("android_version_code") | ||||||||||||||||
| private suspend fun <T> useFirebaseConfig(block: FirebaseRemoteConfig.() -> T): T { | ||||||||||||||||
| fetchAndActivateSuspend() | ||||||||||||||||
| // fetchAndActivate가 완료된 후에 새로운 값을 가져오도록 보장 | ||||||||||||||||
| return block(FirebaseRemoteConfig.getInstance()) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| fun getCafeteriaInfo(): ArrayList<RestaurantInfo> { | ||||||||||||||||
| return parsingJson(instance.getString("cafeteria_information")) | ||||||||||||||||
| private suspend fun fetchAndActivateSuspend() = suspendCancellableCoroutine { continuation -> | ||||||||||||||||
| // fetchAndActivate는 minimumFetchIntervalInSeconds 보다 짧은 시간에 여러번 호출되어도 | ||||||||||||||||
| // 실제로는 minimumFetchIntervalInSeconds 이후에만 fetch가 수행된다. | ||||||||||||||||
|
|
||||||||||||||||
| firebaseRemoteConfig.fetchAndActivate().addOnCompleteListener { task -> | ||||||||||||||||
| continuation.resume(Unit) | ||||||||||||||||
|
||||||||||||||||
| continuation.resume(Unit) | |
| if (task.isSuccessful) { | |
| continuation.resume(Unit) | |
| } else { | |
| Timber.e(task.exception, "fetchAndActivate failed") | |
| continuation.resumeWithException(task.exception ?: Exception("fetchAndActivate failed")) | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,27 @@ | ||||||||||||||||||||||||||||||||||||||
| package com.eatssu.android.domain.usecase.version | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import com.eatssu.android.BuildConfig | ||||||||||||||||||||||||||||||||||||||
| import com.eatssu.android.data.repository.FirebaseRemoteConfigRepository | ||||||||||||||||||||||||||||||||||||||
| import timber.log.Timber | ||||||||||||||||||||||||||||||||||||||
| import javax.inject.Inject | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Firebase Remote Config에서 최신 값을 가져와서 | ||||||||||||||||||||||||||||||||||||||
| * 현재 앱 버전과 비교하여 강제 업데이트가 필요한지 판단합니다. | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| class CheckForceUpdateRequiredUseCase @Inject constructor( | ||||||||||||||||||||||||||||||||||||||
| private val firebaseRemoteConfigRepository: FirebaseRemoteConfigRepository | ||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * @return 현재 앱 버전이 Firebase에 설정된 최소 버전보다 낮으면 true (업데이트 필요) | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| suspend operator fun invoke(): Boolean { | ||||||||||||||||||||||||||||||||||||||
| val remoteVersionCode = firebaseRemoteConfigRepository.getVersionCode() | ||||||||||||||||||||||||||||||||||||||
| val currentVersionCode = BuildConfig.VERSION_CODE | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Timber.d("현재 앱 버전: $currentVersionCode, Firebase 최소 버전: $remoteVersionCode") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return currentVersionCode < remoteVersionCode | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+26
|
||||||||||||||||||||||||||||||||||||||
| val remoteVersionCode = firebaseRemoteConfigRepository.getVersionCode() | |
| val currentVersionCode = BuildConfig.VERSION_CODE | |
| Timber.d("현재 앱 버전: $currentVersionCode, Firebase 최소 버전: $remoteVersionCode") | |
| return currentVersionCode < remoteVersionCode | |
| } | |
| return try { | |
| val remoteVersionCode = firebaseRemoteConfigRepository.getVersionCode() | |
| val currentVersionCode = BuildConfig.VERSION_CODE | |
| Timber.d("현재 앱 버전: $currentVersionCode, Firebase 최소 버전: $remoteVersionCode") | |
| currentVersionCode < remoteVersionCode | |
| } catch (e: Exception) { | |
| Timber.e(e, "Failed to fetch remote version code for force update check") | |
| false | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,15 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.eatssu.android.presentation.cafeteria.info | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.ViewModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.viewModelScope | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.eatssu.android.data.repository.FirebaseRemoteConfigRepository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.eatssu.android.domain.model.RestaurantInfo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.eatssu.common.enums.Restaurant | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.MutableStateFlow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.StateFlow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.asStateFlow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.launch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import timber.log.Timber | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import javax.inject.Inject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -22,10 +24,12 @@ class InfoViewModel @Inject constructor( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val restaurantInfoMap: MutableMap<Restaurant, RestaurantInfo> = mutableMapOf() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| init { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _infoList.value = firebaseRemoteConfigRepository.getCafeteriaInfo() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Timber.d(_infoList.value.toString()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _infoList.value.forEach { restaurantInfo -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| restaurantInfoMap[restaurantInfo.enum] = restaurantInfo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModelScope.launch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _infoList.value = firebaseRemoteConfigRepository.getCafeteriaInfo() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Timber.d(_infoList.value.toString()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _infoList.value.forEach { restaurantInfo -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| restaurantInfoMap[restaurantInfo.enum] = restaurantInfo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
24
to
+31
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val restaurantInfoMap: MutableMap<Restaurant, RestaurantInfo> = mutableMapOf() | |
| init { | |
| _infoList.value = firebaseRemoteConfigRepository.getCafeteriaInfo() | |
| Timber.d(_infoList.value.toString()) | |
| _infoList.value.forEach { restaurantInfo -> | |
| restaurantInfoMap[restaurantInfo.enum] = restaurantInfo | |
| viewModelScope.launch { | |
| _infoList.value = firebaseRemoteConfigRepository.getCafeteriaInfo() | |
| Timber.d(_infoList.value.toString()) | |
| _infoList.value.forEach { restaurantInfo -> | |
| restaurantInfoMap[restaurantInfo.enum] = restaurantInfo | |
| private val _errorMessage = MutableStateFlow<String?>(null) | |
| val errorMessage: StateFlow<String?> = _errorMessage.asStateFlow() | |
| private val restaurantInfoMap: MutableMap<Restaurant, RestaurantInfo> = mutableMapOf() | |
| init { | |
| viewModelScope.launch { | |
| try { | |
| _infoList.value = firebaseRemoteConfigRepository.getCafeteriaInfo() | |
| Timber.d(_infoList.value.toString()) | |
| _infoList.value.forEach { restaurantInfo -> | |
| restaurantInfoMap[restaurantInfo.enum] = restaurantInfo | |
| } | |
| _errorMessage.value = null // Clear error on success | |
| } catch (e: Exception) { | |
| Timber.e(e, "Failed to fetch cafeteria info") | |
| _errorMessage.value = e.message ?: "Unknown error occurred" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,18 @@ | ||
| package com.eatssu.android.presentation.common | ||
|
|
||
|
|
||
| import android.app.AlertDialog | ||
| import android.content.ActivityNotFoundException | ||
| import android.content.Intent | ||
| import android.net.Uri | ||
| import android.os.Bundle | ||
| import androidx.appcompat.app.AppCompatActivity | ||
|
|
||
|
|
||
| import androidx.core.net.toUri | ||
|
|
||
| /** | ||
| * 강제 업데이트 다이얼로그를 표시하는 액티비티 | ||
| * | ||
| * Firebase Remote Config에서 설정한 최소 버전보다 현재 앱 버전이 낮을 경우 | ||
| * 이 액티비티가 표시되며, 사용자는 반드시 업데이트를 해야 앱을 사용할 수 있습니다. | ||
| */ | ||
| class ForceUpdateDialogActivity : AppCompatActivity() { | ||
|
|
||
| override fun onCreate(savedInstanceState: Bundle?) { | ||
|
|
@@ -16,36 +21,36 @@ class ForceUpdateDialogActivity : AppCompatActivity() { | |
| } | ||
|
|
||
| private fun showForceUpdateDialog() { | ||
| val builder = AlertDialog.Builder(this) | ||
| builder.setTitle("강제 업데이트") | ||
| builder.setMessage("새 버전의 앱을 설치해야 합니다.") | ||
| AlertDialog.Builder(this).apply { | ||
| setTitle("업데이트가 필요합니다") | ||
| setMessage("원활한 서비스 이용을 위해\n최신 버전으로 업데이트해 주세요.") | ||
| setPositiveButton("업데이트") { _, _ -> | ||
| openPlayStore() | ||
| finish() | ||
| } | ||
| setCancelable(false) | ||
| create() | ||
| }.show() | ||
|
Comment on lines
+24
to
+33
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
AlertDialog.Builder(this).apply {
setTitle("업데이트가 필요합니다")
setMessage("원활한 서비스 이용을 위해\n최신 버전으로 업데이트해 주세요.")
setPositiveButton("업데이트") { _, _ ->
openPlayStore()
finish()
}
setCancelable(false)
}.show() |
||
| } | ||
|
|
||
| builder.setPositiveButton("업데이트") { dialog, which -> | ||
| // Google Play Store의 앱 페이지로 이동하여 업데이트를 다운로드합니다. | ||
| val appPackageName = packageName | ||
| try { | ||
| startActivity( | ||
| Intent( | ||
| Intent.ACTION_VIEW, | ||
| Uri.parse("market://details?id=$appPackageName") | ||
| ) | ||
| private fun openPlayStore() { | ||
| val appPackageName = packageName | ||
| try { | ||
| // Google Play 앱으로 직접 이동 | ||
| startActivity( | ||
| Intent( | ||
| Intent.ACTION_VIEW, | ||
| "market://details?id=$appPackageName".toUri() | ||
| ) | ||
| } catch (e: android.content.ActivityNotFoundException) { | ||
| startActivity( | ||
| Intent( | ||
| Intent.ACTION_VIEW, | ||
| Uri.parse("https://play.google.com/store/apps/details?id=$appPackageName") | ||
| ) | ||
| ) | ||
| } catch (_: ActivityNotFoundException) { | ||
| // Play 앱이 없는 경우 웹 브라우저로 이동 | ||
| startActivity( | ||
| Intent( | ||
| Intent.ACTION_VIEW, | ||
| "https://play.google.com/store/apps/details?id=$appPackageName".toUri() | ||
| ) | ||
| } | ||
|
|
||
| // 다이얼로그를 종료합니다. | ||
| finish() | ||
| ) | ||
| } | ||
|
|
||
| builder.setCancelable(false) // 사용자가 다이얼로그를 취소할 수 없도록 설정 | ||
|
|
||
| val dialog = builder.create() | ||
| dialog.show() | ||
| } | ||
| } | ||
This file was deleted.
This file was deleted.
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.
useFirebaseConfig함수 내에서FirebaseRemoteConfig.getInstance()를 다시 호출하는 대신, 클래스 프로퍼티인firebaseRemoteConfig를 사용하는 것이 좋습니다.FirebaseRemoteConfig.getInstance()는 싱글톤 인스턴스를 반환하므로 기능적으로는 문제가 없지만, 이미 인스턴스를 프로퍼티로 가지고 있으므로 재호출은 불필요합니다.또한, 수신 객체가 지정된 람다(lambda with receiver)는 확장 함수처럼 호출하는 것이 더 관용적(idiomatic)입니다.