I want to implement the new In-App Update library in my app, but I've noticed that it trigger a memory leak in my activity when it's recreated/rotated.
Here's the only detail I have from LeakCanary:
Obviously, I've nothing if I remove the code from the In-App Update lib especially the addOnSuccessListener :
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
updateInfo.value = appUpdateInfo
updateAvailable.value = true
}else{
updateInfo.value = null
updateAvailable.value = false
}
}
According to this post, I have first used some LiveData, but the problem was the same, so I used a full class to handle the callback, with LiveData :
My Service class :
class AppUpdateService {
val updateAvailable: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val updateDownloaded: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val updateInfo: MutableLiveData<AppUpdateInfo> by lazy { MutableLiveData<AppUpdateInfo>() }
fun checkForUpdate(appUpdateManager: AppUpdateManager){
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
updateInfo.value = appUpdateInfo
updateAvailable.value = true
}else{
updateInfo.value = null
updateAvailable.value = false
}
}
}
fun checkUpdateOnResume(appUpdateManager: AppUpdateManager){
appUpdateManager.appUpdateInfo.addOnSuccessListener {
updateDownloaded.value = (it.installStatus() == InstallStatus.DOWNLOADED)
}
}
}
My Activity simplified :
class MainActivity : BaseActivity(), InstallStateUpdatedListener {
override fun contentViewID(): Int { return R.layout.activity_main }
private val UPDATE_REQUEST_CODE = 8000
private lateinit var appUpdateManager : AppUpdateManager
private val appUpdateService = AppUpdateService()
override fun onStateUpdate(state: InstallState?) {
if(state?.installStatus() == InstallStatus.DOWNLOADED){ notifyUser() }
}
// Called in the onCreate()
override fun setupView(){
appUpdateManager = AppUpdateManagerFactory.create(this)
appUpdateManager.registerListener(this)
setupAppUpdateServiceObservers()
// Check for Update
appUpdateService.checkForUpdate(appUpdateManager)
}
private fun setupAppUpdateServiceObservers(){
appUpdateService.updateAvailable.observe(this, Observer {
if (it)
requestUpdate(appUpdateService.updateInfo.value)
})
appUpdateService.updateDownloaded.observe(this, Observer {
if (it)
notifyUser()
})
}
private fun requestUpdate(appUpdateInfo: AppUpdateInfo?){
appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.FLEXIBLE, this, UPDATE_REQUEST_CODE)
}
private fun notifyUser(){
showSnackbar(getString(R.string.updated_downloaded), getString(R.string.restart)) {
appUpdateManager.completeUpdate()
appUpdateManager.unregisterListener(this)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == UPDATE_REQUEST_CODE) {
if (resultCode != RESULT_OK) {
Timber.d("Update flow failed! Result code: $resultCode")
}
}
}
override fun onDestroy() {
appUpdateManager.unregisterListener(this)
super.onDestroy()
}
override fun onResume() {
super.onResume()
appUpdateService.checkUpdateOnResume(appUpdateManager)
}
}
I don't really understand how to avoid the memory leak as the appUpdateManager has to be created with the context of the activity, and it looks to be the thing that causes the memory leak with the callback.
Does someone already implement it without having this issue?