Skip to content

Commit

Permalink
[FEAT/#466] 인앱 업데이트 자체 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
b1urrrr committed Aug 15, 2024
1 parent 6ac47a7 commit 2dd57c7
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 77 deletions.
7 changes: 6 additions & 1 deletion app/src/main/java/com/el/yello/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package com.el.yello.di

import android.app.Application
import android.content.Context
import com.example.data.util.FileParser
import com.el.yello.presentation.main.ResolutionMetrics
import com.example.data.util.FileParser
import com.google.firebase.database.FirebaseDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand All @@ -29,4 +30,8 @@ object AppModule {
@Singleton
fun provideResolutionMetrics(@ApplicationContext context: Application) =
ResolutionMetrics(context)

@Provides
@Singleton
fun provideDatabaseReference() = FirebaseDatabase.getInstance().reference
}
137 changes: 63 additions & 74 deletions app/src/main/java/com/el/yello/presentation/splash/SplashActivity.kt
Original file line number Diff line number Diff line change
@@ -1,83 +1,92 @@
package com.el.yello.presentation.splash

import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.el.yello.BuildConfig
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.el.yello.R
import com.el.yello.databinding.ActivitySplashBinding
import com.el.yello.presentation.auth.SignInActivity
import com.el.yello.presentation.main.MainActivity
import com.el.yello.util.extension.yelloSnackbar
import com.el.yello.util.manager.NetworkManager
import com.example.ui.base.BindingActivity
import com.example.ui.extension.toast
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.appupdate.AppUpdateOptions
import com.google.android.play.core.install.model.AppUpdateType.IMMEDIATE
import com.google.android.play.core.install.model.UpdateAvailability
import com.example.ui.state.UiState
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber


@AndroidEntryPoint
class SplashActivity : BindingActivity<ActivitySplashBinding>(R.layout.activity_splash) {
private val viewModel by viewModels<SplashViewModel>()

private val appUpdateManager by lazy { AppUpdateManagerFactory.create(this) }

override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)

initView()
}

private fun initView() {
showExtraToastMsg()
initAppUpdate()
checkNetworkUpdateState()
}

private fun showExtraToastMsg() {
yelloSnackbar(binding.root, intent.getStringExtra(EXTRA_TOAST_MSG) ?: return)
}

private fun initAppUpdate() {
private fun checkNetworkUpdateState() {
if (NetworkManager.checkNetworkState(this)) {
if (BuildConfig.DEBUG) {
initSplash()
} else {
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
appUpdateInfo.isUpdateTypeAllowed(IMMEDIATE)
requestUpdate(appUpdateInfo)
} else {
initSplash()
}
}.addOnFailureListener {
initSplash()
}
}
observeIsLatestVersion()
} else {
AlertDialog.Builder(this)
.setTitle("안내")
.setMessage("인터넷 연결을 확인해주세요.")
.setTitle(getString(R.string.splash_guide))
.setMessage(getString(R.string.splash_network_description))
.setCancelable(false)
.setPositiveButton(
"확인",
DialogInterface.OnClickListener { dialog, _ ->
finishAffinity()
},
)
.setPositiveButton(getString(R.string.splash_confirm)) { _, _ ->
finishAffinity()
}
.create()
.show()
}
}

private fun initSplash() {
private fun observeIsLatestVersion() {
viewModel.isLatestVersion.flowWithLifecycle(lifecycle)
.onEach { state ->
when (state) {
is UiState.Empty, is UiState.Loading -> {
return@onEach
}

is UiState.Success -> {
val isLatestVersion = state.data

if (isLatestVersion) {
initSplashView()
} else {
showInAppUpdateDialog()
}
}

is UiState.Failure -> {
// TODO : 인앱 업데이트 필요 여부 조회 실패 시 UI 처리
Timber.e(state.msg)
}
}
}.launchIn(lifecycleScope)
}

private fun initSplashView() {
Handler(Looper.getMainLooper()).postDelayed({
if (viewModel.getIsAutoLogin()) {
navigateToMainScreen()
Expand All @@ -87,20 +96,6 @@ class SplashActivity : BindingActivity<ActivitySplashBinding>(R.layout.activity_
}, 3000)
}

private fun requestUpdate(appUpdateInfo: AppUpdateInfo) {
runCatching {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
activityResultLauncher,
AppUpdateOptions.newBuilder(IMMEDIATE)
.setAllowAssetPackDeletion(true)
.build(),
)
}.onFailure {
Timber.e(it)
}
}

private fun navigateToMainScreen() {
var type: String? = ""
var path: String? = ""
Expand All @@ -123,36 +118,30 @@ class SplashActivity : BindingActivity<ActivitySplashBinding>(R.layout.activity_
finish()
}

private val activityResultLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if (it.resultCode != RESULT_OK) {
toast(getString(R.string.splash_update_error))
finishAffinity()
private fun showInAppUpdateDialog() {
AlertDialog.Builder(this)
.setTitle(getString(R.string.splash_guide))
.setMessage(getString(R.string.slash_update_description))
.setCancelable(false)
.setPositiveButton(getString(R.string.splash_confirm)) { _, _ ->
navigateToMarket()
}
}
.create()
.show()
}

private fun navigateToMarket() {
val uri = Uri.parse(URI_MARKET + packageName)
startActivity(Intent(Intent.ACTION_VIEW, uri))
}

override fun onResume() {
super.onResume()

if (!BuildConfig.DEBUG) {
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
runCatching {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
activityResultLauncher,
AppUpdateOptions.newBuilder(IMMEDIATE)
.build(),
)
}.onFailure {
Timber.e(it)
}
}
}
}
viewModel.checkLatestUpdate()
}

companion object {
private const val EXTRA_TOAST_MSG = "TOAST_MSG"
private const val URI_MARKET = "market://details?id="
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,44 @@
package com.el.yello.presentation.splash

import androidx.lifecycle.ViewModel
import com.el.yello.BuildConfig.VERSION_NAME
import com.example.domain.repository.AuthRepository
import com.example.ui.state.UiState
import com.google.firebase.database.DatabaseReference
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject

@HiltViewModel
class SplashViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val firebaseDatabase: DatabaseReference,
) : ViewModel() {
private val _isLatestVersion = MutableStateFlow<UiState<Boolean>>(UiState.Empty)
val isLatestVersion get() = _isLatestVersion.asStateFlow()

init {
checkLatestUpdate()
}

fun getIsAutoLogin(): Boolean = authRepository.getAutoLogin()

fun checkLatestUpdate() {
firebaseDatabase.child(PATH_REALTIME_VERSION).get()
.addOnSuccessListener { snapshot ->
val updateVersion = snapshot.value.toString().toFloat()
val currentVersion = VERSION_NAME.toFloat()

val isLatestVersion = currentVersion < updateVersion
_isLatestVersion.value = UiState.Success(isLatestVersion)
}
.addOnFailureListener { e ->
_isLatestVersion.value = UiState.Failure(e.toString())
}
}

companion object {
private const val PATH_REALTIME_VERSION = "version"
}
}
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
<string name="manager_not_initialized_error_msg">Billing Manager is not initialized</string>

<string name="splash_title">지금 누군가 당신을 생각하고 있어요!</string>
<string name="splash_guide">안내</string>
<string name="slash_update_description">이 앱을 사용하려면 최신 버전을 다운로드하세요.</string>
<string name="splash_confirm">확인</string>
<string name="splash_network_description">인터넷 연결을 확인해주세요.</string>

<string name="sign_in_tv_title">Yell:o에 오신 걸 환영해요!</string>
<string name="sign_in_tv_subtitle">가입을 시작해볼까요?</string>
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx"}
firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx"}
firebase-remoteConfig = { group = "com.google.firebase", name = "firebase-config-ktx"}
firebase-database = { group = "com.google.firebase", name = "firebase-database" }

## Compose
compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "composeActivityVersion" }
Expand All @@ -182,7 +183,6 @@ compose-bom-ui-test = {group = "androidx.compose.ui", name = "ui-test-junit4"}
compose-bom-debug-ui-test-manifest = {group = "androidx.compose.ui", name="ui-test-manifest"}
compose-runtime = {group = "androidx.compose.runtime", name="runtime"}


## Test
jUnit = { group = "junit", name = "junit", version.ref = "junitVersion" }
androidTest = { group = "androidx.test.ext", name = "junit", version.ref = "androidTestVersion" }
Expand Down Expand Up @@ -258,6 +258,6 @@ appModuleLibraryEtc = [
]

okhttp = ["okhttp", "okhttp-logging-interceptor"]
firebase = ["firebase-analytics", "firebase-crashlytics", "firebase-messaging", "firebase-remoteConfig"]
firebase = ["firebase-analytics", "firebase-crashlytics", "firebase-messaging", "firebase-remoteConfig", "firebase-database"]
flipper = ["flipper", "soloader", "flipper-network", "flipper-leakcanary"]
retrofit = ["retrofit", "retrofit-json-converter"]

0 comments on commit 2dd57c7

Please sign in to comment.