Added coroutine exception handler to EntriesViewModel.kt

This commit is contained in:
Arty Bishop committed 2021-10-27 21:47:02 +01:00
1 parent b6d019ace7
commit 6fa63fdcff
6 files changed
+45 -42

No files matched your search

@@ -17,8 +17,9 @@
*/ */
package com.rtbishop.look4sat.framework.model package com.rtbishop.look4sat.framework.model
sealed class Result<out T : Any> { sealed class DataState<out T> {
data class Success<out T : Any>(val data: T) : Result<T>() data class Success<out T>(val data: T) : DataState<T>()
data class Error(val exception: Throwable) : Result<Nothing>() data class Error(val error: Throwable) : DataState<Nothing>()
object InProgress : Result<Nothing>() object Empty : DataState<Nothing>()
object Loading : DataState<Nothing>()
} }
@@ -29,7 +29,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.SatItem import com.rtbishop.look4sat.data.SatItem
import com.rtbishop.look4sat.databinding.FragmentEntriesBinding import com.rtbishop.look4sat.databinding.FragmentEntriesBinding
import com.rtbishop.look4sat.framework.model.Result import com.rtbishop.look4sat.framework.model.DataState
import com.rtbishop.look4sat.utility.RecyclerDivider import com.rtbishop.look4sat.utility.RecyclerDivider
import com.rtbishop.look4sat.utility.getNavResult import com.rtbishop.look4sat.utility.getNavResult
import com.rtbishop.look4sat.utility.navigateSafe import com.rtbishop.look4sat.utility.navigateSafe
@@ -79,21 +79,21 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) {
} }
private fun handleSatData( private fun handleSatData(
result: Result<List<SatItem>>, dataState: DataState<List<SatItem>>,
binding: FragmentEntriesBinding, binding: FragmentEntriesBinding,
entriesAdapter: EntriesAdapter entriesAdapter: EntriesAdapter
) { ) {
when (result) { when (dataState) {
is Result.Success -> { is DataState.Success -> {
entriesAdapter.submitList(result.data) entriesAdapter.submitList(dataState.data)
binding.entriesProgress.visibility = View.INVISIBLE binding.entriesProgress.visibility = View.INVISIBLE
binding.entriesRecycler.visibility = View.VISIBLE binding.entriesRecycler.visibility = View.VISIBLE
} }
is Result.InProgress -> { is DataState.Loading -> {
binding.entriesProgress.visibility = View.VISIBLE binding.entriesProgress.visibility = View.VISIBLE
binding.entriesRecycler.visibility = View.INVISIBLE binding.entriesRecycler.visibility = View.INVISIBLE
} }
is Result.Error -> { is DataState.Error -> {
binding.entriesProgress.visibility = View.INVISIBLE binding.entriesProgress.visibility = View.INVISIBLE
binding.entriesRecycler.visibility = View.VISIBLE binding.entriesRecycler.visibility = View.VISIBLE
requireView().showSnack(getString(R.string.entries_update_error)) requireView().showSnack(getString(R.string.entries_update_error))
@@ -24,10 +24,12 @@ import androidx.lifecycle.*
import com.rtbishop.look4sat.data.Preferences import com.rtbishop.look4sat.data.Preferences
import com.rtbishop.look4sat.data.SatelliteRepo import com.rtbishop.look4sat.data.SatelliteRepo
import com.rtbishop.look4sat.data.SatItem import com.rtbishop.look4sat.data.SatItem
import com.rtbishop.look4sat.framework.model.Result import com.rtbishop.look4sat.framework.model.DataState
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@@ -38,46 +40,46 @@ class EntriesViewModel @Inject constructor(
private val satelliteRepo: SatelliteRepo, private val satelliteRepo: SatelliteRepo,
) : ViewModel(), EntriesAdapter.EntriesClickListener, SearchView.OnQueryTextListener { ) : ViewModel(), EntriesAdapter.EntriesClickListener, SearchView.OnQueryTextListener {
private val coroutineHandler = CoroutineExceptionHandler { _, throwable ->
Timber.d(throwable)
_satData.value = DataState.Error(throwable)
}
private val transModes = MutableLiveData(satelliteRepo.loadSelectedModes()) private val transModes = MutableLiveData(satelliteRepo.loadSelectedModes())
private val currentQuery = MutableLiveData(String()) private val currentQuery = MutableLiveData(String())
private val itemsWithModes = transModes.switchMap { modes -> private val itemsWithModes = transModes.switchMap { modes ->
liveData { satelliteRepo.getSatItems().collect { emit(filterByModes(it, modes)) } } liveData { satelliteRepo.getSatItems().collect { emit(filterByModes(it, modes)) } }
} }
private val itemsWithQuery = currentQuery.switchMap { query -> private val itemsWithQuery = currentQuery.switchMap { query ->
itemsWithModes.map { items -> Result.Success(filterByQuery(items, query)) } itemsWithModes.map { items -> DataState.Success(filterByQuery(items, query)) }
} }
private val _satData = MediatorLiveData<Result<List<SatItem>>>().apply { private val _satData = MediatorLiveData<DataState<List<SatItem>>>().apply {
addSource(itemsWithQuery) { value -> this.value = value } addSource(itemsWithQuery) { value -> this.value = value }
} }
private var shouldSelectAll = true private var shouldSelectAll = true
val satData: LiveData<Result<List<SatItem>>> = _satData val satData: LiveData<DataState<List<SatItem>>> = _satData
fun updateEntriesFromFile(uri: Uri) { fun updateEntriesFromFile(uri: Uri) {
viewModelScope.launch { viewModelScope.launch(coroutineHandler) {
_satData.value = Result.InProgress _satData.value = DataState.Loading
runCatching { runCatching {
resolver.openInputStream(uri)?.use { stream -> resolver.openInputStream(uri)?.use { stream ->
satelliteRepo.updateEntriesFromFile(stream) satelliteRepo.updateEntriesFromFile(stream)
} }
}.onFailure { _satData.value = Result.Error(it) } }.onFailure { _satData.value = DataState.Error(it) }
} }
} }
fun updateEntriesFromWeb(sources: List<String>) { fun updateEntriesFromWeb(sources: List<String>) {
viewModelScope.launch { viewModelScope.launch(coroutineHandler) {
_satData.value = Result.InProgress _satData.value = DataState.Loading
try {
preferences.saveTleSources(sources) preferences.saveTleSources(sources)
satelliteRepo.updateEntriesFromWeb(sources) satelliteRepo.updateEntriesFromWeb(sources)
} catch (exception: Exception) {
_satData.value = Result.Error(exception)
}
} }
} }
fun selectCurrentItems() { fun selectCurrentItems() {
val currentValue = _satData.value val currentValue = _satData.value
if (currentValue is Result.Success) { if (currentValue is DataState.Success) {
updateSelection(currentValue.data.map { it.catNum }, shouldSelectAll) updateSelection(currentValue.data.map { it.catNum }, shouldSelectAll)
shouldSelectAll = shouldSelectAll.not() shouldSelectAll = shouldSelectAll.not()
} }
@@ -30,7 +30,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.FragmentPassesBinding import com.rtbishop.look4sat.databinding.FragmentPassesBinding
import com.rtbishop.look4sat.domain.SatPass import com.rtbishop.look4sat.domain.SatPass
import com.rtbishop.look4sat.framework.model.Result import com.rtbishop.look4sat.framework.model.DataState
import com.rtbishop.look4sat.utility.RecyclerDivider import com.rtbishop.look4sat.utility.RecyclerDivider
import com.rtbishop.look4sat.utility.navigateSafe import com.rtbishop.look4sat.utility.navigateSafe
import com.rtbishop.look4sat.utility.toTimerString import com.rtbishop.look4sat.utility.toTimerString
@@ -73,21 +73,21 @@ class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesC
} }
private fun handleNewPasses( private fun handleNewPasses(
result: Result<List<SatPass>>, dataState: DataState<List<SatPass>>,
passesAdapter: PassesAdapter, passesAdapter: PassesAdapter,
binding: FragmentPassesBinding binding: FragmentPassesBinding
) { ) {
when (result) { when (dataState) {
is Result.Success -> { is DataState.Success -> {
passesAdapter.submitList(result.data) passesAdapter.submitList(dataState.data)
binding.apply { binding.apply {
passesError.visibility = View.INVISIBLE passesError.visibility = View.INVISIBLE
passesProgress.visibility = View.INVISIBLE passesProgress.visibility = View.INVISIBLE
passesRecycler.visibility = View.VISIBLE passesRecycler.visibility = View.VISIBLE
} }
tickMainTimer(result.data, binding) tickMainTimer(dataState.data, binding)
} }
is Result.InProgress -> { is DataState.Loading -> {
binding.apply { binding.apply {
passesTimer.text = 0L.toTimerString() passesTimer.text = 0L.toTimerString()
passesError.visibility = View.INVISIBLE passesError.visibility = View.INVISIBLE
@@ -95,7 +95,7 @@ class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesC
passesProgress.visibility = View.VISIBLE passesProgress.visibility = View.VISIBLE
} }
} }
is Result.Error -> { is DataState.Error -> {
binding.apply { binding.apply {
passesTimer.text = 0L.toTimerString() passesTimer.text = 0L.toTimerString()
passesProgress.visibility = View.INVISIBLE passesProgress.visibility = View.INVISIBLE
@@ -25,7 +25,7 @@ import com.rtbishop.look4sat.data.Preferences
import com.rtbishop.look4sat.data.SatelliteRepo import com.rtbishop.look4sat.data.SatelliteRepo
import com.rtbishop.look4sat.domain.Predictor import com.rtbishop.look4sat.domain.Predictor
import com.rtbishop.look4sat.domain.SatPass import com.rtbishop.look4sat.domain.SatPass
import com.rtbishop.look4sat.framework.model.Result import com.rtbishop.look4sat.framework.model.DataState
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@@ -39,16 +39,16 @@ class PassesViewModel @Inject constructor(
private val preferences: Preferences private val preferences: Preferences
) : ViewModel() { ) : ViewModel() {
private val _passes = MutableLiveData<Result<List<SatPass>>>(Result.InProgress) private val _passes = MutableLiveData<DataState<List<SatPass>>>(DataState.Loading)
private val _isFirstLaunchDone = MutableLiveData<Boolean>() private val _isFirstLaunchDone = MutableLiveData<Boolean>()
private var passesProcessing: Job? = null private var passesProcessing: Job? = null
val passes: LiveData<Result<List<SatPass>>> = _passes val passes: LiveData<DataState<List<SatPass>>> = _passes
val isFirstLaunchDone: LiveData<Boolean> = _isFirstLaunchDone val isFirstLaunchDone: LiveData<Boolean> = _isFirstLaunchDone
init { init {
if (preferences.isSetupDone()) { if (preferences.isSetupDone()) {
viewModelScope.launch { viewModelScope.launch {
_passes.postValue(Result.InProgress) _passes.postValue(DataState.Loading)
val dateNow = Date() val dateNow = Date()
val satellites = satelliteRepo.getSelectedSatellites() val satellites = satelliteRepo.getSelectedSatellites()
val stationPos = preferences.loadStationPosition() val stationPos = preferences.loadStationPosition()
@@ -70,7 +70,7 @@ class PassesViewModel @Inject constructor(
fun triggerInitialSetup() { fun triggerInitialSetup() {
preferences.updatePositionFromGPS() preferences.updatePositionFromGPS()
viewModelScope.launch { viewModelScope.launch {
_passes.postValue(Result.InProgress) _passes.postValue(DataState.Loading)
val defaultCatNums = listOf(43700, 25544, 25338, 28654, 33591, 40069, 27607, 24278) val defaultCatNums = listOf(43700, 25544, 25338, 28654, 33591, 40069, 27607, 24278)
val dateNow = Date() val dateNow = Date()
val satellites = satelliteRepo.getSelectedSatellites() val satellites = satelliteRepo.getSelectedSatellites()
@@ -87,7 +87,7 @@ class PassesViewModel @Inject constructor(
fun forceCalculation() { fun forceCalculation() {
viewModelScope.launch { viewModelScope.launch {
_passes.postValue(Result.InProgress) _passes.postValue(DataState.Loading)
passesProcessing?.cancelAndJoin() passesProcessing?.cancelAndJoin()
val dateNow = Date() val dateNow = Date()
val satellites = satelliteRepo.getSelectedSatellites() val satellites = satelliteRepo.getSelectedSatellites()
@@ -117,7 +117,7 @@ class PassesViewModel @Inject constructor(
} }
} }
currentPasses = currentPasses.filter { it.progress < 100 } currentPasses = currentPasses.filter { it.progress < 100 }
_passes.postValue(Result.Success(currentPasses.map { it.copy() })) _passes.postValue(DataState.Success(currentPasses.map { it.copy() }))
delay(1000) delay(1000)
} }
} }
+2 -2
View File
@@ -5,12 +5,12 @@ buildscript {
coroutines_version = '1.5.2-native-mt' coroutines_version = '1.5.2-native-mt'
material_version = '1.4.0' material_version = '1.4.0'
splash_screen_version = '1.0.0-alpha01' splash_screen_version = '1.0.0-alpha01'
constraint_layout_version = '2.1.0' constraint_layout_version = '2.1.1'
lifecycle_version = '2.3.1' lifecycle_version = '2.3.1'
navigation_version = '2.3.5' navigation_version = '2.3.5'
preference_version = '1.1.1' preference_version = '1.1.1'
room_version = '2.3.0' room_version = '2.3.0'
hilt_version = '2.38.1' hilt_version = '2.39'
retrofit_version = '2.9.0' retrofit_version = '2.9.0'
osmdroid_version = '6.1.11' osmdroid_version = '6.1.11'
timber_version = '5.0.1' timber_version = '5.0.1'