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

[1.1.0/AN-FEAT] 인앱 업데이트 기능 구현 #358

Open
wants to merge 3 commits into
base: an/develop
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ dependencies {
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.crashlytics.buildtools)
implementation(libs.bundles.firebase)
implementation(libs.app.update)
implementation(libs.app.update.ktx)
// android test
androidTestImplementation(libs.bundles.android.test)
androidTestRuntimeOnly(libs.junit5.android.test.runner)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
import poke.rogue.helper.R
Expand All @@ -22,20 +23,47 @@ import poke.rogue.helper.presentation.util.context.stringOf
import poke.rogue.helper.presentation.util.context.toast
import poke.rogue.helper.presentation.util.logClickEvent
import poke.rogue.helper.presentation.util.repeatOnStarted
import poke.rogue.helper.update.UpdateManager
import timber.log.Timber

class HomeActivity : ToolbarActivity<ActivityHomeBinding>(R.layout.activity_home) {
private val viewModel by viewModels<HomeViewModel>()
private val logger: AnalyticsLogger = analyticsLogger()
private lateinit var updateManager: UpdateManager

override val toolbar: Toolbar
get() = binding.toolbarHome

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initUpdateManager()
initViews()
initObservers()
}

override fun onDestroy() {
super.onDestroy()
updateManager.unregisterInstallStateUpdateListener()
}

private fun initUpdateManager() {
updateManager = UpdateManager(applicationContext)
updateManager.registerInstallStateUpdateListener()
Comment on lines +46 to +51
Copy link
Contributor

Choose a reason for hiding this comment

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

별건 아니긴하지만 레벨 4에서배운 DefaultLifecycleObserver 를 활용한다면 �액티비티의 생명주기에 맞게 알아서 register/unregister 해줄 수 있을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

옹 좋은데요 ??? 👍👍
DefaultLifecycleObserver를 활용해서 리스너 관리 할게요


val appUpdateLauncher =
registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult(),
) { result ->
// logger도 달아야겠죠??
if (result.resultCode == RESULT_OK) {
Timber.i("Update completed successfully")
} else {
Timber.e("Update failed, result code: ${result.resultCode}")
}
}
updateManager.checkForAppUpdates(appUpdateLauncher)
}

private fun initViews() =
with(binding) {
supportActionBar?.setDisplayShowTitleEnabled(false)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package poke.rogue.helper.update

import android.content.Context
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.appupdate.AppUpdateOptions
import com.google.android.play.core.install.InstallStateUpdatedListener
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.InstallStatus
import com.google.android.play.core.install.model.UpdateAvailability
import timber.log.Timber

class UpdateManager(
private val context: Context,
) {
private val appUpdateManager: AppUpdateManager = AppUpdateManagerFactory.create(context)
private val updateType = AppUpdateType.FLEXIBLE
Copy link

Choose a reason for hiding this comment

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

정말 개인적으로 이건 변수로 따로 안 빼는게 좋아 보입니다.
아래 있는 checkForAppUpdate 함수의
val isUpdateAllowed = info.isUpdateTypeAllowed(updateType)
👇
val isUpdateAllowed = info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)

이게 더 빨리 읽힘 ㅎ
근데 사소해

Copy link
Contributor

Choose a reason for hiding this comment

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

저도 동의합니다! 중복이 되긴하지만 지역 변수로 만들거나 그대로 사용하는게 더 잘 읽히는 것 같네요 ㅋㅅㅋ

Copy link
Author

Choose a reason for hiding this comment

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

변수로 뺀 이유는, UpdateManager에서 updateType은 가장 중요하다고 생각해서 뺏습니다 !
만약 업데이트 타입을 바꾸기 위해서 해당 클래스로 들어왔을 때, checkForAppUpdate()를 찾는 번거로움도 있을 것 같기도 하고~


private val installStateUpdateListener =
InstallStateUpdatedListener { state ->
when (state.installStatus()) {
InstallStatus.INSTALLING -> Timber.i("Update is downloading")

InstallStatus.DOWNLOADED -> {
Timber.i("Update installed successfully")
appUpdateManager.completeUpdate()
Copy link

Choose a reason for hiding this comment

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

이거 다운로드 완료되면 어떻게 되나요?
영상에 첨부 안되어 있어서

Copy link
Author

@kkosang kkosang Oct 14, 2024

Choose a reason for hiding this comment

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

다운로드 완료되면 플레이스토어 들어가져서 설치하고 재시작됩니다 !

an.feat._.webm

}

InstallStatus.CANCELED -> Timber.e("Update was cancelled")
}
}

fun checkForAppUpdates(appUpdateLauncher: ActivityResultLauncher<IntentSenderRequest>) {
appUpdateManager.appUpdateInfo.addOnSuccessListener { info ->
if (checkForAppUpdate(info)) {
val updateOptions = AppUpdateOptions.newBuilder(updateType).build()
appUpdateManager.startUpdateFlowForResult(
info,
appUpdateLauncher,
updateOptions,
)
}
}
}

private fun checkForAppUpdate(info: AppUpdateInfo): Boolean {
Copy link

Choose a reason for hiding this comment

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

boolean 을 리턴하는데 메서드 네이밍이 checkFor 로 시작해요.
저희 컨벤션에 맞지 않아요.

appUpdateIsEnabled 등으로 메서드 네이밍을 빌더로 해야 합니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

저는 이런 경우 should 라는 prefix를 선호합니다! 이유는 Android api 가 해당 네이밍을 많이 써서에요 ㅎㅎ

shouldAppUpdate() 어떠신가요??

근데 이건 동사인가..?

Copy link

Choose a reason for hiding this comment

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

shouldAppUpdate() Boolean 이라 이것도 괜찮을 듯?

Copy link
Author

Choose a reason for hiding this comment

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

boolean 을 리턴하는데 메서드 네이밍이 checkFor 로 시작해요.
저희 컨벤션에 맞지 않아요.

헉쓰 그렇네요~

Android api 가 해당 네이밍을 많이 써서에요 ㅎㅎ

shouldAppUpdate()로 변경하겠습니다~~

val isUpdateAvailable = info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
val isUpdateAllowed = info.isUpdateTypeAllowed(updateType)
return isUpdateAvailable && isUpdateAllowed
}

fun registerInstallStateUpdateListener() {
appUpdateManager.registerListener(installStateUpdateListener)
}

fun unregisterInstallStateUpdateListener() {
appUpdateManager.unregisterListener(installStateUpdateListener)
}
}
3 changes: 3 additions & 0 deletions android/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ datastore = "1.0.0"
google-services = "4.4.2"
firebase = "33.1.2"
firebase-crashlytics = "3.0.2"
app-update = "2.1.0"

# Android-Test
android-junit5-plugin = "1.10.0.0"
Expand Down Expand Up @@ -89,6 +90,8 @@ firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "fir
firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" }
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" }
firebase-crashlytics-plugin = { module = "com.google.firebase:firebase-crashlytics-gradle", version.ref = "firebase-crashlytics" }
app-update = { module = "com.google.android.play:app-update", version.ref = "app-update" }
app-update-ktx = { module = "com.google.android.play:app-update-ktx", version.ref = "app-update" }
Comment on lines +93 to +94
Copy link
Contributor

Choose a reason for hiding this comment

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

뭔가 ktx 만 추가해도 될 것 같다는 느낌?? 아닌가욥?

Copy link
Author

@kkosang kkosang Oct 14, 2024

Choose a reason for hiding this comment

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

그러게요 코틀린 환경에서만 사용하는거면 ktx만 추가해도 되는군요 !
app-update는 삭제했습니다~

# android test
androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-junit" }
android-test-fragment = { module = "androidx.fragment:fragment-testing", version.ref = "androidx-test-fragment" }
Expand Down
Loading