diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
index 4a05c5be94..41d7f72b8f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
@@ -4,13 +4,11 @@
 package org.yuzu.yuzu_emu.adapters
 
 import android.view.LayoutInflater
-import android.view.View
 import android.view.ViewGroup
 import android.widget.Toast
 import androidx.core.content.res.ResourcesCompat
 import androidx.fragment.app.FragmentActivity
 import androidx.navigation.findNavController
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
@@ -19,72 +17,58 @@ import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
 import org.yuzu.yuzu_emu.model.Applet
 import org.yuzu.yuzu_emu.model.AppletInfo
 import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
-class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
-    RecyclerView.Adapter<AppletAdapter.AppletViewHolder>(),
-    View.OnClickListener {
-
+class AppletAdapter(val activity: FragmentActivity, applets: List<Applet>) :
+    AbstractListAdapter<Applet, AppletAdapter.AppletViewHolder>(applets) {
     override fun onCreateViewHolder(
         parent: ViewGroup,
         viewType: Int
     ): AppletAdapter.AppletViewHolder {
         CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-            .apply { root.setOnClickListener(this@AppletAdapter) }
             .also { return AppletViewHolder(it) }
     }
 
-    override fun onBindViewHolder(holder: AppletViewHolder, position: Int) =
-        holder.bind(applets[position])
-
-    override fun getItemCount(): Int = applets.size
-
-    override fun onClick(view: View) {
-        val applet = (view.tag as AppletViewHolder).applet
-        val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
-        if (appletPath.isEmpty()) {
-            Toast.makeText(
-                YuzuApplication.appContext,
-                R.string.applets_error_applet,
-                Toast.LENGTH_SHORT
-            ).show()
-            return
-        }
-
-        if (applet.appletInfo == AppletInfo.Cabinet) {
-            view.findNavController()
-                .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
-            return
-        }
-
-        NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
-        val appletGame = Game(
-            title = YuzuApplication.appContext.getString(applet.titleId),
-            path = appletPath
-        )
-        val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
-        view.findNavController().navigate(action)
-    }
-
     inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        lateinit var applet: Applet
-
-        init {
-            itemView.tag = this
-        }
-
-        fun bind(applet: Applet) {
-            this.applet = applet
-
-            binding.title.setText(applet.titleId)
-            binding.description.setText(applet.descriptionId)
+        AbstractViewHolder<Applet>(binding) {
+        override fun bind(model: Applet) {
+            binding.title.setText(model.titleId)
+            binding.description.setText(model.descriptionId)
             binding.icon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                     binding.icon.context.resources,
-                    applet.iconId,
+                    model.iconId,
                     binding.icon.context.theme
                 )
             )
+
+            binding.root.setOnClickListener { onClick(model) }
+        }
+
+        fun onClick(applet: Applet) {
+            val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
+            if (appletPath.isEmpty()) {
+                Toast.makeText(
+                    binding.root.context,
+                    R.string.applets_error_applet,
+                    Toast.LENGTH_SHORT
+                ).show()
+                return
+            }
+
+            if (applet.appletInfo == AppletInfo.Cabinet) {
+                binding.root.findNavController()
+                    .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
+                return
+            }
+
+            NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
+            val appletGame = Game(
+                title = YuzuApplication.appContext.getString(applet.titleId),
+                path = appletPath
+            )
+            val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
+            binding.root.findNavController().navigate(action)
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
index e7b7c0f2f6..a56137148f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
@@ -4,12 +4,10 @@
 package org.yuzu.yuzu_emu.adapters
 
 import android.view.LayoutInflater
-import android.view.View
 import android.view.ViewGroup
 import androidx.core.content.res.ResourcesCompat
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
@@ -19,54 +17,43 @@ import org.yuzu.yuzu_emu.model.CabinetMode
 import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder
 import org.yuzu.yuzu_emu.model.AppletInfo
 import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 class CabinetLauncherDialogAdapter(val fragment: Fragment) :
-    RecyclerView.Adapter<CabinetModeViewHolder>(),
-    View.OnClickListener {
-    private val cabinetModes = CabinetMode.values().copyOfRange(1, CabinetMode.values().size)
+    AbstractListAdapter<CabinetMode, CabinetModeViewHolder>(
+        CabinetMode.values().copyOfRange(1, CabinetMode.entries.size).toList()
+    ) {
 
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder {
         DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-            .apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) }
             .also { return CabinetModeViewHolder(it) }
     }
 
-    override fun getItemCount(): Int = cabinetModes.size
-
-    override fun onBindViewHolder(holder: CabinetModeViewHolder, position: Int) =
-        holder.bind(cabinetModes[position])
-
-    override fun onClick(view: View) {
-        val mode = (view.tag as CabinetModeViewHolder).cabinetMode
-        val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
-        NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
-        NativeLibrary.setCabinetMode(mode.id)
-        val appletGame = Game(
-            title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
-            path = appletPath
-        )
-        val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
-        fragment.findNavController().navigate(action)
-    }
-
     inner class CabinetModeViewHolder(val binding: DialogListItemBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        lateinit var cabinetMode: CabinetMode
-
-        init {
-            itemView.tag = this
-        }
-
-        fun bind(cabinetMode: CabinetMode) {
-            this.cabinetMode = cabinetMode
+        AbstractViewHolder<CabinetMode>(binding) {
+        override fun bind(model: CabinetMode) {
             binding.icon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                     binding.icon.context.resources,
-                    cabinetMode.iconId,
+                    model.iconId,
                     binding.icon.context.theme
                 )
             )
-            binding.title.setText(cabinetMode.titleId)
+            binding.title.setText(model.titleId)
+
+            binding.root.setOnClickListener { onClick(model) }
+        }
+
+        private fun onClick(mode: CabinetMode) {
+            val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
+            NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
+            NativeLibrary.setCabinetMode(mode.id)
+            val appletGame = Game(
+                title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
+                path = appletPath
+            )
+            val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
+            fragment.findNavController().navigate(action)
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
index 95841d7863..0046d53141 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
@@ -12,23 +12,22 @@ 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.databinding.CardInstallableIconBinding
 import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
 import org.yuzu.yuzu_emu.model.GameProperty
 import org.yuzu.yuzu_emu.model.InstallableProperty
 import org.yuzu.yuzu_emu.model.SubmenuProperty
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 class GamePropertiesAdapter(
     private val viewLifecycle: LifecycleOwner,
     private var properties: List<GameProperty>
-) :
-    RecyclerView.Adapter<GamePropertiesAdapter.GamePropertyViewHolder>() {
+) : AbstractListAdapter<GameProperty, AbstractViewHolder<GameProperty>>(properties) {
     override fun onCreateViewHolder(
         parent: ViewGroup,
         viewType: Int
-    ): GamePropertyViewHolder {
+    ): AbstractViewHolder<GameProperty> {
         val inflater = LayoutInflater.from(parent.context)
         return when (viewType) {
             PropertyType.Submenu.ordinal -> {
@@ -51,11 +50,6 @@ class GamePropertiesAdapter(
         }
     }
 
-    override fun getItemCount(): Int = properties.size
-
-    override fun onBindViewHolder(holder: GamePropertyViewHolder, position: Int) =
-        holder.bind(properties[position])
-
     override fun getItemViewType(position: Int): Int {
         return when (properties[position]) {
             is SubmenuProperty -> PropertyType.Submenu.ordinal
@@ -63,14 +57,10 @@ class GamePropertiesAdapter(
         }
     }
 
-    sealed class GamePropertyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
-        abstract fun bind(property: GameProperty)
-    }
-
     inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) :
-        GamePropertyViewHolder(binding.root) {
-        override fun bind(property: GameProperty) {
-            val submenuProperty = property as SubmenuProperty
+        AbstractViewHolder<GameProperty>(binding) {
+        override fun bind(model: GameProperty) {
+            val submenuProperty = model as SubmenuProperty
 
             binding.root.setOnClickListener {
                 submenuProperty.action.invoke()
@@ -108,9 +98,9 @@ class GamePropertiesAdapter(
     }
 
     inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
-        GamePropertyViewHolder(binding.root) {
-        override fun bind(property: GameProperty) {
-            val installableProperty = property as InstallableProperty
+        AbstractViewHolder<GameProperty>(binding) {
+        override fun bind(model: GameProperty) {
+            val installableProperty = model as InstallableProperty
 
             binding.title.setText(installableProperty.titleId)
             binding.description.setText(installableProperty.descriptionId)
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 58ce343f41..b512845d5c 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
@@ -14,69 +14,37 @@ 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
 import org.yuzu.yuzu_emu.model.HomeSetting
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 class HomeSettingAdapter(
     private val activity: AppCompatActivity,
     private val viewLifecycle: LifecycleOwner,
-    var options: List<HomeSetting>
-) :
-    RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
-    View.OnClickListener {
+    options: List<HomeSetting>
+) : AbstractListAdapter<HomeSetting, HomeSettingAdapter.HomeOptionViewHolder>(options) {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
-        val binding =
-            CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-        binding.root.setOnClickListener(this)
-        return HomeOptionViewHolder(binding)
-    }
-
-    override fun getItemCount(): Int {
-        return options.size
-    }
-
-    override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
-        holder.bind(options[position])
-    }
-
-    override fun onClick(view: View) {
-        val holder = view.tag as HomeOptionViewHolder
-        if (holder.option.isEnabled.invoke()) {
-            holder.option.onClick.invoke()
-        } else {
-            MessageDialogFragment.newInstance(
-                activity,
-                titleId = holder.option.disabledTitleId,
-                descriptionId = holder.option.disabledMessageId
-            ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
-        }
+        CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+            .also { return HomeOptionViewHolder(it) }
     }
 
     inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        lateinit var option: HomeSetting
-
-        init {
-            itemView.tag = this
-        }
-
-        fun bind(option: HomeSetting) {
-            this.option = option
-            binding.optionTitle.text = activity.resources.getString(option.titleId)
-            binding.optionDescription.text = activity.resources.getString(option.descriptionId)
+        AbstractViewHolder<HomeSetting>(binding) {
+        override fun bind(model: HomeSetting) {
+            binding.optionTitle.text = activity.resources.getString(model.titleId)
+            binding.optionDescription.text = activity.resources.getString(model.descriptionId)
             binding.optionIcon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                     activity.resources,
-                    option.iconId,
+                    model.iconId,
                     activity.theme
                 )
             )
 
-            when (option.titleId) {
+            when (model.titleId) {
                 R.string.get_early_access ->
                     binding.optionLayout.background =
                         ContextCompat.getDrawable(
@@ -85,7 +53,7 @@ class HomeSettingAdapter(
                         )
             }
 
-            if (!option.isEnabled.invoke()) {
+            if (!model.isEnabled.invoke()) {
                 binding.optionTitle.alpha = 0.5f
                 binding.optionDescription.alpha = 0.5f
                 binding.optionIcon.alpha = 0.5f
@@ -93,7 +61,7 @@ class HomeSettingAdapter(
 
             viewLifecycle.lifecycleScope.launch {
                 viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    option.details.collect { updateOptionDetails(it) }
+                    model.details.collect { updateOptionDetails(it) }
                 }
             }
             binding.optionDetail.postDelayed(
@@ -103,6 +71,20 @@ class HomeSettingAdapter(
                 },
                 3000
             )
+
+            binding.root.setOnClickListener { onClick(model) }
+        }
+
+        private fun onClick(model: HomeSetting) {
+            if (model.isEnabled.invoke()) {
+                model.onClick.invoke()
+            } else {
+                MessageDialogFragment.newInstance(
+                    activity,
+                    titleId = model.disabledTitleId,
+                    descriptionId = model.disabledMessageId
+                ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
+            }
         }
 
         private fun updateOptionDetails(detailString: String) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
index e960fbaabb..4218c4e52c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
@@ -6,43 +6,33 @@ package org.yuzu.yuzu_emu.adapters
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
 import org.yuzu.yuzu_emu.model.Installable
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
-class InstallableAdapter(private val installables: List<Installable>) :
-    RecyclerView.Adapter<InstallableAdapter.InstallableViewHolder>() {
+class InstallableAdapter(installables: List<Installable>) :
+    AbstractListAdapter<Installable, InstallableAdapter.InstallableViewHolder>(installables) {
     override fun onCreateViewHolder(
         parent: ViewGroup,
         viewType: Int
     ): InstallableAdapter.InstallableViewHolder {
-        val binding =
-            CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-        return InstallableViewHolder(binding)
+        CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+            .also { return InstallableViewHolder(it) }
     }
 
-    override fun getItemCount(): Int = installables.size
-
-    override fun onBindViewHolder(holder: InstallableAdapter.InstallableViewHolder, position: Int) =
-        holder.bind(installables[position])
-
     inner class InstallableViewHolder(val binding: CardInstallableBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        lateinit var installable: Installable
+        AbstractViewHolder<Installable>(binding) {
+        override fun bind(model: Installable) {
+            binding.title.setText(model.titleId)
+            binding.description.setText(model.descriptionId)
 
-        fun bind(installable: Installable) {
-            this.installable = installable
-
-            binding.title.setText(installable.titleId)
-            binding.description.setText(installable.descriptionId)
-
-            if (installable.install != null) {
+            if (model.install != null) {
                 binding.buttonInstall.visibility = View.VISIBLE
-                binding.buttonInstall.setOnClickListener { installable.install.invoke() }
+                binding.buttonInstall.setOnClickListener { model.install.invoke() }
             }
-            if (installable.export != null) {
+            if (model.export != null) {
                 binding.buttonExport.visibility = View.VISIBLE
-                binding.buttonExport.setOnClickListener { installable.export.invoke() }
+                binding.buttonExport.setOnClickListener { model.export.invoke() }
             }
         }
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
index bc6ff13643..38bb1f96f1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
@@ -7,49 +7,33 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.appcompat.app.AppCompatActivity
-import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
 import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
 import org.yuzu.yuzu_emu.model.License
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
-class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List<License>) :
-    RecyclerView.Adapter<LicenseAdapter.LicenseViewHolder>(),
-    View.OnClickListener {
+class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) :
+    AbstractListAdapter<License, LicenseAdapter.LicenseViewHolder>(licenses) {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder {
-        val binding =
-            ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-        binding.root.setOnClickListener(this)
-        return LicenseViewHolder(binding)
+        ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+            .also { return LicenseViewHolder(it) }
     }
 
-    override fun getItemCount(): Int = licenses.size
+    inner class LicenseViewHolder(val binding: ListItemSettingBinding) :
+        AbstractViewHolder<License>(binding) {
+        override fun bind(model: License) {
+            binding.apply {
+                textSettingName.text = root.context.getString(model.titleId)
+                textSettingDescription.text = root.context.getString(model.descriptionId)
+                textSettingValue.visibility = View.GONE
 
-    override fun onBindViewHolder(holder: LicenseViewHolder, position: Int) {
-        holder.bind(licenses[position])
-    }
-
-    override fun onClick(view: View) {
-        val license = (view.tag as LicenseViewHolder).license
-        LicenseBottomSheetDialogFragment.newInstance(license)
-            .show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
-    }
-
-    inner class LicenseViewHolder(val binding: ListItemSettingBinding) : ViewHolder(binding.root) {
-        lateinit var license: License
-
-        init {
-            itemView.tag = this
+                root.setOnClickListener { onClick(model) }
+            }
         }
 
-        fun bind(license: License) {
-            this.license = license
-
-            val context = YuzuApplication.appContext
-            binding.textSettingName.text = context.getString(license.titleId)
-            binding.textSettingDescription.text = context.getString(license.descriptionId)
-            binding.textSettingValue.visibility = View.GONE
+        private fun onClick(license: License) {
+            LicenseBottomSheetDialogFragment.newInstance(license)
+                .show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
index 6b46d359e5..02118e1a8a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
@@ -10,7 +10,6 @@ import android.view.ViewGroup
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.content.res.ResourcesCompat
 import androidx.lifecycle.ViewModelProvider
-import androidx.recyclerview.widget.RecyclerView
 import com.google.android.material.button.MaterialButton
 import org.yuzu.yuzu_emu.databinding.PageSetupBinding
 import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -18,31 +17,19 @@ import org.yuzu.yuzu_emu.model.SetupCallback
 import org.yuzu.yuzu_emu.model.SetupPage
 import org.yuzu.yuzu_emu.model.StepState
 import org.yuzu.yuzu_emu.utils.ViewUtils
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
-class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
-    RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
+class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
+    AbstractListAdapter<SetupPage, SetupAdapter.SetupPageViewHolder>(pages) {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder {
-        val binding = PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-        return SetupPageViewHolder(binding)
+        PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+            .also { return SetupPageViewHolder(it) }
     }
 
-    override fun getItemCount(): Int = pages.size
-
-    override fun onBindViewHolder(holder: SetupPageViewHolder, position: Int) =
-        holder.bind(pages[position])
-
     inner class SetupPageViewHolder(val binding: PageSetupBinding) :
-        RecyclerView.ViewHolder(binding.root), SetupCallback {
-        lateinit var page: SetupPage
-
-        init {
-            itemView.tag = this
-        }
-
-        fun bind(page: SetupPage) {
-            this.page = page
-
-            if (page.stepCompleted.invoke() == StepState.COMPLETE) {
+        AbstractViewHolder<SetupPage>(binding), SetupCallback {
+        override fun bind(model: SetupPage) {
+            if (model.stepCompleted.invoke() == StepState.COMPLETE) {
                 binding.buttonAction.visibility = View.INVISIBLE
                 binding.textConfirmation.visibility = View.VISIBLE
             }
@@ -50,31 +37,31 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
             binding.icon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                     activity.resources,
-                    page.iconId,
+                    model.iconId,
                     activity.theme
                 )
             )
-            binding.textTitle.text = activity.resources.getString(page.titleId)
+            binding.textTitle.text = activity.resources.getString(model.titleId)
             binding.textDescription.text =
-                Html.fromHtml(activity.resources.getString(page.descriptionId), 0)
+                Html.fromHtml(activity.resources.getString(model.descriptionId), 0)
 
             binding.buttonAction.apply {
-                text = activity.resources.getString(page.buttonTextId)
-                if (page.buttonIconId != 0) {
+                text = activity.resources.getString(model.buttonTextId)
+                if (model.buttonIconId != 0) {
                     icon = ResourcesCompat.getDrawable(
                         activity.resources,
-                        page.buttonIconId,
+                        model.buttonIconId,
                         activity.theme
                     )
                 }
                 iconGravity =
-                    if (page.leftAlignedIcon) {
+                    if (model.leftAlignedIcon) {
                         MaterialButton.ICON_GRAVITY_START
                     } else {
                         MaterialButton.ICON_GRAVITY_END
                     }
                 setOnClickListener {
-                    page.buttonAction.invoke(this@SetupPageViewHolder)
+                    model.buttonAction.invoke(this@SetupPageViewHolder)
                 }
             }
         }