migrate timeline api calls from Rx Single to suspending functions (#2690)

* migrate timeline api calls from Rx Single to suspending functions

* fix tests
This commit is contained in:
Konrad Pozniak 2022-09-13 19:47:55 +02:00 committed by GitHub
parent 25ba40a7ec
commit 655ce30031
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 106 deletions

View File

@ -22,7 +22,6 @@ import androidx.paging.RemoteMediator
import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.components.timeline.util.ifExpected
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.viewdata.AttachmentViewData import com.keylesspalace.tusky.viewdata.AttachmentViewData
import kotlinx.coroutines.rx3.await
import retrofit2.HttpException import retrofit2.HttpException
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
@ -39,7 +38,7 @@ class AccountMediaRemoteMediator(
try { try {
val statusResponse = when (loadType) { val statusResponse = when (loadType) {
LoadType.REFRESH -> { LoadType.REFRESH -> {
api.accountStatuses(viewModel.accountId, onlyMedia = true).await() api.accountStatuses(viewModel.accountId, onlyMedia = true)
} }
LoadType.PREPEND -> { LoadType.PREPEND -> {
return MediatorResult.Success(endOfPaginationReached = true) return MediatorResult.Success(endOfPaginationReached = true)
@ -47,7 +46,7 @@ class AccountMediaRemoteMediator(
LoadType.APPEND -> { LoadType.APPEND -> {
val maxId = state.lastItemOrNull()?.statusId val maxId = state.lastItemOrNull()?.statusId
if (maxId != null) { if (maxId != null) {
api.accountStatuses(viewModel.accountId, maxId = maxId, onlyMedia = true).await() api.accountStatuses(viewModel.accountId, maxId = maxId, onlyMedia = true)
} else { } else {
return MediatorResult.Success(endOfPaginationReached = false) return MediatorResult.Success(endOfPaginationReached = false)
} }

View File

@ -30,7 +30,6 @@ import com.keylesspalace.tusky.db.TimelineStatusEntity
import com.keylesspalace.tusky.db.TimelineStatusWithAccount import com.keylesspalace.tusky.db.TimelineStatusWithAccount
import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import kotlinx.coroutines.rx3.await
import retrofit2.HttpException import retrofit2.HttpException
@OptIn(ExperimentalPagingApi::class) @OptIn(ExperimentalPagingApi::class)
@ -71,7 +70,7 @@ class CachedTimelineRemoteMediator(
maxId = cachedTopId, maxId = cachedTopId,
sinceId = topPlaceholderId, // so already existing placeholders don't get accidentally overwritten sinceId = topPlaceholderId, // so already existing placeholders don't get accidentally overwritten
limit = state.config.pageSize limit = state.config.pageSize
).await() )
val statuses = statusResponse.body() val statuses = statusResponse.body()
if (statusResponse.isSuccessful && statuses != null) { if (statusResponse.isSuccessful && statuses != null) {
@ -86,14 +85,14 @@ class CachedTimelineRemoteMediator(
val statusResponse = when (loadType) { val statusResponse = when (loadType) {
LoadType.REFRESH -> { LoadType.REFRESH -> {
api.homeTimeline(sinceId = topPlaceholderId, limit = state.config.pageSize).await() api.homeTimeline(sinceId = topPlaceholderId, limit = state.config.pageSize)
} }
LoadType.PREPEND -> { LoadType.PREPEND -> {
return MediatorResult.Success(endOfPaginationReached = true) return MediatorResult.Success(endOfPaginationReached = true)
} }
LoadType.APPEND -> { LoadType.APPEND -> {
val maxId = state.pages.findLast { it.data.isNotEmpty() }?.data?.lastOrNull()?.status?.serverId val maxId = state.pages.findLast { it.data.isNotEmpty() }?.data?.lastOrNull()?.status?.serverId
api.homeTimeline(maxId = maxId, limit = state.config.pageSize).await() api.homeTimeline(maxId = maxId, limit = state.config.pageSize)
} }
} }

View File

@ -50,7 +50,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.await
import retrofit2.HttpException import retrofit2.HttpException
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.DurationUnit import kotlin.time.DurationUnit
@ -176,7 +175,7 @@ class CachedTimelineViewModel @Inject constructor(
sinceId = nextPlaceholderId, sinceId = nextPlaceholderId,
limit = LOAD_AT_ONCE limit = LOAD_AT_ONCE
) )
}.await() }
val statuses = response.body() val statuses = response.body()
if (!response.isSuccessful || statuses == null) { if (!response.isSuccessful || statuses == null) {

View File

@ -45,7 +45,6 @@ import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.await
import retrofit2.HttpException import retrofit2.HttpException
import retrofit2.Response import retrofit2.Response
import java.io.IOException import java.io.IOException
@ -298,7 +297,7 @@ class NetworkTimelineViewModel @Inject constructor(
Kind.FAVOURITES -> api.favourites(fromId, uptoId, limit) Kind.FAVOURITES -> api.favourites(fromId, uptoId, limit)
Kind.BOOKMARKS -> api.bookmarks(fromId, uptoId, limit) Kind.BOOKMARKS -> api.bookmarks(fromId, uptoId, limit)
Kind.LIST -> api.listTimeline(id!!, fromId, uptoId, limit) Kind.LIST -> api.listTimeline(id!!, fromId, uptoId, limit)
}.await() }
} }
private fun StatusViewData.Concrete.update() { private fun StatusViewData.Concrete.update() {

View File

@ -85,37 +85,38 @@ interface MastodonApi {
fun getFilters(): Single<List<Filter>> fun getFilters(): Single<List<Filter>>
@GET("api/v1/timelines/home") @GET("api/v1/timelines/home")
fun homeTimeline( @Throws(Exception::class)
suspend fun homeTimeline(
@Query("max_id") maxId: String? = null, @Query("max_id") maxId: String? = null,
@Query("since_id") sinceId: String? = null, @Query("since_id") sinceId: String? = null,
@Query("limit") limit: Int? = null @Query("limit") limit: Int? = null
): Single<Response<List<Status>>> ): Response<List<Status>>
@GET("api/v1/timelines/public") @GET("api/v1/timelines/public")
fun publicTimeline( suspend fun publicTimeline(
@Query("local") local: Boolean? = null, @Query("local") local: Boolean? = null,
@Query("max_id") maxId: String? = null, @Query("max_id") maxId: String? = null,
@Query("since_id") sinceId: String? = null, @Query("since_id") sinceId: String? = null,
@Query("limit") limit: Int? = null @Query("limit") limit: Int? = null
): Single<Response<List<Status>>> ): Response<List<Status>>
@GET("api/v1/timelines/tag/{hashtag}") @GET("api/v1/timelines/tag/{hashtag}")
fun hashtagTimeline( suspend fun hashtagTimeline(
@Path("hashtag") hashtag: String, @Path("hashtag") hashtag: String,
@Query("any[]") any: List<String>?, @Query("any[]") any: List<String>?,
@Query("local") local: Boolean?, @Query("local") local: Boolean?,
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Single<Response<List<Status>>> ): Response<List<Status>>
@GET("api/v1/timelines/list/{listId}") @GET("api/v1/timelines/list/{listId}")
fun listTimeline( suspend fun listTimeline(
@Path("listId") listId: String, @Path("listId") listId: String,
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Single<Response<List<Status>>> ): Response<List<Status>>
@GET("api/v1/notifications") @GET("api/v1/notifications")
fun notifications( fun notifications(
@ -317,7 +318,7 @@ interface MastodonApi {
* @param onlyMedia only return statuses that have media attached * @param onlyMedia only return statuses that have media attached
*/ */
@GET("api/v1/accounts/{id}/statuses") @GET("api/v1/accounts/{id}/statuses")
fun accountStatuses( suspend fun accountStatuses(
@Path("id") accountId: String, @Path("id") accountId: String,
@Query("max_id") maxId: String? = null, @Query("max_id") maxId: String? = null,
@Query("since_id") sinceId: String? = null, @Query("since_id") sinceId: String? = null,
@ -325,7 +326,7 @@ interface MastodonApi {
@Query("exclude_replies") excludeReplies: Boolean? = null, @Query("exclude_replies") excludeReplies: Boolean? = null,
@Query("only_media") onlyMedia: Boolean? = null, @Query("only_media") onlyMedia: Boolean? = null,
@Query("pinned") pinned: Boolean? = null @Query("pinned") pinned: Boolean? = null
): Single<Response<List<Status>>> ): Response<List<Status>>
@GET("api/v1/accounts/{id}/followers") @GET("api/v1/accounts/{id}/followers")
fun accountFollowers( fun accountFollowers(
@ -419,18 +420,18 @@ interface MastodonApi {
fun unblockDomain(@Field("domain") domain: String): Call<Any> fun unblockDomain(@Field("domain") domain: String): Call<Any>
@GET("api/v1/favourites") @GET("api/v1/favourites")
fun favourites( suspend fun favourites(
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Single<Response<List<Status>>> ): Response<List<Status>>
@GET("api/v1/bookmarks") @GET("api/v1/bookmarks")
fun bookmarks( suspend fun bookmarks(
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Single<Response<List<Status>>> ): Response<List<Status>>
@GET("api/v1/follow_requests") @GET("api/v1/follow_requests")
fun followRequests( fun followRequests(

View File

@ -17,7 +17,6 @@ import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.db.Converters import com.keylesspalace.tusky.db.Converters
import com.keylesspalace.tusky.db.TimelineStatusWithAccount import com.keylesspalace.tusky.db.TimelineStatusWithAccount
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
@ -30,6 +29,7 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.robolectric.Shadows.shadowOf import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
@ -78,7 +78,7 @@ class CachedTimelineRemoteMediatorTest {
val remoteMediator = CachedTimelineRemoteMediator( val remoteMediator = CachedTimelineRemoteMediator(
accountManager = accountManager, accountManager = accountManager,
api = mock { api = mock {
on { homeTimeline(anyOrNull(), anyOrNull(), anyOrNull()) } doReturn Single.just(Response.error(500, "".toResponseBody())) onBlocking { homeTimeline(anyOrNull(), anyOrNull(), anyOrNull()) } doReturn Response.error(500, "".toResponseBody())
}, },
db = db, db = db,
gson = Gson() gson = Gson()
@ -98,7 +98,7 @@ class CachedTimelineRemoteMediatorTest {
val remoteMediator = CachedTimelineRemoteMediator( val remoteMediator = CachedTimelineRemoteMediator(
accountManager = accountManager, accountManager = accountManager,
api = mock { api = mock {
on { homeTimeline(anyOrNull(), anyOrNull(), anyOrNull()) } doReturn Single.error(IOException()) onBlocking { homeTimeline(anyOrNull(), anyOrNull(), anyOrNull()) } doThrow IOException()
}, },
db = db, db = db,
gson = Gson() gson = Gson()
@ -154,24 +154,20 @@ class CachedTimelineRemoteMediatorTest {
val remoteMediator = CachedTimelineRemoteMediator( val remoteMediator = CachedTimelineRemoteMediator(
accountManager = accountManager, accountManager = accountManager,
api = mock { api = mock {
on { homeTimeline(limit = 3) } doReturn Single.just( onBlocking { homeTimeline(limit = 3) } doReturn Response.success(
Response.success(
listOf( listOf(
mockStatus("8"), mockStatus("8"),
mockStatus("7"), mockStatus("7"),
mockStatus("5") mockStatus("5")
) )
) )
) onBlocking { homeTimeline(maxId = "3", limit = 3) } doReturn Response.success(
on { homeTimeline(maxId = "3", limit = 3) } doReturn Single.just(
Response.success(
listOf( listOf(
mockStatus("3"), mockStatus("3"),
mockStatus("2"), mockStatus("2"),
mockStatus("1") mockStatus("1")
) )
) )
)
}, },
db = db, db = db,
gson = Gson() gson = Gson()
@ -222,24 +218,20 @@ class CachedTimelineRemoteMediatorTest {
val remoteMediator = CachedTimelineRemoteMediator( val remoteMediator = CachedTimelineRemoteMediator(
accountManager = accountManager, accountManager = accountManager,
api = mock { api = mock {
on { homeTimeline(limit = 20) } doReturn Single.just( onBlocking { homeTimeline(limit = 20) } doReturn Response.success(
Response.success(
listOf( listOf(
mockStatus("8"), mockStatus("8"),
mockStatus("7"), mockStatus("7"),
mockStatus("5") mockStatus("5")
) )
) )
) onBlocking { homeTimeline(maxId = "3", limit = 20) } doReturn Response.success(
on { homeTimeline(maxId = "3", limit = 20) } doReturn Single.just(
Response.success(
listOf( listOf(
mockStatus("3"), mockStatus("3"),
mockStatus("2"), mockStatus("2"),
mockStatus("1") mockStatus("1")
) )
) )
)
}, },
db = db, db = db,
gson = Gson() gson = Gson()
@ -287,24 +279,20 @@ class CachedTimelineRemoteMediatorTest {
val remoteMediator = CachedTimelineRemoteMediator( val remoteMediator = CachedTimelineRemoteMediator(
accountManager = accountManager, accountManager = accountManager,
api = mock { api = mock {
on { homeTimeline(limit = 3) } doReturn Single.just( onBlocking { homeTimeline(limit = 3) } doReturn Response.success(
Response.success(
listOf( listOf(
mockStatus("6"), mockStatus("6"),
mockStatus("4"), mockStatus("4"),
mockStatus("3") mockStatus("3")
) )
) )
) onBlocking { homeTimeline(maxId = "3", limit = 3) } doReturn Response.success(
on { homeTimeline(maxId = "3", limit = 3) } doReturn Single.just(
Response.success(
listOf( listOf(
mockStatus("3"), mockStatus("3"),
mockStatus("2"), mockStatus("2"),
mockStatus("1") mockStatus("1")
) )
) )
)
}, },
db = db, db = db,
gson = Gson() gson = Gson()
@ -344,15 +332,13 @@ class CachedTimelineRemoteMediatorTest {
val remoteMediator = CachedTimelineRemoteMediator( val remoteMediator = CachedTimelineRemoteMediator(
accountManager = accountManager, accountManager = accountManager,
api = mock { api = mock {
on { homeTimeline(limit = 20) } doReturn Single.just( onBlocking { homeTimeline(limit = 20) } doReturn Response.success(
Response.success(
listOf( listOf(
mockStatus("5"), mockStatus("5"),
mockStatus("4"), mockStatus("4"),
mockStatus("3") mockStatus("3")
) )
) )
)
}, },
db = db, db = db,
gson = Gson() gson = Gson()
@ -397,17 +383,14 @@ class CachedTimelineRemoteMediatorTest {
val remoteMediator = CachedTimelineRemoteMediator( val remoteMediator = CachedTimelineRemoteMediator(
accountManager = accountManager, accountManager = accountManager,
api = mock { api = mock {
on { homeTimeline(limit = 20) } doReturn Single.just( onBlocking { homeTimeline(limit = 20) } doReturn Response.success(emptyList())
Response.success(emptyList())
) onBlocking { homeTimeline(maxId = "3", limit = 20) } doReturn Response.success(
on { homeTimeline(maxId = "3", limit = 20) } doReturn Single.just(
Response.success(
listOf( listOf(
mockStatus("3"), mockStatus("3"),
mockStatus("1") mockStatus("1")
) )
) )
)
}, },
db = db, db = db,
gson = Gson() gson = Gson()
@ -452,23 +435,19 @@ class CachedTimelineRemoteMediatorTest {
val remoteMediator = CachedTimelineRemoteMediator( val remoteMediator = CachedTimelineRemoteMediator(
accountManager = accountManager, accountManager = accountManager,
api = mock { api = mock {
on { homeTimeline(sinceId = "6", limit = 20) } doReturn Single.just( onBlocking { homeTimeline(sinceId = "6", limit = 20) } doReturn Response.success(
Response.success(
listOf( listOf(
mockStatus("9"), mockStatus("9"),
mockStatus("8"), mockStatus("8"),
mockStatus("7") mockStatus("7")
) )
) )
) onBlocking { homeTimeline(maxId = "8", sinceId = "6", limit = 20) } doReturn Response.success(
on { homeTimeline(maxId = "8", sinceId = "6", limit = 20) } doReturn Single.just(
Response.success(
listOf( listOf(
mockStatus("8"), mockStatus("8"),
mockStatus("7") mockStatus("7")
) )
) )
)
}, },
db = db, db = db,
gson = Gson() gson = Gson()
@ -515,15 +494,13 @@ class CachedTimelineRemoteMediatorTest {
val remoteMediator = CachedTimelineRemoteMediator( val remoteMediator = CachedTimelineRemoteMediator(
accountManager = accountManager, accountManager = accountManager,
api = mock { api = mock {
on { homeTimeline(maxId = "5", limit = 20) } doReturn Single.just( onBlocking { homeTimeline(maxId = "5", limit = 20) } doReturn Response.success(
Response.success(
listOf( listOf(
mockStatus("3"), mockStatus("3"),
mockStatus("2"), mockStatus("2"),
mockStatus("1") mockStatus("1")
) )
) )
)
}, },
db = db, db = db,
gson = Gson() gson = Gson()