Skip to content
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

[FEAT/#466] 인앱 업데이트 자체 구현 #467

Merged
merged 5 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
143 changes: 73 additions & 70 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,101 @@
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.BuildConfig.DEBUG
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()
initObserver()
}

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()
if (DEBUG) {
initSplashView()
} else {
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
appUpdateInfo.isUpdateTypeAllowed(IMMEDIATE)
requestUpdate(appUpdateInfo)
} else {
initSplash()
}
}.addOnFailureListener {
initSplash()
}
viewModel.checkLatestUpdate()
}
} 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 initObserver() {
observeIsLatestVersion()
}

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 +105,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 +127,35 @@ 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)
}
}
}
if (DEBUG) {
initSplashView()
} else {
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,40 @@
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()

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())
Comment on lines +26 to +33
Copy link
Member

Choose a reason for hiding this comment

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

isLatestVersion 변수는 한번밖에 안쓰여서 변수 3개를 한번에 처리할 수 있을 것 같은데, 지금은 코드를 가독성 좋게 만드는 과정일까요?
(옐로 쌤들한테 불필요한 변수 설정 최소화하라고 배웠던 기억이 있어서 .. ㅋ ㅋ)

Copy link
Member Author

Choose a reason for hiding this comment

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

넹 어떤 조건인지 써주는 느낌으로 변수 만들었슴다

}
}

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 build-logic/convention/src/main/kotlin/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ object Constants {
const val compileSdk = 34
const val minSdk = 28
const val targetSdk = 34
const val versionCode = 52
const val versionName = "2.2"
const val versionCode = 53
const val versionName = "2.3"
const val jvmVersion = "18"
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ class AndroidApplicationPlugin : Plugin<Project> {
androidTestImplementation(libs.getLibrary("espresso"))

// google
implementation(libs.getLibrary("inAppUpdate"))
implementation(libs.getLibrary("ossLicense"))
implementation(libs.getLibrary("gson"))

Expand Down
Loading
Loading