From 4a3cbf00216918a53b143f6bbfc4b22a49761770 Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Thu, 31 Aug 2023 13:32:48 -0400
Subject: [PATCH 1/2] android: Use StateFlow instead of LiveData

---
 .../yuzu_emu/adapters/HomeSettingAdapter.kt   |  10 +-
 .../features/settings/ui/SettingsActivity.kt  |  53 ++++++---
 .../features/settings/ui/SettingsFragment.kt  |  43 ++++---
 .../yuzu_emu/fragments/EmulationFragment.kt   | 110 +++++++++++-------
 .../IndeterminateProgressDialogFragment.kt    |  32 +++--
 .../yuzu/yuzu_emu/fragments/SearchFragment.kt |  46 +++++---
 .../fragments/SettingsSearchFragment.kt       |  16 ++-
 .../yuzu/yuzu_emu/fragments/SetupFragment.kt  |  16 ++-
 .../yuzu/yuzu_emu/model/EmulationViewModel.kt |  44 ++++---
 .../org/yuzu/yuzu_emu/model/GamesViewModel.kt |  44 +++----
 .../org/yuzu/yuzu_emu/model/HomeSetting.kt    |   6 +-
 .../org/yuzu/yuzu_emu/model/HomeViewModel.kt  |  28 ++---
 .../yuzu/yuzu_emu/model/SettingsViewModel.kt  |  57 ++++-----
 .../org/yuzu/yuzu_emu/model/TaskViewModel.kt  |  27 ++---
 .../org/yuzu/yuzu_emu/ui/GamesFragment.kt     |  66 +++++++----
 .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt |  20 +++-
 16 files changed, 368 insertions(+), 250 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
index 8d87d3bd72..1675627a10 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -10,8 +10,12 @@ import android.view.ViewGroup
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.content.ContextCompat
 import androidx.core.content.res.ResourcesCompat
+import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
 import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@@ -86,7 +90,11 @@ class HomeSettingAdapter(
                 binding.optionIcon.alpha = 0.5f
             }
 
-            option.details.observe(viewLifecycle) { updateOptionDetails(it) }
+            viewLifecycle.lifecycleScope.launch {
+                viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    option.details.collect { updateOptionDetails(it) }
+                }
+            }
             binding.optionDetail.postDelayed(
                 {
                     binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index 908c01265c..4d2f2f604f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import androidx.navigation.fragment.NavHostFragment
 import androidx.navigation.navArgs
 import com.google.android.material.color.MaterialColors
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
 import java.io.IOException
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {
             )
         }
 
-        settingsViewModel.shouldRecreate.observe(this) {
-            if (it) {
-                settingsViewModel.setShouldRecreate(false)
-                recreate()
+        lifecycleScope.apply {
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    settingsViewModel.shouldRecreate.collectLatest {
+                        if (it) {
+                            settingsViewModel.setShouldRecreate(false)
+                            recreate()
+                        }
+                    }
+                }
             }
-        }
-        settingsViewModel.shouldNavigateBack.observe(this) {
-            if (it) {
-                settingsViewModel.setShouldNavigateBack(false)
-                navigateBack()
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    settingsViewModel.shouldNavigateBack.collectLatest {
+                        if (it) {
+                            settingsViewModel.setShouldNavigateBack(false)
+                            navigateBack()
+                        }
+                    }
+                }
             }
-        }
-        settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
-            if (it) {
-                settingsViewModel.setShouldShowResetSettingsDialog(false)
-                ResetSettingsDialogFragment().show(
-                    supportFragmentManager,
-                    ResetSettingsDialogFragment.TAG
-                )
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
+                        if (it) {
+                            settingsViewModel.setShouldShowResetSettingsDialog(false)
+                            ResetSettingsDialogFragment().show(
+                                supportFragmentManager,
+                                ResetSettingsDialogFragment.TAG
+                            )
+                        }
+                    }
+                }
             }
         }
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index bc319714c7..2a816183ab 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -13,11 +13,15 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import androidx.navigation.findNavController
 import androidx.navigation.fragment.navArgs
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.divider.MaterialDividerItemDecoration
 import com.google.android.material.transition.MaterialSharedAxis
+import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
@@ -51,6 +55,8 @@ class SettingsFragment : Fragment() {
         return binding.root
     }
 
+    // This is using the correct scope, lint is just acting up
+    @SuppressLint("UnsafeRepeatOnLifecycleDetector")
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         settingsAdapter = SettingsAdapter(this, requireContext())
         presenter = SettingsFragmentPresenter(
@@ -75,24 +81,27 @@ class SettingsFragment : Fragment() {
             settingsViewModel.setShouldNavigateBack(true)
         }
 
-        settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
-            if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
-        }
-
-        settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
-            if (it) {
-                settingsViewModel.setShouldReloadSettingsList(false)
-                presenter.loadSettingsList()
+        viewLifecycleOwner.lifecycleScope.apply {
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    settingsViewModel.shouldReloadSettingsList.collectLatest {
+                        if (it) {
+                            settingsViewModel.setShouldReloadSettingsList(false)
+                            presenter.loadSettingsList()
+                        }
+                    }
+                }
             }
-        }
-
-        settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
-            if (it) {
-                reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
-                exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
-            } else {
-                reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
-                exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+            launch {
+                settingsViewModel.isUsingSearch.collectLatest {
+                    if (it) {
+                        reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+                        exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+                    } else {
+                        reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+                        exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+                    }
+                }
             }
         }
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 944ae652e4..1addb23265 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.slider.Slider
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
@@ -129,6 +130,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         return binding.root
     }
 
+    // This is using the correct scope, lint is just acting up
+    @SuppressLint("UnsafeRepeatOnLifecycleDetector")
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         binding.surfaceEmulation.holder.addCallback(this)
         binding.showFpsText.setTextColor(Color.YELLOW)
@@ -205,59 +208,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
             }
         )
 
-        viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
-            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
-                WindowInfoTracker.getOrCreate(requireContext())
-                    .windowLayoutInfo(requireActivity())
-                    .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
-            }
-        }
-
         GameIconUtils.loadGameIcon(game, binding.loadingImage)
         binding.loadingTitle.text = game.title
         binding.loadingTitle.isSelected = true
         binding.loadingText.isSelected = true
 
-        emulationViewModel.shaderProgress.observe(viewLifecycleOwner) {
-            if (it > 0 && it != emulationViewModel.totalShaders.value!!) {
-                binding.loadingProgressIndicator.isIndeterminate = false
-
-                if (it < binding.loadingProgressIndicator.max) {
-                    binding.loadingProgressIndicator.progress = it
+        viewLifecycleOwner.lifecycleScope.apply {
+            launch {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    WindowInfoTracker.getOrCreate(requireContext())
+                        .windowLayoutInfo(requireActivity())
+                        .collect {
+                            updateFoldableLayout(requireActivity() as EmulationActivity, it)
+                        }
                 }
             }
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    emulationViewModel.shaderProgress.collectLatest {
+                        if (it > 0 && it != emulationViewModel.totalShaders.value) {
+                            binding.loadingProgressIndicator.isIndeterminate = false
 
-            if (it == emulationViewModel.totalShaders.value!!) {
-                binding.loadingText.setText(R.string.loading)
-                binding.loadingProgressIndicator.isIndeterminate = true
+                            if (it < binding.loadingProgressIndicator.max) {
+                                binding.loadingProgressIndicator.progress = it
+                            }
+                        }
+
+                        if (it == emulationViewModel.totalShaders.value) {
+                            binding.loadingText.setText(R.string.loading)
+                            binding.loadingProgressIndicator.isIndeterminate = true
+                        }
+                    }
+                }
             }
-        }
-        emulationViewModel.totalShaders.observe(viewLifecycleOwner) {
-            binding.loadingProgressIndicator.max = it
-        }
-        emulationViewModel.shaderMessage.observe(viewLifecycleOwner) {
-            if (it.isNotEmpty()) {
-                binding.loadingText.text = it
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    emulationViewModel.totalShaders.collectLatest {
+                        binding.loadingProgressIndicator.max = it
+                    }
+                }
             }
-        }
-
-        emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started ->
-            if (started) {
-                binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
-                ViewUtils.showView(binding.surfaceInputOverlay)
-                ViewUtils.hideView(binding.loadingIndicator)
-
-                // Setup overlay
-                updateShowFpsOverlay()
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    emulationViewModel.shaderMessage.collectLatest {
+                        if (it.isNotEmpty()) {
+                            binding.loadingText.text = it
+                        }
+                    }
+                }
             }
-        }
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    emulationViewModel.emulationStarted.collectLatest {
+                        if (it) {
+                            binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
+                            ViewUtils.showView(binding.surfaceInputOverlay)
+                            ViewUtils.hideView(binding.loadingIndicator)
 
-        emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) {
-            if (it) {
-                binding.loadingText.setText(R.string.shutting_down)
-                ViewUtils.showView(binding.loadingIndicator)
-                ViewUtils.hideView(binding.inputContainer)
-                ViewUtils.hideView(binding.showFpsText)
+                            // Setup overlay
+                            updateShowFpsOverlay()
+                        }
+                    }
+                }
+            }
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    emulationViewModel.isEmulationStopping.collectLatest {
+                        if (it) {
+                            binding.loadingText.setText(R.string.shutting_down)
+                            ViewUtils.showView(binding.loadingIndicator)
+                            ViewUtils.hideView(binding.inputContainer)
+                            ViewUtils.hideView(binding.showFpsText)
+                        }
+                    }
+                }
             }
         }
     }
@@ -274,9 +298,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                 }
             }
         } else {
-            if (EmulationMenuSettings.showOverlay &&
-                emulationViewModel.emulationStarted.value == true
-            ) {
+            if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
                 binding.surfaceInputOverlay.post {
                     binding.surfaceInputOverlay.visibility = View.VISIBLE
                 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
index 181bd983af..ea8eb073ac 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -9,8 +9,12 @@ import android.widget.Toast
 import androidx.appcompat.app.AppCompatActivity
 import androidx.fragment.app.DialogFragment
 import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
 import org.yuzu.yuzu_emu.model.TaskViewModel
 
@@ -28,21 +32,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
             .create()
         dialog.setCanceledOnTouchOutside(false)
 
-        taskViewModel.isComplete.observe(this) { complete ->
-            if (complete) {
-                dialog.dismiss()
-                when (val result = taskViewModel.result.value) {
-                    is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show()
-                    is MessageDialogFragment -> result.show(
-                        requireActivity().supportFragmentManager,
-                        MessageDialogFragment.TAG
-                    )
+        viewLifecycleOwner.lifecycleScope.launch {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                taskViewModel.isComplete.collect {
+                    if (it) {
+                        dialog.dismiss()
+                        when (val result = taskViewModel.result.value) {
+                            is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
+                                .show()
+
+                            is MessageDialogFragment -> result.show(
+                                requireActivity().supportFragmentManager,
+                                MessageDialogFragment.TAG
+                            )
+                        }
+                        taskViewModel.clear()
+                    }
                 }
-                taskViewModel.clear()
             }
         }
 
-        if (taskViewModel.isRunning.value == false) {
+        if (!taskViewModel.isRunning.value) {
             taskViewModel.runTask()
         }
         return dialog
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
index f54dccc69d..2dbca76a59 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -3,6 +3,7 @@
 
 package org.yuzu.yuzu_emu.fragments
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.content.SharedPreferences
 import android.os.Bundle
@@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
 import androidx.core.widget.doOnTextChanged
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import androidx.preference.PreferenceManager
 import info.debatty.java.stringsimilarity.Jaccard
 import info.debatty.java.stringsimilarity.JaroWinkler
+import kotlinx.coroutines.launch
 import java.util.Locale
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
@@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
         return binding.root
     }
 
+    // This is using the correct scope, lint is just acting up
+    @SuppressLint("UnsafeRepeatOnLifecycleDetector")
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         homeViewModel.setNavigationVisibility(visible = true, animated = false)
         preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
             filterAndSearch()
         }
 
-        gamesViewModel.apply {
-            searchFocused.observe(viewLifecycleOwner) { searchFocused ->
-                if (searchFocused) {
-                    focusSearch()
-                    gamesViewModel.setSearchFocused(false)
+        viewLifecycleOwner.lifecycleScope.apply {
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    gamesViewModel.searchFocused.collect {
+                        if (it) {
+                            focusSearch()
+                            gamesViewModel.setSearchFocused(false)
+                        }
+                    }
                 }
             }
-
-            games.observe(viewLifecycleOwner) { filterAndSearch() }
-            searchedGames.observe(viewLifecycleOwner) {
-                (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
-                if (it.isEmpty()) {
-                    binding.noResultsView.visibility = View.VISIBLE
-                } else {
-                    binding.noResultsView.visibility = View.GONE
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    gamesViewModel.games.collect { filterAndSearch() }
+                }
+            }
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    gamesViewModel.searchedGames.collect {
+                        (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
+                        if (it.isEmpty()) {
+                            binding.noResultsView.visibility = View.VISIBLE
+                        } else {
+                            binding.noResultsView.visibility = View.GONE
+                        }
+                    }
                 }
             }
         }
@@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
     private inner class ScoredGame(val score: Double, val item: Game)
 
     private fun filterAndSearch() {
-        val baseList = gamesViewModel.games.value!!
+        val baseList = gamesViewModel.games.value
         val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
             R.id.chip_recently_played -> {
                 baseList.filter {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
index 55b6a0367b..9d0594c6ed 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
@@ -15,10 +15,14 @@ import androidx.core.view.updatePadding
 import androidx.core.widget.doOnTextChanged
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.divider.MaterialDividerItemDecoration
 import com.google.android.material.transition.MaterialSharedAxis
 import info.debatty.java.stringsimilarity.Cosine
+import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
 import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
@@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
             search()
             binding.settingsList.smoothScrollToPosition(0)
         }
-        settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
-            if (it) {
-                settingsViewModel.setShouldReloadSettingsList(false)
-                search()
+        viewLifecycleOwner.lifecycleScope.launch {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                settingsViewModel.shouldReloadSettingsList.collect {
+                    if (it) {
+                        settingsViewModel.setShouldReloadSettingsList(false)
+                        search()
+                    }
+                }
             }
         }
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index d50c421a01..fbb2f6e18d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -22,10 +22,14 @@ import androidx.core.view.isVisible
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import androidx.navigation.findNavController
 import androidx.preference.PreferenceManager
 import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
 import com.google.android.material.transition.MaterialFadeThrough
+import kotlinx.coroutines.launch
 import java.io.File
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
@@ -206,10 +210,14 @@ class SetupFragment : Fragment() {
             )
         }
 
-        homeViewModel.shouldPageForward.observe(viewLifecycleOwner) {
-            if (it) {
-                pageForward()
-                homeViewModel.setShouldPageForward(false)
+        viewLifecycleOwner.lifecycleScope.launch {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                homeViewModel.shouldPageForward.collect {
+                    if (it) {
+                        pageForward()
+                        homeViewModel.setShouldPageForward(false)
+                    }
+                }
             }
         }
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
index e35f51bc3a..f34870c2d9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
@@ -3,28 +3,28 @@
 
 package org.yuzu.yuzu_emu.model
 
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 
 class EmulationViewModel : ViewModel() {
-    private val _emulationStarted = MutableLiveData(false)
-    val emulationStarted: LiveData<Boolean> get() = _emulationStarted
+    val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
+    private val _emulationStarted = MutableStateFlow(false)
 
-    private val _isEmulationStopping = MutableLiveData(false)
-    val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping
+    val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
+    private val _isEmulationStopping = MutableStateFlow(false)
 
-    private val _shaderProgress = MutableLiveData(0)
-    val shaderProgress: LiveData<Int> get() = _shaderProgress
+    val shaderProgress: StateFlow<Int> get() = _shaderProgress
+    private val _shaderProgress = MutableStateFlow(0)
 
-    private val _totalShaders = MutableLiveData(0)
-    val totalShaders: LiveData<Int> get() = _totalShaders
+    val totalShaders: StateFlow<Int> get() = _totalShaders
+    private val _totalShaders = MutableStateFlow(0)
 
-    private val _shaderMessage = MutableLiveData("")
-    val shaderMessage: LiveData<String> get() = _shaderMessage
+    val shaderMessage: StateFlow<String> get() = _shaderMessage
+    private val _shaderMessage = MutableStateFlow("")
 
     fun setEmulationStarted(started: Boolean) {
-        _emulationStarted.postValue(started)
+        _emulationStarted.value = started
     }
 
     fun setIsEmulationStopping(value: Boolean) {
@@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {
     }
 
     fun clear() {
-        _emulationStarted.value = false
-        _isEmulationStopping.value = false
-        _shaderProgress.value = 0
-        _totalShaders.value = 0
-        _shaderMessage.value = ""
+        setEmulationStarted(false)
+        setIsEmulationStopping(false)
+        setShaderProgress(0)
+        setTotalShaders(0)
+        setShaderMessage("")
+    }
+
+    companion object {
+        const val KEY_EMULATION_STARTED = "EmulationStarted"
+        const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
+        const val KEY_SHADER_PROGRESS = "ShaderProgress"
+        const val KEY_TOTAL_SHADERS = "TotalShaders"
+        const val KEY_SHADER_MESSAGE = "ShaderMessage"
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 1fe42f9229..6e09fa81d2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model
 
 import android.net.Uri
 import androidx.documentfile.provider.DocumentFile
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import androidx.preference.PreferenceManager
 import java.util.Locale
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import kotlinx.serialization.ExperimentalSerializationApi
@@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
 
 @OptIn(ExperimentalSerializationApi::class)
 class GamesViewModel : ViewModel() {
-    private val _games = MutableLiveData<List<Game>>(emptyList())
-    val games: LiveData<List<Game>> get() = _games
+    val games: StateFlow<List<Game>> get() = _games
+    private val _games = MutableStateFlow(emptyList<Game>())
 
-    private val _searchedGames = MutableLiveData<List<Game>>(emptyList())
-    val searchedGames: LiveData<List<Game>> get() = _searchedGames
+    val searchedGames: StateFlow<List<Game>> get() = _searchedGames
+    private val _searchedGames = MutableStateFlow(emptyList<Game>())
 
-    private val _isReloading = MutableLiveData(false)
-    val isReloading: LiveData<Boolean> get() = _isReloading
+    val isReloading: StateFlow<Boolean> get() = _isReloading
+    private val _isReloading = MutableStateFlow(false)
 
-    private val _shouldSwapData = MutableLiveData(false)
-    val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData
+    val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
+    private val _shouldSwapData = MutableStateFlow(false)
 
-    private val _shouldScrollToTop = MutableLiveData(false)
-    val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop
+    val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
+    private val _shouldScrollToTop = MutableStateFlow(false)
 
-    private val _searchFocused = MutableLiveData(false)
-    val searchFocused: LiveData<Boolean> get() = _searchFocused
+    val searchFocused: StateFlow<Boolean> get() = _searchFocused
+    private val _searchFocused = MutableStateFlow(false)
 
     init {
         // Ensure keys are loaded so that ROM metadata can be decrypted.
@@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
             )
         )
 
-        _games.postValue(sortedList)
+        _games.value = sortedList
     }
 
     fun setSearchedGames(games: List<Game>) {
-        _searchedGames.postValue(games)
+        _searchedGames.value = games
     }
 
     fun setShouldSwapData(shouldSwap: Boolean) {
-        _shouldSwapData.postValue(shouldSwap)
+        _shouldSwapData.value = shouldSwap
     }
 
     fun setShouldScrollToTop(shouldScroll: Boolean) {
-        _shouldScrollToTop.postValue(shouldScroll)
+        _shouldScrollToTop.value = shouldScroll
     }
 
     fun setSearchFocused(searchFocused: Boolean) {
-        _searchFocused.postValue(searchFocused)
+        _searchFocused.value = searchFocused
     }
 
     fun reloadGames(directoryChanged: Boolean) {
-        if (isReloading.value == true) {
+        if (isReloading.value) {
             return
         }
-        _isReloading.postValue(true)
+        _isReloading.value = true
 
         viewModelScope.launch {
             withContext(Dispatchers.IO) {
                 NativeLibrary.resetRomMetadata()
                 setGames(GameHelper.getGames())
-                _isReloading.postValue(false)
+                _isReloading.value = false
 
                 if (directoryChanged) {
                     setShouldSwapData(true)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
index 498c222fa9..b32e193738 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
@@ -3,8 +3,8 @@
 
 package org.yuzu.yuzu_emu.model
 
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 
 data class HomeSetting(
     val titleId: Int,
@@ -14,5 +14,5 @@ data class HomeSetting(
     val isEnabled: () -> Boolean = { true },
     val disabledTitleId: Int = 0,
     val disabledMessageId: Int = 0,
-    val details: LiveData<String> = MutableLiveData("")
+    val details: StateFlow<String> = MutableStateFlow("")
 )
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index a48ef7a88a..756f767216 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model
 
 import android.net.Uri
 import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.preference.PreferenceManager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.utils.GameHelper
 
 class HomeViewModel : ViewModel() {
-    private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
-    val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible
+    val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
+    private val _navigationVisible = MutableStateFlow(Pair(false, false))
 
-    private val _statusBarShadeVisible = MutableLiveData(true)
-    val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
+    val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
+    private val _statusBarShadeVisible = MutableStateFlow(true)
 
-    private val _shouldPageForward = MutableLiveData(false)
-    val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
+    val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
+    private val _shouldPageForward = MutableStateFlow(false)
 
-    private val _gamesDir = MutableLiveData(
+    val gamesDir: StateFlow<String> get() = _gamesDir
+    private val _gamesDir = MutableStateFlow(
         Uri.parse(
             PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
                 .getString(GameHelper.KEY_GAME_PATH, "")
         ).path ?: ""
     )
-    val gamesDir: LiveData<String> get() = _gamesDir
 
     var navigatedToSetup = false
 
-    init {
-        _navigationVisible.value = Pair(false, false)
-    }
-
     fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
-        if (_navigationVisible.value?.first == visible) {
+        if (navigationVisible.value.first == visible) {
             return
         }
         _navigationVisible.value = Pair(visible, animated)
     }
 
     fun setStatusBarShadeVisibility(visible: Boolean) {
-        if (_statusBarShadeVisible.value == visible) {
+        if (statusBarShadeVisible.value == visible) {
             return
         }
         _statusBarShadeVisible.value = visible
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index d16d15fa6a..53fa7a8dec 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -3,48 +3,43 @@
 
 package org.yuzu.yuzu_emu.model
 
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
 
-class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
+class SettingsViewModel : ViewModel() {
     var game: Game? = null
 
     var shouldSave = false
 
     var clickedItem: SettingsItem? = null
 
-    private val _toolbarTitle = MutableLiveData("")
-    val toolbarTitle: LiveData<String> get() = _toolbarTitle
+    val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
+    private val _shouldRecreate = MutableStateFlow(false)
 
-    private val _shouldRecreate = MutableLiveData(false)
-    val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
+    val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
+    private val _shouldNavigateBack = MutableStateFlow(false)
 
-    private val _shouldNavigateBack = MutableLiveData(false)
-    val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
+    val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
+    private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
 
-    private val _shouldShowResetSettingsDialog = MutableLiveData(false)
-    val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
+    val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
+    private val _shouldReloadSettingsList = MutableStateFlow(false)
 
-    private val _shouldReloadSettingsList = MutableLiveData(false)
-    val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
+    val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
+    private val _isUsingSearch = MutableStateFlow(false)
 
-    private val _isUsingSearch = MutableLiveData(false)
-    val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
+    val sliderProgress: StateFlow<Int> get() = _sliderProgress
+    private val _sliderProgress = MutableStateFlow(-1)
 
-    val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
+    val sliderTextValue: StateFlow<String> get() = _sliderTextValue
+    private val _sliderTextValue = MutableStateFlow("")
 
-    val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
-
-    val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
-
-    fun setToolbarTitle(value: String) {
-        _toolbarTitle.value = value
-    }
+    val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
+    private val _adapterItemChanged = MutableStateFlow(-1)
 
     fun setShouldRecreate(value: Boolean) {
         _shouldRecreate.value = value
@@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
     }
 
     fun setSliderTextValue(value: Float, units: String) {
-        savedStateHandle[KEY_SLIDER_PROGRESS] = value
-        savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
+        _sliderProgress.value = value.toInt()
+        _sliderTextValue.value = String.format(
             YuzuApplication.appContext.getString(R.string.value_with_units),
             value.toInt().toString(),
             units
@@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
     }
 
     fun setSliderProgress(value: Float) {
-        savedStateHandle[KEY_SLIDER_PROGRESS] = value
+        _sliderProgress.value = value.toInt()
     }
 
     fun setAdapterItemChanged(value: Int) {
-        savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
+        _adapterItemChanged.value = value
     }
 
     fun clear() {
         game = null
         shouldSave = false
     }
-
-    companion object {
-        const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
-        const val KEY_SLIDER_PROGRESS = "SliderProgress"
-        const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
index 27ea725a5c..531c2aaf0b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
@@ -3,29 +3,25 @@
 
 package org.yuzu.yuzu_emu.model
 
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 
 class TaskViewModel : ViewModel() {
-    private val _result = MutableLiveData<Any>()
-    val result: LiveData<Any> = _result
+    val result: StateFlow<Any> get() = _result
+    private val _result = MutableStateFlow(Any())
 
-    private val _isComplete = MutableLiveData<Boolean>()
-    val isComplete: LiveData<Boolean> = _isComplete
+    val isComplete: StateFlow<Boolean> get() = _isComplete
+    private val _isComplete = MutableStateFlow(false)
 
-    private val _isRunning = MutableLiveData<Boolean>()
-    val isRunning: LiveData<Boolean> = _isRunning
+    val isRunning: StateFlow<Boolean> get() = _isRunning
+    private val _isRunning = MutableStateFlow(false)
 
     lateinit var task: () -> Any
 
-    init {
-        clear()
-    }
-
     fun clear() {
         _result.value = Any()
         _isComplete.value = false
@@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {
     }
 
     fun runTask() {
-        if (_isRunning.value == true) {
+        if (isRunning.value) {
             return
         }
         _isRunning.value = true
 
         viewModelScope.launch(Dispatchers.IO) {
             val res = task()
-            _result.postValue(res)
-            _isComplete.postValue(true)
+            _result.value = res
+            _isComplete.value = true
+            _isRunning.value = false
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
index b0156dca5e..35e3654580 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
@@ -3,6 +3,7 @@
 
 package org.yuzu.yuzu_emu.ui
 
+import android.annotation.SuppressLint
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
@@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import com.google.android.material.color.MaterialColors
 import com.google.android.material.transition.MaterialFadeThrough
+import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.adapters.GameAdapter
 import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
@@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
         return binding.root
     }
 
+    // This is using the correct scope, lint is just acting up
+    @SuppressLint("UnsafeRepeatOnLifecycleDetector")
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         homeViewModel.setNavigationVisibility(visible = true, animated = false)
 
@@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
                 if (_binding == null) {
                     return@post
                 }
-                binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!!
+                binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
             }
         }
 
-        gamesViewModel.apply {
-            // Watch for when we get updates to any of our games lists
-            isReloading.observe(viewLifecycleOwner) { isReloading ->
-                binding.swipeRefresh.isRefreshing = isReloading
-            }
-            games.observe(viewLifecycleOwner) {
-                (binding.gridGames.adapter as GameAdapter).submitList(it)
-                if (it.isEmpty()) {
-                    binding.noticeText.visibility = View.VISIBLE
-                } else {
-                    binding.noticeText.visibility = View.GONE
+        viewLifecycleOwner.lifecycleScope.apply {
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
                 }
             }
-            shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
-                if (shouldSwapData) {
-                    (binding.gridGames.adapter as GameAdapter).submitList(
-                        gamesViewModel.games.value!!
-                    )
-                    gamesViewModel.setShouldSwapData(false)
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    gamesViewModel.games.collect {
+                        (binding.gridGames.adapter as GameAdapter).submitList(it)
+                        if (it.isEmpty()) {
+                            binding.noticeText.visibility = View.VISIBLE
+                        } else {
+                            binding.noticeText.visibility = View.GONE
+                        }
+                    }
                 }
             }
-
-            // Check if the user reselected the games menu item and then scroll to top of the list
-            shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll ->
-                if (shouldScroll) {
-                    scrollToTop()
-                    gamesViewModel.setShouldScrollToTop(false)
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    gamesViewModel.shouldSwapData.collect {
+                        if (it) {
+                            (binding.gridGames.adapter as GameAdapter).submitList(
+                                gamesViewModel.games.value
+                            )
+                            gamesViewModel.setShouldSwapData(false)
+                        }
+                    }
+                }
+            }
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    gamesViewModel.shouldScrollToTop.collect {
+                        if (it) {
+                            scrollToTop()
+                            gamesViewModel.setShouldScrollToTop(false)
+                        }
+                    }
                 }
             }
         }
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 7d8e06ad8a..1ee833cf63 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
@@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import androidx.navigation.NavController
 import androidx.navigation.fragment.NavHostFragment
 import androidx.navigation.ui.setupWithNavController
@@ -115,16 +117,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
         }
 
         // Prevents navigation from being drawn for a short time on recreation if set to hidden
-        if (!homeViewModel.navigationVisible.value?.first!!) {
+        if (!homeViewModel.navigationVisible.value.first) {
             binding.navigationView.visibility = View.INVISIBLE
             binding.statusBarShade.visibility = View.INVISIBLE
         }
 
-        homeViewModel.navigationVisible.observe(this) {
-            showNavigation(it.first, it.second)
-        }
-        homeViewModel.statusBarShadeVisible.observe(this) { visible ->
-            showStatusBarShade(visible)
+        lifecycleScope.apply {
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
+                }
+            }
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
+                }
+            }
         }
 
         // Dismiss previous notifications (should not happen unless a crash occurred)

From 8baed5d95d27c9947b1bf3ef6cb39c4344f47486 Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Thu, 14 Sep 2023 15:05:44 -0400
Subject: [PATCH 2/2] android: Refactor menu tags to enum

---
 .../features/settings/model/Settings.kt       | 11 +++++
 .../settings/model/view/SubmenuSetting.kt     |  4 +-
 .../features/settings/ui/SettingsFragment.kt  | 10 +++--
 .../settings/ui/SettingsFragmentPresenter.kt  | 45 ++++++-------------
 .../yuzu_emu/fragments/EmulationFragment.kt   |  3 +-
 .../fragments/HomeSettingsFragment.kt         |  5 +--
 .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt |  3 +-
 .../main/res/navigation/home_navigation.xml   |  2 +-
 .../res/navigation/settings_navigation.xml    |  2 +-
 9 files changed, 39 insertions(+), 46 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index 0702236e81..08e2a973db 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -80,6 +80,17 @@ object Settings {
     const val SECTION_THEME = "Theme"
     const val SECTION_DEBUG = "Debug"
 
+    enum class MenuTag(val titleId: Int) {
+        SECTION_ROOT(R.string.advanced_settings),
+        SECTION_GENERAL(R.string.preferences_general),
+        SECTION_SYSTEM(R.string.preferences_system),
+        SECTION_RENDERER(R.string.preferences_graphics),
+        SECTION_AUDIO(R.string.preferences_audio),
+        SECTION_CPU(R.string.cpu),
+        SECTION_THEME(R.string.preferences_theme),
+        SECTION_DEBUG(R.string.preferences_debug);
+    }
+
     const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
 
     const val PREF_OVERLAY_VERSION = "OverlayVersion"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
index 91c273964a..b343e527ef 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -3,10 +3,12 @@
 
 package org.yuzu.yuzu_emu.features.settings.model.view
 
+import org.yuzu.yuzu_emu.features.settings.model.Settings
+
 class SubmenuSetting(
     titleId: Int,
     descriptionId: Int,
-    val menuKey: String
+    val menuKey: Settings.MenuTag
 ) : SettingsItem(emptySetting, titleId, descriptionId) {
     override val type = TYPE_SUBMENU
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index 2a816183ab..70d8ec14bb 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -3,6 +3,7 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
+import android.annotation.SuppressLint
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
@@ -21,10 +22,11 @@ import androidx.navigation.fragment.navArgs
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.divider.MaterialDividerItemDecoration
 import com.google.android.material.transition.MaterialSharedAxis
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
+import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.model.SettingsViewModel
 
 class SettingsFragment : Fragment() {
@@ -62,10 +64,10 @@ class SettingsFragment : Fragment() {
         presenter = SettingsFragmentPresenter(
             settingsViewModel,
             settingsAdapter!!,
-            args.menuTag,
-            args.game?.gameId ?: ""
+            args.menuTag
         )
 
+        binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
         val dividerDecoration = MaterialDividerItemDecoration(
             requireContext(),
             LinearLayoutManager.VERTICAL
@@ -105,7 +107,7 @@ class SettingsFragment : Fragment() {
             }
         }
 
-        if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
+        if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
             binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
             binding.toolbarSettings.setOnMenuItemClickListener {
                 when (it.itemId) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 22a529b1ba..766414a6c8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
 import android.content.Context
 import android.content.SharedPreferences
 import android.os.Build
-import android.text.TextUtils
 import android.widget.Toast
 import androidx.preference.PreferenceManager
 import org.yuzu.yuzu_emu.R
@@ -20,15 +19,13 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.*
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.model.SettingsViewModel
 import org.yuzu.yuzu_emu.utils.NativeConfig
 
 class SettingsFragmentPresenter(
     private val settingsViewModel: SettingsViewModel,
     private val adapter: SettingsAdapter,
-    private var menuTag: String,
-    private var gameId: String
+    private var menuTag: Settings.MenuTag
 ) {
     private var settingsList = ArrayList<SettingsItem>()
 
@@ -53,24 +50,15 @@ class SettingsFragmentPresenter(
     }
 
     fun loadSettingsList() {
-        if (!TextUtils.isEmpty(gameId)) {
-            settingsViewModel.setToolbarTitle(
-                context.getString(
-                    R.string.advanced_settings_game,
-                    gameId
-                )
-            )
-        }
-
         val sl = ArrayList<SettingsItem>()
         when (menuTag) {
-            SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
-            Settings.SECTION_GENERAL -> addGeneralSettings(sl)
-            Settings.SECTION_SYSTEM -> addSystemSettings(sl)
-            Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
-            Settings.SECTION_AUDIO -> addAudioSettings(sl)
-            Settings.SECTION_THEME -> addThemeSettings(sl)
-            Settings.SECTION_DEBUG -> addDebugSettings(sl)
+            Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
+            Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
+            Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
+            Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
+            Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
+            Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
+            Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
             else -> {
                 val context = YuzuApplication.appContext
                 Toast.makeText(
@@ -86,13 +74,12 @@ class SettingsFragmentPresenter(
     }
 
     private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
-        settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
         sl.apply {
-            add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
-            add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
-            add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER))
-            add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO))
-            add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
+            add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
+            add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
+            add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
+            add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
+            add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
             add(
                 RunnableSetting(R.string.reset_to_default, 0, false) {
                     settingsViewModel.setShouldShowResetSettingsDialog(true)
@@ -102,7 +89,6 @@ class SettingsFragmentPresenter(
     }
 
     private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
-        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
         sl.apply {
             add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
             add(ShortSetting.RENDERER_SPEED_LIMIT.key)
@@ -112,7 +98,6 @@ class SettingsFragmentPresenter(
     }
 
     private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
-        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
         sl.apply {
             add(BooleanSetting.USE_DOCKED_MODE.key)
             add(IntSetting.REGION_INDEX.key)
@@ -123,7 +108,6 @@ class SettingsFragmentPresenter(
     }
 
     private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
-        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
         sl.apply {
             add(IntSetting.RENDERER_ACCURACY.key)
             add(IntSetting.RENDERER_RESOLUTION.key)
@@ -140,7 +124,6 @@ class SettingsFragmentPresenter(
     }
 
     private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
-        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
         sl.apply {
             add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
             add(ByteSetting.AUDIO_VOLUME.key)
@@ -148,7 +131,6 @@ class SettingsFragmentPresenter(
     }
 
     private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
-        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
         sl.apply {
             val theme: AbstractIntSetting = object : AbstractIntSetting {
                 override val int: Int
@@ -261,7 +243,6 @@ class SettingsFragmentPresenter(
     }
 
     private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
-        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
         sl.apply {
             add(HeaderSetting(R.string.gpu))
             add(IntSetting.RENDERER_BACKEND.key)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 1addb23265..3e6c157c7f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -50,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
 import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.model.EmulationViewModel
 import org.yuzu.yuzu_emu.overlay.InputOverlay
@@ -166,7 +165,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                 R.id.menu_settings -> {
                     val action = HomeNavigationDirections.actionGlobalSettingsActivity(
                         null,
-                        SettingsFile.FILE_NAME_CONFIG
+                        Settings.MenuTag.SECTION_ROOT
                     )
                     binding.root.findNavController().navigate(action)
                     true
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index cbbe14d220..c119e69c97 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -37,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
 import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
 import org.yuzu.yuzu_emu.features.DocumentProvider
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.model.HomeSetting
 import org.yuzu.yuzu_emu.model.HomeViewModel
 import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -78,7 +77,7 @@ class HomeSettingsFragment : Fragment() {
                     {
                         val action = HomeNavigationDirections.actionGlobalSettingsActivity(
                             null,
-                            SettingsFile.FILE_NAME_CONFIG
+                            Settings.MenuTag.SECTION_ROOT
                         )
                         binding.root.findNavController().navigate(action)
                     }
@@ -100,7 +99,7 @@ class HomeSettingsFragment : Fragment() {
                     {
                         val action = HomeNavigationDirections.actionGlobalSettingsActivity(
                             null,
-                            Settings.SECTION_THEME
+                            Settings.MenuTag.SECTION_THEME
                         )
                         binding.root.findNavController().navigate(action)
                     }
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 1ee833cf63..b6b6c6c171 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
@@ -42,7 +42,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
 import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
 import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
 import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
 import org.yuzu.yuzu_emu.model.GamesViewModel
@@ -109,7 +108,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
                 R.id.homeSettingsFragment -> {
                     val action = HomeNavigationDirections.actionGlobalSettingsActivity(
                         null,
-                        SettingsFile.FILE_NAME_CONFIG
+                        Settings.MenuTag.SECTION_ROOT
                     )
                     navHostFragment.navController.navigate(action)
                 }
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 2085430bf8..2e0ce7a3dc 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -82,7 +82,7 @@
             app:nullable="true" />
         <argument
             android:name="menuTag"
-            app:argType="string" />
+            app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
     </activity>
 
     <action
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
index 88e1b45877..1d87d36b32 100644
--- a/src/android/app/src/main/res/navigation/settings_navigation.xml
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -10,7 +10,7 @@
         android:label="SettingsFragment">
         <argument
             android:name="menuTag"
-            app:argType="string" />
+            app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
         <argument
             android:name="game"
             app:argType="org.yuzu.yuzu_emu.model.Game"