Merge pull request #724 from NotWoods/viewmodel-no-leak
Remove HomeViewModel memory leak
This commit is contained in:
commit
eb28c6163c
|
@ -84,10 +84,12 @@ class ExpandableAppListAdapter(
|
|||
appDownload.apply {
|
||||
setOnClickListener {
|
||||
viewModel.openInstallDialog(
|
||||
activity.supportFragmentManager,
|
||||
buttonTag,
|
||||
apps[position]
|
||||
)
|
||||
}
|
||||
appDownload.setIconResource(buttonTag.image)
|
||||
contentDescription = activity.getString(
|
||||
when (buttonTag) {
|
||||
ButtonTag.UPDATE -> R.string.accessibility_update
|
||||
|
@ -108,11 +110,6 @@ class ExpandableAppListAdapter(
|
|||
dataModel?.installedVersionName?.observe(activity) {
|
||||
appVersionInstalled.text = it
|
||||
}
|
||||
dataModel?.buttonImage?.observe(activity) {
|
||||
if (it != null) {
|
||||
appDownload.icon = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ class LinkAdapter(
|
|||
|
||||
fun bind(position: Int) {
|
||||
binding.linkBg.setOnClickListener {
|
||||
viewModel.openUrl(links[position].linkUrl)
|
||||
viewModel.openUrl(context, links[position].linkUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class SponsorAdapter(
|
|||
with(binding) {
|
||||
sponsorName.text = sponsors[position].name
|
||||
cardSponsor.setOnClickListener {
|
||||
viewModel.openUrl(sponsors[position].url)
|
||||
viewModel.openUrl(context, sponsors[position].url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package com.vanced.manager.core
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
|
||||
/**
|
||||
* CombinedLiveData is a helper class to combine results from two LiveData sources.
|
||||
* @param combine Function reference that will be used to combine all LiveData data.
|
||||
* @param R The type of data returned after combining all LiveData data.
|
||||
* Usage:
|
||||
* CombinedLiveData(
|
||||
* getLiveData1(),
|
||||
* getLiveData2()
|
||||
* ) { data1, data2 ->
|
||||
* // Use datas[0], datas[1], ..., datas[N] to return a value
|
||||
* }
|
||||
*/
|
||||
class CombinedLiveData<R, A, B>(
|
||||
liveDataA: LiveData<A>,
|
||||
liveDataB: LiveData<B>,
|
||||
private val combine: (a: A?, b: B?) -> R
|
||||
) : MediatorLiveData<R>() {
|
||||
|
||||
private var a: A? = null
|
||||
private var b: B? = null
|
||||
|
||||
init {
|
||||
addSource(liveDataA) {
|
||||
a = it
|
||||
value = combine(a, b)
|
||||
}
|
||||
addSource(liveDataB) {
|
||||
b = it
|
||||
value = combine(a, b)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,10 @@
|
|||
package com.vanced.manager.model
|
||||
|
||||
enum class ButtonTag {
|
||||
INSTALL, UPDATE, REINSTALL
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.vanced.manager.R
|
||||
|
||||
enum class ButtonTag(@DrawableRes val image: Int) {
|
||||
INSTALL(R.drawable.ic_app_download),
|
||||
UPDATE(R.drawable.ic_app_update),
|
||||
REINSTALL(R.drawable.ic_app_reinstall)
|
||||
}
|
|
@ -5,63 +5,46 @@ import android.graphics.drawable.Drawable
|
|||
import android.os.Build
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.*
|
||||
import com.beust.klaxon.JsonObject
|
||||
import com.vanced.manager.R
|
||||
import com.vanced.manager.core.CombinedLiveData
|
||||
import com.vanced.manager.utils.PackageHelper.isPackageInstalled
|
||||
|
||||
open class DataModel(
|
||||
private val jsonObject: LiveData<JsonObject?>,
|
||||
private val context: Context,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
jsonObject: LiveData<JsonObject?>,
|
||||
context: Context,
|
||||
val appPkg: String,
|
||||
val appName: String,
|
||||
val appDescription: String,
|
||||
@DrawableRes val appIcon: Int
|
||||
) {
|
||||
|
||||
private val versionCode = MutableLiveData<Int>()
|
||||
private val installedVersionCode = MutableLiveData<Int>()
|
||||
val isAppInstalled = Transformations.map(jsonObject) { isAppInstalled(appPkg) }
|
||||
|
||||
private val versionCode = Transformations.map(jsonObject) { jobj ->
|
||||
jobj?.int("versionCode") ?: 0
|
||||
}
|
||||
private val installedVersionCode = Transformations.map(isAppInstalled) {
|
||||
getPkgVersionCode(appPkg, it)
|
||||
}
|
||||
private val unavailable = context.getString(R.string.unavailable)
|
||||
private val pm = context.packageManager
|
||||
|
||||
val isAppInstalled = MutableLiveData<Boolean>()
|
||||
val versionName = MutableLiveData<String>()
|
||||
val installedVersionName = MutableLiveData<String>()
|
||||
val buttonTag = MutableLiveData<ButtonTag>()
|
||||
val buttonImage = MutableLiveData<Drawable>()
|
||||
val changelog = MutableLiveData<String>()
|
||||
|
||||
private fun fetch() {
|
||||
val jobj = jsonObject.value
|
||||
isAppInstalled.value = isAppInstalled(appPkg)
|
||||
versionCode.value = jobj?.int("versionCode") ?: 0
|
||||
versionName.value = jobj?.string("version") ?: unavailable
|
||||
changelog.value = jobj?.string("changelog") ?: unavailable
|
||||
val versionName = Transformations.map(jsonObject) { jobj ->
|
||||
jobj?.string("version") ?: unavailable
|
||||
}
|
||||
val changelog = Transformations.map(jsonObject) { jobj ->
|
||||
jobj?.string("changelog") ?: unavailable
|
||||
}
|
||||
val installedVersionName = Transformations.map(isAppInstalled) {
|
||||
getPkgVersionName(appPkg, it)
|
||||
}
|
||||
val buttonTag = CombinedLiveData(versionCode, installedVersionCode) { versionCode, installedVersionCode ->
|
||||
compareInt(installedVersionCode, versionCode)
|
||||
}
|
||||
|
||||
init {
|
||||
fetch()
|
||||
with(lifecycleOwner) {
|
||||
jsonObject.observe(this) {
|
||||
fetch()
|
||||
}
|
||||
isAppInstalled.observe(this) {
|
||||
installedVersionCode.value = getPkgVersionCode(appPkg, it)
|
||||
installedVersionName.value = getPkgVersionName(appPkg, it)
|
||||
}
|
||||
versionCode.observe(this) { versionCode ->
|
||||
installedVersionCode.observe(this) { installedVersionCode ->
|
||||
buttonTag.value = compareInt(installedVersionCode, versionCode)
|
||||
buttonImage.value = compareIntDrawable(installedVersionCode, versionCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun isAppInstalled(pkg: String): Boolean = isPackageInstalled(pkg, context.packageManager)
|
||||
open fun isAppInstalled(pkg: String): Boolean = isPackageInstalled(pkg, pm)
|
||||
|
||||
private fun getPkgVersionName(pkg: String, isAppInstalled: Boolean): String {
|
||||
return if (isAppInstalled) {
|
||||
|
@ -95,16 +78,4 @@ open class DataModel(
|
|||
}
|
||||
return ButtonTag.INSTALL
|
||||
}
|
||||
|
||||
private fun compareIntDrawable(int1: Int?, int2: Int?): Drawable {
|
||||
if (int2 != null && int1 != null) {
|
||||
return when {
|
||||
int1 == 0 -> ContextCompat.getDrawable(context, R.drawable.ic_app_download)!!
|
||||
int2 > int1 -> ContextCompat.getDrawable(context, R.drawable.ic_app_update)!!
|
||||
int1 >= int2 -> ContextCompat.getDrawable(context, R.drawable.ic_app_reinstall)!!
|
||||
else -> ContextCompat.getDrawable(context, R.drawable.ic_app_download)!!
|
||||
}
|
||||
}
|
||||
return ContextCompat.getDrawable(context, R.drawable.ic_app_download)!!
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ import com.vanced.manager.utils.PackageHelper
|
|||
class RootDataModel(
|
||||
jsonObject: LiveData<JsonObject?>,
|
||||
context: Context,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
appPkg: String,
|
||||
appName: String,
|
||||
appDescription: String,
|
||||
|
@ -22,7 +21,7 @@ class RootDataModel(
|
|||
//Ironic, isn't it?
|
||||
private val scriptName: String?
|
||||
) : DataModel(
|
||||
jsonObject, context, lifecycleOwner, appPkg, appName, appDescription, appIcon
|
||||
jsonObject, context, appPkg, appName, appDescription, appIcon
|
||||
) {
|
||||
|
||||
override fun isAppInstalled(pkg: String): Boolean {
|
||||
|
|
|
@ -26,7 +26,6 @@ import com.vanced.manager.databinding.FragmentHomeBinding
|
|||
import com.vanced.manager.ui.dialogs.AppInfoDialog
|
||||
import com.vanced.manager.ui.dialogs.DialogContainer.installAlertBuilder
|
||||
import com.vanced.manager.ui.viewmodels.HomeViewModel
|
||||
import com.vanced.manager.ui.viewmodels.HomeViewModelFactory
|
||||
import com.vanced.manager.utils.isFetching
|
||||
import com.vanced.manager.utils.manager
|
||||
|
||||
|
@ -37,9 +36,7 @@ class HomeFragment : BindingFragment<FragmentHomeBinding>() {
|
|||
const val REFRESH_HOME = "REFRESH_HOME"
|
||||
}
|
||||
|
||||
private val viewModel: HomeViewModel by viewModels {
|
||||
HomeViewModelFactory(requireActivity())
|
||||
}
|
||||
private val viewModel: HomeViewModel by viewModels()
|
||||
|
||||
private val localBroadcastManager by lazy { LocalBroadcastManager.getInstance(requireActivity()) }
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package com.vanced.manager.ui.viewmodels
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
|
||||
import com.vanced.manager.R
|
||||
|
@ -38,12 +39,11 @@ import com.vanced.manager.utils.PackageHelper.uninstallRootApk
|
|||
import com.vanced.manager.utils.PackageHelper.vancedInstallFilesExist
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
//TODO fix leak
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
class HomeViewModel(private val activity: FragmentActivity) : ViewModel() {
|
||||
class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private val prefs = getDefaultSharedPreferences(activity)
|
||||
private val prefs = getDefaultSharedPreferences(context)
|
||||
private val variant get() = prefs.getString("vanced_variant", "nonroot")
|
||||
private val context: Context get() = getApplication()
|
||||
|
||||
val vancedModel = MutableLiveData<DataModel>()
|
||||
val vancedRootModel = MutableLiveData<RootDataModel>()
|
||||
|
@ -54,13 +54,13 @@ class HomeViewModel(private val activity: FragmentActivity) : ViewModel() {
|
|||
|
||||
fun fetchData() {
|
||||
viewModelScope.launch {
|
||||
loadJson(activity)
|
||||
loadJson(context)
|
||||
}
|
||||
}
|
||||
|
||||
private val microgToast = Toast.makeText(activity, R.string.no_microg, Toast.LENGTH_LONG)
|
||||
private val microgToast = Toast.makeText(context, R.string.no_microg, Toast.LENGTH_LONG)
|
||||
|
||||
fun openUrl(url: String) {
|
||||
fun openUrl(context: Context, url: String) {
|
||||
val color: Int =
|
||||
when (url) {
|
||||
DISCORD -> R.color.Discord
|
||||
|
@ -71,82 +71,82 @@ class HomeViewModel(private val activity: FragmentActivity) : ViewModel() {
|
|||
else -> R.color.Vanced
|
||||
}
|
||||
|
||||
openUrl(url, color, activity)
|
||||
openUrl(url, color, context)
|
||||
}
|
||||
|
||||
fun launchApp(app: String, isRoot: Boolean) {
|
||||
val componentName = when (app) {
|
||||
activity.getString(R.string.vanced) -> if (isRoot) ComponentName(
|
||||
context.getString(R.string.vanced) -> if (isRoot) ComponentName(
|
||||
vancedRootPkg,
|
||||
"$vancedRootPkg.HomeActivity"
|
||||
) else ComponentName(vancedPkg, "$vancedRootPkg.HomeActivity")
|
||||
activity.getString(R.string.music) -> if (isRoot) ComponentName(
|
||||
context.getString(R.string.music) -> if (isRoot) ComponentName(
|
||||
musicRootPkg,
|
||||
"$musicRootPkg.activities.MusicActivity"
|
||||
) else ComponentName(musicPkg, "$musicRootPkg.activities.MusicActivity")
|
||||
activity.getString(R.string.microg) -> ComponentName(
|
||||
context.getString(R.string.microg) -> ComponentName(
|
||||
microgPkg,
|
||||
"org.microg.gms.ui.SettingsActivity"
|
||||
)
|
||||
else -> throw IllegalArgumentException("Can't open this app")
|
||||
}
|
||||
try {
|
||||
activity.startActivity(Intent().setComponent(componentName))
|
||||
context.startActivity(Intent().setComponent(componentName))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
log("VMHMV", e.toString())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun openInstallDialog(buttonTag: ButtonTag?, app: String) {
|
||||
if (variant == "nonroot" && app != activity.getString(R.string.microg) && !microgModel.value?.isAppInstalled?.value!!) {
|
||||
fun openInstallDialog(fragmentManager: FragmentManager, buttonTag: ButtonTag?, app: String) {
|
||||
if (variant == "nonroot" && app != context.getString(R.string.microg) && !microgModel.value?.isAppInstalled?.value!!) {
|
||||
microgToast.show()
|
||||
return
|
||||
}
|
||||
|
||||
if (buttonTag == ButtonTag.UPDATE) {
|
||||
when (app) {
|
||||
activity.getString(R.string.vanced) -> VancedPreferencesDialog().show(activity)
|
||||
activity.getString(R.string.music) -> MusicPreferencesDialog().show(activity)
|
||||
else -> AppDownloadDialog.newInstance(app).show(activity)
|
||||
context.getString(R.string.vanced) -> VancedPreferencesDialog().show(fragmentManager)
|
||||
context.getString(R.string.music) -> MusicPreferencesDialog().show(fragmentManager)
|
||||
else -> AppDownloadDialog.newInstance(app).show(fragmentManager)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
when (app) {
|
||||
activity.getString(R.string.vanced) -> {
|
||||
context.getString(R.string.vanced) -> {
|
||||
when (variant) {
|
||||
"nonroot" -> {
|
||||
if (vancedInstallFilesExist(activity)) {
|
||||
InstallationFilesDetectedDialog.newInstance(app).show(activity)
|
||||
if (vancedInstallFilesExist(context)) {
|
||||
InstallationFilesDetectedDialog.newInstance(app).show(fragmentManager)
|
||||
} else {
|
||||
VancedPreferencesDialog().show(activity)
|
||||
VancedPreferencesDialog().show(fragmentManager)
|
||||
}
|
||||
}
|
||||
"root" -> {
|
||||
VancedPreferencesDialog().show(activity)
|
||||
VancedPreferencesDialog().show(fragmentManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
activity.getString(R.string.music) -> {
|
||||
context.getString(R.string.music) -> {
|
||||
when (variant) {
|
||||
"nonroot" -> {
|
||||
if (musicApkExists(activity)) {
|
||||
InstallationFilesDetectedDialog.newInstance(app).show(activity)
|
||||
if (musicApkExists(context)) {
|
||||
InstallationFilesDetectedDialog.newInstance(app).show(fragmentManager)
|
||||
} else {
|
||||
MusicPreferencesDialog().show(activity)
|
||||
MusicPreferencesDialog().show(fragmentManager)
|
||||
}
|
||||
}
|
||||
"root" -> {
|
||||
MusicPreferencesDialog().show(activity)
|
||||
MusicPreferencesDialog().show(fragmentManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
activity.getString(R.string.microg) -> {
|
||||
if (apkExist(activity, "microg.apk")) {
|
||||
InstallationFilesDetectedDialog.newInstance(app).show(activity)
|
||||
context.getString(R.string.microg) -> {
|
||||
if (apkExist(context, "microg.apk")) {
|
||||
InstallationFilesDetectedDialog.newInstance(app).show(fragmentManager)
|
||||
} else {
|
||||
AppDownloadDialog.newInstance(app).show(activity)
|
||||
AppDownloadDialog.newInstance(app).show(fragmentManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,32 +155,30 @@ class HomeViewModel(private val activity: FragmentActivity) : ViewModel() {
|
|||
|
||||
fun uninstallPackage(pkg: String) {
|
||||
if (variant == "root" && uninstallRootApk(pkg)) {
|
||||
viewModelScope.launch { loadJson(activity) }
|
||||
viewModelScope.launch { loadJson(context) }
|
||||
} else {
|
||||
uninstallApk(pkg, activity)
|
||||
uninstallApk(pkg, context)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
with(activity) {
|
||||
with(context) {
|
||||
if (variant == "root") {
|
||||
vancedRootModel.value = RootDataModel(
|
||||
vanced,
|
||||
this,
|
||||
this,
|
||||
vancedRootPkg,
|
||||
this.getString(R.string.vanced),
|
||||
activity.getString(R.string.description_vanced),
|
||||
this.getString(R.string.description_vanced),
|
||||
R.drawable.ic_vanced,
|
||||
"vanced"
|
||||
)
|
||||
musicRootModel.value = RootDataModel(
|
||||
music,
|
||||
this,
|
||||
this,
|
||||
musicRootPkg,
|
||||
this.getString(R.string.music),
|
||||
activity.getString(R.string.description_vanced_music),
|
||||
this.getString(R.string.description_vanced_music),
|
||||
R.drawable.ic_music,
|
||||
"music"
|
||||
)
|
||||
|
@ -188,35 +186,31 @@ class HomeViewModel(private val activity: FragmentActivity) : ViewModel() {
|
|||
vancedModel.value = DataModel(
|
||||
vanced,
|
||||
this,
|
||||
this,
|
||||
vancedPkg,
|
||||
this.getString(R.string.vanced),
|
||||
activity.getString(R.string.description_vanced),
|
||||
this.getString(R.string.description_vanced),
|
||||
R.drawable.ic_vanced
|
||||
)
|
||||
musicModel.value = DataModel(
|
||||
music,
|
||||
this,
|
||||
this,
|
||||
musicPkg,
|
||||
this.getString(R.string.music),
|
||||
activity.getString(R.string.description_vanced_music),
|
||||
this.getString(R.string.description_vanced_music),
|
||||
R.drawable.ic_music
|
||||
)
|
||||
microgModel.value = DataModel(
|
||||
microg,
|
||||
this,
|
||||
this,
|
||||
microgPkg,
|
||||
this.getString(R.string.microg),
|
||||
activity.getString(R.string.description_microg),
|
||||
this.getString(R.string.description_microg),
|
||||
R.drawable.ic_microg
|
||||
)
|
||||
}
|
||||
managerModel.value = DataModel(
|
||||
manager,
|
||||
this,
|
||||
this,
|
||||
managerPkg,
|
||||
this.getString(R.string.app_name),
|
||||
"Just manager meh",
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
package com.vanced.manager.ui.viewmodels
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
||||
class HomeViewModelFactory(private val activity: FragmentActivity) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return HomeViewModel(activity) as T
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ import android.widget.RadioGroup
|
|||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
|
@ -22,14 +23,18 @@ val RadioGroup.checkedButtonTag: String?
|
|||
checkedRadioButtonId
|
||||
)?.tag?.toString()
|
||||
|
||||
fun DialogFragment.show(activity: FragmentActivity) {
|
||||
fun DialogFragment.show(fragmentManager: FragmentManager) {
|
||||
try {
|
||||
show(activity.supportFragmentManager, "")
|
||||
show(fragmentManager, "")
|
||||
} catch (e: Exception) {
|
||||
log("VMUI", e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
|
||||
fun DialogFragment.show(activity: FragmentActivity) {
|
||||
show(activity.supportFragmentManager)
|
||||
}
|
||||
|
||||
fun List<String>.convertToAppVersions(): List<String> = listOf("latest") + reversed()
|
||||
|
||||
fun String.formatVersion(context: Context): String =
|
||||
|
|
Loading…
Reference in New Issue