Add Loading fallback states

Add fallback states for when there is no music & if the load fails, along with a retry button if that happens.
This commit is contained in:
OxygenCobalt 2020-08-19 09:45:15 -06:00
parent 1b21552576
commit 24452e8fa4
8 changed files with 116 additions and 26 deletions

View file

@ -17,8 +17,11 @@ import org.oxycblt.auxio.music.MusicLoadResponse
class LoadingFragment : Fragment() { class LoadingFragment : Fragment() {
private val loadingModel: LoadingViewModel by lazy { private val loadingModel: LoadingViewModel by lazy {
ViewModelProvider(this, LoadingViewModel.Factory( ViewModelProvider(
requireActivity().application) this,
LoadingViewModel.Factory(
requireActivity().application
)
).get(LoadingViewModel::class.java) ).get(LoadingViewModel::class.java)
} }
@ -34,21 +37,62 @@ class LoadingFragment : Fragment() {
) )
binding.lifecycleOwner = this binding.lifecycleOwner = this
binding.loadingModel = loadingModel
loadingModel.musicRepoResponse.observe(viewLifecycleOwner, Observer { response -> loadingModel.musicRepoResponse.observe(
onMusicLoadResponse(response) viewLifecycleOwner,
}) Observer { response ->
onMusicLoadResponse(response)
}
)
loadingModel.doRetry.observe(
viewLifecycleOwner,
Observer { retry ->
onRetry(retry)
}
)
Log.d(this::class.simpleName, "Fragment created.") Log.d(this::class.simpleName, "Fragment created.")
return binding.root return binding.root
} }
private fun onMusicLoadResponse(response: MusicLoadResponse) { private fun onMusicLoadResponse(repoResponse: MusicLoadResponse?) {
if (response == MusicLoadResponse.DONE) {
this.findNavController().navigate( // Don't run this if the value is null, Which is what the value changes to after
LoadingFragmentDirections.actionToLibrary() // this is run.
) repoResponse?.let { response ->
if (response == MusicLoadResponse.DONE) {
this.findNavController().navigate(
LoadingFragmentDirections.actionToLibrary()
)
} else {
// If the response wasn't a success, then show the specific error message
// depending on which error response was given, along with a retry button
binding.loadingBar.visibility = View.GONE
binding.statusText.visibility = View.VISIBLE
binding.resetButton.visibility = View.VISIBLE
if (response == MusicLoadResponse.NO_MUSIC) {
binding.statusText.text = getString(R.string.error_no_music)
} else {
binding.statusText.text = getString(R.string.error_music_load_failed)
}
}
loadingModel.doneWithResponse()
} }
} }
}
private fun onRetry(retry: Boolean) {
if (retry) {
binding.loadingBar.visibility = View.VISIBLE
binding.statusText.visibility = View.GONE
binding.resetButton.visibility = View.GONE
loadingModel.doneWithRetry()
}
}
}

View file

@ -6,12 +6,16 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.oxycblt.auxio.music.MusicLoadResponse import org.oxycblt.auxio.music.MusicLoadResponse
import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicRepository
class LoadingViewModel(private val app: Application) : ViewModel() { class LoadingViewModel(private val app: Application) : ViewModel() {
private val loadingJob = Job() private val loadingJob = Job()
private val ioScope = CoroutineScope( private val ioScope = CoroutineScope(
Dispatchers.IO Dispatchers.IO
@ -20,6 +24,9 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
private val mMusicRepoResponse = MutableLiveData<MusicLoadResponse>() private val mMusicRepoResponse = MutableLiveData<MusicLoadResponse>()
val musicRepoResponse: LiveData<MusicLoadResponse> get() = mMusicRepoResponse val musicRepoResponse: LiveData<MusicLoadResponse> get() = mMusicRepoResponse
private val mDoRetry = MutableLiveData<Boolean>()
val doRetry: LiveData<Boolean> get() = mDoRetry
init { init {
startMusicRepo() startMusicRepo()
@ -40,6 +47,20 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
} }
} }
fun doneWithResponse() {
mMusicRepoResponse.value = null
}
fun retry() {
mDoRetry.value = true
startMusicRepo()
}
fun doneWithRetry() {
mDoRetry.value = false
}
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
@ -53,7 +74,8 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
if (modelClass.isAssignableFrom(LoadingViewModel::class.java)) { if (modelClass.isAssignableFrom(LoadingViewModel::class.java)) {
return LoadingViewModel(application) as T return LoadingViewModel(application) as T
} }
throw IllegalArgumentException("Unknown ViewModel class")
throw IllegalArgumentException("Unknown ViewModel class.")
} }
} }
} }

View file

@ -149,7 +149,6 @@ class MusicRepository {
) )
return songList return songList
} catch (error: Exception) { } catch (error: Exception) {
// TODO: Add better error handling // TODO: Add better error handling

View file

@ -19,5 +19,4 @@
tools:ignore="FragmentTagUsage" /> tools:ignore="FragmentTagUsage" />
</FrameLayout> </FrameLayout>
</layout> </layout>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout <layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">

View file

@ -3,6 +3,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="loadingModel"
type="org.oxycblt.auxio.loading.LoadingViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -14,22 +21,37 @@
android:indeterminateTint="?attr/colorAccent" android:indeterminateTint="?attr/colorAccent"
android:indeterminateTintMode="src_in" android:indeterminateTintMode="src_in"
android:paddingBottom="@dimen/padding_small" android:paddingBottom="@dimen/padding_small"
app:layout_constraintBottom_toTopOf="@+id/text_indexing_library" app:layout_constraintBottom_toTopOf="@+id/status_text"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"/> app:layout_constraintVertical_chainStyle="packed" />
<TextView <TextView
android:id="@+id/text_indexing_library" android:id="@+id/status_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/label_loading_music" android:text="@string/status_loading_music"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toTopOf="@+id/reset_button"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loading_bar" /> app:layout_constraintTop_toBottomOf="@+id/loading_bar" />
<Button
android:id="@+id/reset_button"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_retry"
android:onClick="@{() -> loadingModel.retry()}"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/status_text" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -9,7 +9,7 @@
android:id="@+id/loadingFragment" android:id="@+id/loadingFragment"
android:name="org.oxycblt.auxio.loading.LoadingFragment" android:name="org.oxycblt.auxio.loading.LoadingFragment"
android:label="LoadingFragment" android:label="LoadingFragment"
tools:layout="@layout/fragment_loading" > tools:layout="@layout/fragment_loading">
<action <action
android:id="@+id/action_to_library" android:id="@+id/action_to_library"
app:destination="@id/libraryFragment" /> app:destination="@id/libraryFragment" />

View file

@ -2,7 +2,12 @@
<resources> <resources>
<string name="app_name">Auxio</string> <string name="app_name">Auxio</string>
<string name="label_loading_music">Scanning your music library for the first time...</string> <string name="status_loading_music">Scanning your music library for the first time...</string>
<string name="error_no_music">No music found.</string>
<string name="error_music_load_failed">Music loading failed.</string>
<string name="label_retry">Retry</string>
<string name="title_library_fragment">Library</string> <string name="title_library_fragment">Library</string>
</resources> </resources>