diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt index 59e76ae178..1c230f4447 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt @@ -16,6 +16,7 @@ import androidx.annotation.ColorInt import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.RecyclerView import com.google.android.material.color.MaterialColors import com.google.android.material.datepicker.CalendarConstraints @@ -543,6 +544,25 @@ class SettingsAdapter( } } + override fun onViewRecycled(holder: SettingViewHolder) { + super.onViewRecycled(holder) + holder.onViewRecycled() + } + + override fun onViewAttachedToWindow(holder: SettingViewHolder) { + super.onViewAttachedToWindow(holder) + holder.onViewAttachedToWindow() + } + + override fun onViewDetachedFromWindow(holder: SettingViewHolder) { + super.onViewDetachedFromWindow(holder) + holder.onViewDetachedFromWindow() + } + + fun getFragmentLifecycle(): Lifecycle { + return fragmentView.getFragmentLifecycle() + } + private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { val valuesId = item.valuesId diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt index 6a294ee139..991d3c3c2b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt @@ -18,6 +18,7 @@ import androidx.core.view.updatePadding import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar @@ -25,7 +26,6 @@ import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.databinding.FragmentSettingsBinding import org.dolphinemu.dolphinemu.features.settings.model.Settings import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem -import org.dolphinemu.dolphinemu.ui.main.MainActivity import org.dolphinemu.dolphinemu.ui.main.MainPresenter import org.dolphinemu.dolphinemu.utils.GpuDriverInstallResult import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable @@ -200,6 +200,10 @@ class SettingsFragment : Fragment(), SettingsFragmentView { .show() } + override fun getFragmentLifecycle(): Lifecycle { + return lifecycle + } + private fun askForDriverFile() { val intent = Intent(Intent.ACTION_GET_CONTENT).apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt index 076beaff6f..b0ad3e28b1 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.kt @@ -4,6 +4,7 @@ package org.dolphinemu.dolphinemu.features.settings.ui import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Lifecycle import org.dolphinemu.dolphinemu.features.settings.model.Settings import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem import org.dolphinemu.dolphinemu.utils.GpuDriverInstallResult @@ -119,4 +120,9 @@ interface SettingsFragmentView { * Shows a dialog asking the user to install or uninstall a GPU driver */ fun showGpuDriverDialog() + + /** + * Returns the Lifecycle for the Fragment. + */ + fun getFragmentLifecycle(): Lifecycle } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.kt index 2054b08f43..dd5d4c97f0 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.kt @@ -9,15 +9,17 @@ import android.view.View import android.view.View.OnLongClickListener import android.widget.TextView import android.widget.Toast -import androidx.recyclerview.widget.RecyclerView +import androidx.lifecycle.LifecycleOwner import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.dolphinemu.dolphinemu.DolphinApplication import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter +import org.dolphinemu.dolphinemu.utils.LifecycleViewHolder abstract class SettingViewHolder(itemView: View, protected val adapter: SettingsAdapter) : - RecyclerView.ViewHolder(itemView), View.OnClickListener, OnLongClickListener { + LifecycleViewHolder(itemView, adapter.getFragmentLifecycle()), + LifecycleOwner, View.OnClickListener, OnLongClickListener { init { itemView.setOnClickListener(this) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LifecycleViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LifecycleViewHolder.kt new file mode 100644 index 0000000000..47696ac4bb --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LifecycleViewHolder.kt @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.utils + +import android.view.View +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.recyclerview.widget.RecyclerView + +/** + * A ViewHolder with an attached Lifecycle. + * + * @param itemView The view held by the ViewHolder. + * @param parentLifecycle If the passed-in Lifecycle changes state to DESTROYED, this ViewHolder + * will also change state to DESTROYED. This should normally be set to the + * Lifecycle of the containing Fragment or Activity to prevent instances + * of this class from leaking. + */ +abstract class LifecycleViewHolder(itemView: View, parentLifecycle: Lifecycle) : + RecyclerView.ViewHolder(itemView), LifecycleOwner { + + private val lifecycleRegistry = LifecycleRegistry(this) + + override val lifecycle: Lifecycle + get() = lifecycleRegistry + + init { + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) + parentLifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + // Make sure this ViewHolder doesn't leak if its lifecycle has observers + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) + } + }) + } + + /** + * To be called from a function overriding [RecyclerView.Adapter.onViewRecycled]. + */ + fun onViewRecycled() { + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) + } + + /** + * To be called from a function overriding [RecyclerView.Adapter.onViewAttachedToWindow]. + */ + fun onViewAttachedToWindow() { + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + } + + /** + * To be called from a function overriding [RecyclerView.Adapter.onViewDetachedFromWindow]. + */ + fun onViewDetachedFromWindow() { + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) + } +}