Don't hide potential timeline bugs by catching all exceptions (#2372)

* don't hide potential timeline bugs by catching all exceptions

* fix NetworkTimelineRemoteMediatorTest

* improve ifExpected function

* fix code formatting
This commit is contained in:
Konrad Pozniak 2022-03-08 21:39:59 +01:00 committed by GitHub
parent 4d8289b245
commit 34b7a3c8ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 39 additions and 22 deletions

View file

@ -0,0 +1,17 @@
package com.keylesspalace.tusky.components.timeline.util
import retrofit2.HttpException
import java.io.IOException
fun Throwable.isExpected() = this is IOException || this is HttpException
inline fun <T> ifExpected(
t: Throwable,
cb: () -> T
): T {
if (t.isExpected()) {
return cb()
} else {
throw t
}
}

View file

@ -23,6 +23,7 @@ import androidx.room.withTransaction
import com.google.gson.Gson
import com.keylesspalace.tusky.components.timeline.Placeholder
import com.keylesspalace.tusky.components.timeline.toEntity
import com.keylesspalace.tusky.components.timeline.util.ifExpected
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.db.TimelineStatusEntity
@ -109,7 +110,9 @@ class CachedTimelineRemoteMediator(
}
return MediatorResult.Success(endOfPaginationReached = statuses.isEmpty())
} catch (e: Exception) {
return MediatorResult.Error(e)
return ifExpected(e) {
MediatorResult.Error(e)
}
}
}

View file

@ -34,6 +34,7 @@ import com.keylesspalace.tusky.appstore.ReblogEvent
import com.keylesspalace.tusky.components.timeline.Placeholder
import com.keylesspalace.tusky.components.timeline.toEntity
import com.keylesspalace.tusky.components.timeline.toViewData
import com.keylesspalace.tusky.components.timeline.util.ifExpected
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.entity.Poll
@ -191,10 +192,12 @@ class CachedTimelineViewModel @Inject constructor(
}
}
} catch (e: Exception) {
ifExpected(e) {
loadMoreFailed(placeholderId, e)
}
}
}
}
private suspend fun loadMoreFailed(placeholderId: String, e: Exception) {
Log.w("CachedTimelineVM", "failed loading statuses", e)

View file

@ -19,6 +19,7 @@ import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import com.keylesspalace.tusky.components.timeline.util.ifExpected
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.util.HttpHeaderLink
import com.keylesspalace.tusky.util.dec
@ -107,7 +108,9 @@ class NetworkTimelineRemoteMediator(
viewModel.currentSource?.invalidate()
return MediatorResult.Success(endOfPaginationReached = statuses.isEmpty())
} catch (e: Exception) {
return MediatorResult.Error(e)
return ifExpected(e) {
MediatorResult.Error(e)
}
}
}
}

View file

@ -28,6 +28,7 @@ import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.FavoriteEvent
import com.keylesspalace.tusky.appstore.PinEvent
import com.keylesspalace.tusky.appstore.ReblogEvent
import com.keylesspalace.tusky.components.timeline.util.ifExpected
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Status
@ -43,6 +44,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.await
import retrofit2.HttpException
import retrofit2.Response
import java.io.IOException
import javax.inject.Inject
/**
@ -170,10 +172,12 @@ class NetworkTimelineViewModel @Inject constructor(
currentSource?.invalidate()
} catch (e: Exception) {
ifExpected(e) {
loadMoreFailed(placeholderId, e)
}
}
}
}
private fun loadMoreFailed(placeholderId: String, e: Exception) {
Log.w("NetworkTimelineVM", "failed loading statuses", e)
@ -214,6 +218,7 @@ class NetworkTimelineViewModel @Inject constructor(
currentSource?.invalidate()
}
@Throws(IOException::class, HttpException::class)
suspend fun fetchStatusesForKind(
fromId: String?,
uptoId: String?,

View file

@ -33,6 +33,7 @@ import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
import com.keylesspalace.tusky.appstore.ReblogEvent
import com.keylesspalace.tusky.appstore.StatusDeletedEvent
import com.keylesspalace.tusky.appstore.UnfollowEvent
import com.keylesspalace.tusky.components.timeline.util.ifExpected
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.Poll
@ -46,8 +47,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.asFlow
import kotlinx.coroutines.rx3.await
import retrofit2.HttpException
import java.io.IOException
abstract class TimelineViewModel(
private val timelineCases: TimelineCases,
@ -291,19 +290,6 @@ abstract class TimelineViewModel(
}
}
private fun isExpectedRequestException(t: Exception) = t is IOException || t is HttpException
private inline fun ifExpected(
t: Exception,
cb: () -> Unit
) {
if (isExpectedRequestException(t)) {
cb()
} else {
throw t
}
}
companion object {
private const val TAG = "TimelineVM"
internal const val LOAD_AT_ONCE = 30

View file

@ -27,7 +27,7 @@ import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import retrofit2.HttpException
import retrofit2.Response
import java.lang.RuntimeException
import java.io.IOException
@Config(sdk = [29])
@RunWith(AndroidJUnit4::class)
@ -66,7 +66,7 @@ class NetworkTimelineRemoteMediatorTest {
val timelineViewModel: NetworkTimelineViewModel = mock {
on { statusData } doReturn mutableListOf()
onBlocking { fetchStatusesForKind(anyOrNull(), anyOrNull(), anyOrNull()) } doThrow RuntimeException()
onBlocking { fetchStatusesForKind(anyOrNull(), anyOrNull(), anyOrNull()) } doThrow IOException()
}
val remoteMediator = NetworkTimelineRemoteMediator(accountManager, timelineViewModel)
@ -74,7 +74,7 @@ class NetworkTimelineRemoteMediatorTest {
val result = runBlocking { remoteMediator.load(LoadType.REFRESH, state()) }
assertTrue(result is RemoteMediator.MediatorResult.Error)
assertTrue((result as RemoteMediator.MediatorResult.Error).throwable is RuntimeException)
assertTrue((result as RemoteMediator.MediatorResult.Error).throwable is IOException)
}
@Test