ui: avoid crashes with simultanious navigation

If navigation occurs with more than one destination at once, just drop
the slower one rather than crashing.

Resolves #374.
This commit is contained in:
Alexander Capehart 2023-03-17 15:33:59 -06:00
parent 1df15a32cd
commit 5e0059fdba
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
12 changed files with 43 additions and 32 deletions

View file

@ -270,7 +270,7 @@ class MainFragment :
when (action) {
is MainNavigationAction.Expand -> tryExpandSheets()
is MainNavigationAction.Collapse -> tryCollapseSheets()
is MainNavigationAction.Directions -> findNavController().navigate(action.directions)
is MainNavigationAction.Directions -> findNavController().navigateSafe(action.directions)
}
navModel.mainNavigationAction.consume()

View file

@ -209,7 +209,7 @@ class AlbumDetailFragment :
} else {
logD("Navigating to another album")
findNavController()
.navigate(AlbumDetailFragmentDirections.actionShowAlbum(item.album.uid))
.navigateSafe(AlbumDetailFragmentDirections.actionShowAlbum(item.album.uid))
}
}
@ -223,7 +223,7 @@ class AlbumDetailFragment :
} else {
logD("Navigating to another album")
findNavController()
.navigate(AlbumDetailFragmentDirections.actionShowAlbum(item.uid))
.navigateSafe(AlbumDetailFragmentDirections.actionShowAlbum(item.uid))
}
}
@ -231,7 +231,7 @@ class AlbumDetailFragment :
is Artist -> {
logD("Navigating to another artist")
findNavController()
.navigate(AlbumDetailFragmentDirections.actionShowArtist(item.uid))
.navigateSafe(AlbumDetailFragmentDirections.actionShowArtist(item.uid))
}
null -> {}
else -> error("Unexpected datatype: ${item::class.java}")

View file

@ -42,11 +42,7 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.unlikelyToBeNull
import org.oxycblt.auxio.util.*
/**
* A [ListFragment] that shows information about an [Artist].
@ -225,14 +221,14 @@ class ArtistDetailFragment :
is Song -> {
logD("Navigating to another album")
findNavController()
.navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.album.uid))
.navigateSafe(ArtistDetailFragmentDirections.actionShowAlbum(item.album.uid))
}
// Launch a new detail view for an album, even if it is part of
// this artist.
is Album -> {
logD("Navigating to another album")
findNavController()
.navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.uid))
.navigateSafe(ArtistDetailFragmentDirections.actionShowAlbum(item.uid))
}
// If the artist that should be navigated to is this artist, then
// scroll back to the top. Otherwise launch a new detail view.
@ -244,7 +240,7 @@ class ArtistDetailFragment :
} else {
logD("Navigating to another artist")
findNavController()
.navigate(ArtistDetailFragmentDirections.actionShowArtist(item.uid))
.navigateSafe(ArtistDetailFragmentDirections.actionShowArtist(item.uid))
}
}
null -> {}

View file

@ -43,11 +43,7 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.unlikelyToBeNull
import org.oxycblt.auxio.util.*
/**
* A [ListFragment] that shows information for a particular [Genre].
@ -216,17 +212,17 @@ class GenreDetailFragment :
is Song -> {
logD("Navigating to another song")
findNavController()
.navigate(GenreDetailFragmentDirections.actionShowAlbum(item.album.uid))
.navigateSafe(GenreDetailFragmentDirections.actionShowAlbum(item.album.uid))
}
is Album -> {
logD("Navigating to another album")
findNavController()
.navigate(GenreDetailFragmentDirections.actionShowAlbum(item.uid))
.navigateSafe(GenreDetailFragmentDirections.actionShowAlbum(item.uid))
}
is Artist -> {
logD("Navigating to another artist")
findNavController()
.navigate(GenreDetailFragmentDirections.actionShowArtist(item.uid))
.navigateSafe(GenreDetailFragmentDirections.actionShowArtist(item.uid))
}
is Genre -> {
navModel.exploreNavigationItem.consume()

View file

@ -201,7 +201,7 @@ class HomeFragment :
R.id.action_search -> {
logD("Navigating to search")
setupAxisTransitions(MaterialSharedAxis.Z)
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
findNavController().navigateSafe(HomeFragmentDirections.actionShowSearch())
}
R.id.action_settings -> {
logD("Navigating to settings")
@ -454,7 +454,7 @@ class HomeFragment :
}
setupAxisTransitions(MaterialSharedAxis.X)
findNavController().navigate(action)
findNavController().navigateSafe(action)
}
private fun updateSelection(selected: List<Music>) {

View file

@ -186,7 +186,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
}
// Keyboard is no longer needed.
hideKeyboard()
findNavController().navigate(action)
findNavController().navigateSafe(action)
}
private fun updateSelection(selected: List<Music>) {

View file

@ -30,6 +30,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
import org.oxycblt.auxio.util.navigateSafe
import org.oxycblt.auxio.util.showToast
/**
@ -63,19 +64,19 @@ class RootPreferenceFragment : BasePreferenceFragment(R.xml.preferences_root) {
// do one.
when (preference.key) {
getString(R.string.set_key_ui) -> {
findNavController().navigate(RootPreferenceFragmentDirections.goToUiPreferences())
findNavController().navigateSafe(RootPreferenceFragmentDirections.goToUiPreferences())
}
getString(R.string.set_key_personalize) -> {
findNavController()
.navigate(RootPreferenceFragmentDirections.goToPersonalizePreferences())
.navigateSafe(RootPreferenceFragmentDirections.goToPersonalizePreferences())
}
getString(R.string.set_key_music) -> {
findNavController()
.navigate(RootPreferenceFragmentDirections.goToMusicPreferences())
.navigateSafe(RootPreferenceFragmentDirections.goToMusicPreferences())
}
getString(R.string.set_key_audio) -> {
findNavController()
.navigate(RootPreferenceFragmentDirections.goToAudioPreferences())
.navigateSafe(RootPreferenceFragmentDirections.goToAudioPreferences())
}
getString(R.string.set_key_reindex) -> musicModel.refresh()
getString(R.string.set_key_rescan) -> musicModel.rescan()

View file

@ -22,6 +22,7 @@ import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.R
import org.oxycblt.auxio.settings.BasePreferenceFragment
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
import org.oxycblt.auxio.util.navigateSafe
/**
* Audio settings interface.
@ -32,7 +33,7 @@ class AudioPreferenceFragment : BasePreferenceFragment(R.xml.preferences_audio)
override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_pre_amp)) {
findNavController().navigate(AudioPreferenceFragmentDirections.goToPreAmpDialog())
findNavController().navigateSafe(AudioPreferenceFragmentDirections.goToPreAmpDialog())
}
}
}

View file

@ -26,6 +26,7 @@ import javax.inject.Inject
import org.oxycblt.auxio.R
import org.oxycblt.auxio.settings.BasePreferenceFragment
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
import org.oxycblt.auxio.util.navigateSafe
/**
* "Content" settings.
@ -38,7 +39,7 @@ class MusicPreferenceFragment : BasePreferenceFragment(R.xml.preferences_music)
override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_separators)) {
findNavController().navigate(MusicPreferenceFragmentDirections.goToSeparatorsDialog())
findNavController().navigateSafe(MusicPreferenceFragmentDirections.goToSeparatorsDialog())
}
}

View file

@ -22,6 +22,7 @@ import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.R
import org.oxycblt.auxio.settings.BasePreferenceFragment
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
import org.oxycblt.auxio.util.navigateSafe
/**
* Personalization settings interface.
@ -31,7 +32,7 @@ import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
class PersonalizePreferenceFragment : BasePreferenceFragment(R.xml.preferences_personalize) {
override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_home_tabs)) {
findNavController().navigate(PersonalizePreferenceFragmentDirections.goToTabDialog())
findNavController().navigateSafe(PersonalizePreferenceFragmentDirections.goToTabDialog())
}
}
}

View file

@ -28,6 +28,7 @@ import org.oxycblt.auxio.settings.BasePreferenceFragment
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
import org.oxycblt.auxio.ui.UISettings
import org.oxycblt.auxio.util.isNight
import org.oxycblt.auxio.util.navigateSafe
/**
* Display preferences.
@ -40,7 +41,7 @@ class UIPreferenceFragment : BasePreferenceFragment(R.xml.preferences_ui) {
override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_accent)) {
findNavController().navigate(UIPreferenceFragmentDirections.goToAccentDialog())
findNavController().navigateSafe(UIPreferenceFragmentDirections.goToAccentDialog())
}
}

View file

@ -29,6 +29,9 @@ import androidx.appcompat.widget.AppCompatButton
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.Insets
import androidx.core.graphics.drawable.DrawableCompat
import androidx.navigation.NavAction
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
@ -113,6 +116,17 @@ fun AppCompatButton.fixDoubleRipple() {
}
}
/**
* Crash-safe wrapped around [NavController.navigate] that will not crash if multiple destinations
* are selected at once.
* @param directions The [NavDirections] to navigate with.
*/
fun NavController.navigateSafe(directions: NavDirections) = try {
navigate(directions)
} catch (e: IllegalStateException) {
// Nothing to do.
}
/**
* Get the [CoordinatorLayout.Behavior] of a [View], or null if the [View] is not part of a
* [CoordinatorLayout] or does not have a [CoordinatorLayout.Behavior].
@ -149,7 +163,7 @@ val WindowInsets.systemGestureInsetsCompat: Insets
// this should allow this code to fall back to system bar insets easily if the system
// does not provide system gesture insets. This does require androidx Insets to allow
// us to use the max method on all versions however, so we will want to convert the
// system-provided insets to such..
// system-provided insets to such.
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
// API 30+, use window inset map.