aboutsummaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
authorxbzk <xbzk@eden-emu.dev>2026-03-08 00:41:40 +0100
committercrueter <crueter@eden-emu.dev>2026-03-08 00:41:40 +0100
commit9423a33fc2192a6ff1fb613468c764f82d8d1ec7 (patch)
tree9ae84e382142498351a1b129c2e626164e455ddb /src/android
parentafec66f598d2327946b869a1b744e4b4cbc1066c (diff)
[android,ui] fix addons list not refresh upon rotation (#3687)
Fixes a bug reported by Pavel, in which an RC as returning empty patchList after phone rotated. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3687 Reviewed-by: DraVee <chimera@dravee.dev> Reviewed-by: MaranBr <maranbr@eden-emu.dev> Co-authored-by: xbzk <xbzk@eden-emu.dev> Co-committed-by: xbzk <xbzk@eden-emu.dev>
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt76
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt2
3 files changed, 67 insertions, 24 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
index 573549d84b..96b7a8cce2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -19,7 +19,6 @@ import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
-import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.AddonAdapter
import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding
@@ -42,7 +41,7 @@ class AddonsFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- addonViewModel.onOpenAddons(args.game)
+ addonViewModel.onAddonsViewCreated(args.game)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
@@ -122,12 +121,14 @@ class AddonsFragment : Fragment() {
override fun onResume() {
super.onResume()
- addonViewModel.refreshAddons()
+ addonViewModel.onAddonsViewStarted(args.game)
}
override fun onDestroy() {
+ if (activity?.isChangingConfigurations != true) {
+ addonViewModel.onCloseAddons()
+ }
super.onDestroy()
- addonViewModel.onCloseAddons()
}
val installAddon =
@@ -167,7 +168,7 @@ class AddonsFragment : Fragment() {
} catch (_: Exception) {
return@newInstance errorMessage
}
- addonViewModel.refreshAddons()
+ addonViewModel.refreshAddons(force = true)
return@newInstance getString(R.string.addon_installed_successfully)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
} else {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt
index 2a0e72be26..2331630c4e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt
@@ -18,7 +18,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
import java.util.concurrent.atomic.AtomicBoolean
class AddonViewModel : ViewModel() {
- private val _patchList = MutableStateFlow(mutableListOf<Patch>())
+ private val _patchList = MutableStateFlow<List<Patch>>(emptyList())
val addonList get() = _patchList.asStateFlow()
private val _showModInstallPicker = MutableStateFlow(false)
@@ -31,34 +31,62 @@ class AddonViewModel : ViewModel() {
val addonToDelete = _addonToDelete.asStateFlow()
var game: Game? = null
+ private var loadedGameKey: String? = null
private val isRefreshing = AtomicBoolean(false)
+ private val pendingRefresh = AtomicBoolean(false)
- fun onOpenAddons(game: Game) {
+ fun onAddonsViewCreated(game: Game) {
this.game = game
- refreshAddons()
+ refreshAddons(commitEmpty = false)
}
- fun refreshAddons() {
- if (isRefreshing.get() || game == null) {
+ fun onAddonsViewStarted(game: Game) {
+ this.game = game
+ val hasLoadedCurrentGame = loadedGameKey == gameKey(game)
+ refreshAddons(force = !hasLoadedCurrentGame)
+ }
+
+ fun refreshAddons(force: Boolean = false, commitEmpty: Boolean = true) {
+ val currentGame = game ?: return
+ val currentGameKey = gameKey(currentGame)
+ if (!force && loadedGameKey == currentGameKey) {
+ return
+ }
+ if (!isRefreshing.compareAndSet(false, true)) {
+ if (force) {
+ pendingRefresh.set(true)
+ }
return
}
- isRefreshing.set(true)
+
viewModelScope.launch {
- withContext(Dispatchers.IO) {
- val patchList = (
- NativeLibrary.getPatchesForFile(game!!.path, game!!.programId)
- ?: emptyArray()
- ).toMutableList()
+ try {
+ val patches = withContext(Dispatchers.IO) {
+ NativeLibrary.getPatchesForFile(currentGame.path, currentGame.programId)
+ } ?: return@launch
+
+ val patchList = patches.toMutableList()
patchList.sortBy { it.name }
// Ensure only one update is enabled
ensureSingleUpdateEnabled(patchList)
removeDuplicates(patchList)
+ if (patchList.isEmpty() && !commitEmpty) {
+ return@launch
+ }
+ if (gameKey(game ?: return@launch) != currentGameKey) {
+ return@launch
+ }
- _patchList.value = patchList
+ _patchList.value = patchList.toList()
+ loadedGameKey = currentGameKey
+ } finally {
isRefreshing.set(false)
+ if (pendingRefresh.compareAndSet(true, false)) {
+ refreshAddons(force = true)
+ }
}
}
}
@@ -119,17 +147,26 @@ class AddonViewModel : ViewModel() {
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
}
- refreshAddons()
+ refreshAddons(force = true)
}
fun onCloseAddons() {
- if (_patchList.value.isEmpty()) {
+ val currentGame = game ?: run {
+ _patchList.value = emptyList()
+ loadedGameKey = null
+ return
+ }
+ val currentList = _patchList.value
+ if (currentList.isEmpty()) {
+ _patchList.value = emptyList()
+ loadedGameKey = null
+ game = null
return
}
NativeConfig.setDisabledAddons(
- game!!.programId,
- _patchList.value.mapNotNull {
+ currentGame.programId,
+ currentList.mapNotNull {
if (it.enabled) {
null
} else {
@@ -148,7 +185,8 @@ class AddonViewModel : ViewModel() {
}.toTypedArray()
)
NativeConfig.saveGlobalConfig()
- _patchList.value.clear()
+ _patchList.value = emptyList()
+ loadedGameKey = null
game = null
}
@@ -159,4 +197,8 @@ class AddonViewModel : ViewModel() {
fun showModNoticeDialog(show: Boolean) {
_showModNoticeDialog.value = show
}
+
+ private fun gameKey(game: Game): String {
+ return "${game.programId}|${game.path}"
+ }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index db4cc0f60e..74a171cf1f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -642,7 +642,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
- addonViewModel.refreshAddons()
+ addonViewModel.refreshAddons(force = true)
val separator = System.lineSeparator() ?: "\n"
val installResult = StringBuilder()