From 655ce30031bc274864ac942833303d3ca13b1aa3 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 13 Sep 2022 19:47:55 +0200 Subject: [PATCH] migrate timeline api calls from Rx Single to suspending functions (#2690) * migrate timeline api calls from Rx Single to suspending functions * fix tests --- .../media/AccountMediaRemoteMediator.kt | 5 +- .../viewmodel/CachedTimelineRemoteMediator.kt | 7 +- .../viewmodel/CachedTimelineViewModel.kt | 3 +- .../viewmodel/NetworkTimelineViewModel.kt | 3 +- .../tusky/network/MastodonApi.kt | 29 ++-- .../CachedTimelineRemoteMediatorTest.kt | 139 ++++++++---------- 6 files changed, 80 insertions(+), 106 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaRemoteMediator.kt index 734745f9c..81865b0fe 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaRemoteMediator.kt @@ -22,7 +22,6 @@ import androidx.paging.RemoteMediator import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.viewdata.AttachmentViewData -import kotlinx.coroutines.rx3.await import retrofit2.HttpException @OptIn(ExperimentalPagingApi::class) @@ -39,7 +38,7 @@ class AccountMediaRemoteMediator( try { val statusResponse = when (loadType) { LoadType.REFRESH -> { - api.accountStatuses(viewModel.accountId, onlyMedia = true).await() + api.accountStatuses(viewModel.accountId, onlyMedia = true) } LoadType.PREPEND -> { return MediatorResult.Success(endOfPaginationReached = true) @@ -47,7 +46,7 @@ class AccountMediaRemoteMediator( LoadType.APPEND -> { val maxId = state.lastItemOrNull()?.statusId if (maxId != null) { - api.accountStatuses(viewModel.accountId, maxId = maxId, onlyMedia = true).await() + api.accountStatuses(viewModel.accountId, maxId = maxId, onlyMedia = true) } else { return MediatorResult.Success(endOfPaginationReached = false) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt index ebab4440a..b29ab0cf6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt @@ -30,7 +30,6 @@ import com.keylesspalace.tusky.db.TimelineStatusEntity import com.keylesspalace.tusky.db.TimelineStatusWithAccount import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.MastodonApi -import kotlinx.coroutines.rx3.await import retrofit2.HttpException @OptIn(ExperimentalPagingApi::class) @@ -71,7 +70,7 @@ class CachedTimelineRemoteMediator( maxId = cachedTopId, sinceId = topPlaceholderId, // so already existing placeholders don't get accidentally overwritten limit = state.config.pageSize - ).await() + ) val statuses = statusResponse.body() if (statusResponse.isSuccessful && statuses != null) { @@ -86,14 +85,14 @@ class CachedTimelineRemoteMediator( val statusResponse = when (loadType) { LoadType.REFRESH -> { - api.homeTimeline(sinceId = topPlaceholderId, limit = state.config.pageSize).await() + api.homeTimeline(sinceId = topPlaceholderId, limit = state.config.pageSize) } LoadType.PREPEND -> { return MediatorResult.Success(endOfPaginationReached = true) } LoadType.APPEND -> { 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) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt index 97bc625b4..217055dc0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt @@ -50,7 +50,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import kotlinx.coroutines.rx3.await import retrofit2.HttpException import javax.inject.Inject import kotlin.time.DurationUnit @@ -176,7 +175,7 @@ class CachedTimelineViewModel @Inject constructor( sinceId = nextPlaceholderId, limit = LOAD_AT_ONCE ) - }.await() + } val statuses = response.body() if (!response.isSuccessful || statuses == null) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt index 8c81df1db..7335e8f36 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt @@ -45,7 +45,6 @@ import kotlinx.coroutines.asExecutor import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import kotlinx.coroutines.rx3.await import retrofit2.HttpException import retrofit2.Response import java.io.IOException @@ -298,7 +297,7 @@ class NetworkTimelineViewModel @Inject constructor( Kind.FAVOURITES -> api.favourites(fromId, uptoId, limit) Kind.BOOKMARKS -> api.bookmarks(fromId, uptoId, limit) Kind.LIST -> api.listTimeline(id!!, fromId, uptoId, limit) - }.await() + } } private fun StatusViewData.Concrete.update() { diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt index 50d090f43..ca322031e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -85,37 +85,38 @@ interface MastodonApi { fun getFilters(): Single> @GET("api/v1/timelines/home") - fun homeTimeline( + @Throws(Exception::class) + suspend fun homeTimeline( @Query("max_id") maxId: String? = null, @Query("since_id") sinceId: String? = null, @Query("limit") limit: Int? = null - ): Single>> + ): Response> @GET("api/v1/timelines/public") - fun publicTimeline( + suspend fun publicTimeline( @Query("local") local: Boolean? = null, @Query("max_id") maxId: String? = null, @Query("since_id") sinceId: String? = null, @Query("limit") limit: Int? = null - ): Single>> + ): Response> @GET("api/v1/timelines/tag/{hashtag}") - fun hashtagTimeline( + suspend fun hashtagTimeline( @Path("hashtag") hashtag: String, @Query("any[]") any: List?, @Query("local") local: Boolean?, @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Single>> + ): Response> @GET("api/v1/timelines/list/{listId}") - fun listTimeline( + suspend fun listTimeline( @Path("listId") listId: String, @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Single>> + ): Response> @GET("api/v1/notifications") fun notifications( @@ -317,7 +318,7 @@ interface MastodonApi { * @param onlyMedia only return statuses that have media attached */ @GET("api/v1/accounts/{id}/statuses") - fun accountStatuses( + suspend fun accountStatuses( @Path("id") accountId: String, @Query("max_id") maxId: String? = null, @Query("since_id") sinceId: String? = null, @@ -325,7 +326,7 @@ interface MastodonApi { @Query("exclude_replies") excludeReplies: Boolean? = null, @Query("only_media") onlyMedia: Boolean? = null, @Query("pinned") pinned: Boolean? = null - ): Single>> + ): Response> @GET("api/v1/accounts/{id}/followers") fun accountFollowers( @@ -419,18 +420,18 @@ interface MastodonApi { fun unblockDomain(@Field("domain") domain: String): Call @GET("api/v1/favourites") - fun favourites( + suspend fun favourites( @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Single>> + ): Response> @GET("api/v1/bookmarks") - fun bookmarks( + suspend fun bookmarks( @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Single>> + ): Response> @GET("api/v1/follow_requests") fun followRequests( diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt index c117cf59e..927495cfc 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt @@ -17,7 +17,6 @@ import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.Converters import com.keylesspalace.tusky.db.TimelineStatusWithAccount -import io.reactivex.rxjava3.core.Single import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import okhttp3.ResponseBody.Companion.toResponseBody @@ -30,6 +29,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock import org.robolectric.Shadows.shadowOf import org.robolectric.annotation.Config @@ -78,7 +78,7 @@ class CachedTimelineRemoteMediatorTest { val remoteMediator = CachedTimelineRemoteMediator( accountManager = accountManager, 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, gson = Gson() @@ -98,7 +98,7 @@ class CachedTimelineRemoteMediatorTest { val remoteMediator = CachedTimelineRemoteMediator( accountManager = accountManager, api = mock { - on { homeTimeline(anyOrNull(), anyOrNull(), anyOrNull()) } doReturn Single.error(IOException()) + onBlocking { homeTimeline(anyOrNull(), anyOrNull(), anyOrNull()) } doThrow IOException() }, db = db, gson = Gson() @@ -154,22 +154,18 @@ class CachedTimelineRemoteMediatorTest { val remoteMediator = CachedTimelineRemoteMediator( accountManager = accountManager, api = mock { - on { homeTimeline(limit = 3) } doReturn Single.just( - Response.success( - listOf( - mockStatus("8"), - mockStatus("7"), - mockStatus("5") - ) + onBlocking { homeTimeline(limit = 3) } doReturn Response.success( + listOf( + mockStatus("8"), + mockStatus("7"), + mockStatus("5") ) ) - on { homeTimeline(maxId = "3", limit = 3) } doReturn Single.just( - Response.success( - listOf( - mockStatus("3"), - mockStatus("2"), - mockStatus("1") - ) + onBlocking { homeTimeline(maxId = "3", limit = 3) } doReturn Response.success( + listOf( + mockStatus("3"), + mockStatus("2"), + mockStatus("1") ) ) }, @@ -222,22 +218,18 @@ class CachedTimelineRemoteMediatorTest { val remoteMediator = CachedTimelineRemoteMediator( accountManager = accountManager, api = mock { - on { homeTimeline(limit = 20) } doReturn Single.just( - Response.success( - listOf( - mockStatus("8"), - mockStatus("7"), - mockStatus("5") - ) + onBlocking { homeTimeline(limit = 20) } doReturn Response.success( + listOf( + mockStatus("8"), + mockStatus("7"), + mockStatus("5") ) ) - on { homeTimeline(maxId = "3", limit = 20) } doReturn Single.just( - Response.success( - listOf( - mockStatus("3"), - mockStatus("2"), - mockStatus("1") - ) + onBlocking { homeTimeline(maxId = "3", limit = 20) } doReturn Response.success( + listOf( + mockStatus("3"), + mockStatus("2"), + mockStatus("1") ) ) }, @@ -287,22 +279,18 @@ class CachedTimelineRemoteMediatorTest { val remoteMediator = CachedTimelineRemoteMediator( accountManager = accountManager, api = mock { - on { homeTimeline(limit = 3) } doReturn Single.just( - Response.success( - listOf( - mockStatus("6"), - mockStatus("4"), - mockStatus("3") - ) + onBlocking { homeTimeline(limit = 3) } doReturn Response.success( + listOf( + mockStatus("6"), + mockStatus("4"), + mockStatus("3") ) ) - on { homeTimeline(maxId = "3", limit = 3) } doReturn Single.just( - Response.success( - listOf( - mockStatus("3"), - mockStatus("2"), - mockStatus("1") - ) + onBlocking { homeTimeline(maxId = "3", limit = 3) } doReturn Response.success( + listOf( + mockStatus("3"), + mockStatus("2"), + mockStatus("1") ) ) }, @@ -344,13 +332,11 @@ class CachedTimelineRemoteMediatorTest { val remoteMediator = CachedTimelineRemoteMediator( accountManager = accountManager, api = mock { - on { homeTimeline(limit = 20) } doReturn Single.just( - Response.success( - listOf( - mockStatus("5"), - mockStatus("4"), - mockStatus("3") - ) + onBlocking { homeTimeline(limit = 20) } doReturn Response.success( + listOf( + mockStatus("5"), + mockStatus("4"), + mockStatus("3") ) ) }, @@ -397,15 +383,12 @@ class CachedTimelineRemoteMediatorTest { val remoteMediator = CachedTimelineRemoteMediator( accountManager = accountManager, api = mock { - on { homeTimeline(limit = 20) } doReturn Single.just( - Response.success(emptyList()) - ) - on { homeTimeline(maxId = "3", limit = 20) } doReturn Single.just( - Response.success( - listOf( - mockStatus("3"), - mockStatus("1") - ) + onBlocking { homeTimeline(limit = 20) } doReturn Response.success(emptyList()) + + onBlocking { homeTimeline(maxId = "3", limit = 20) } doReturn Response.success( + listOf( + mockStatus("3"), + mockStatus("1") ) ) }, @@ -452,21 +435,17 @@ class CachedTimelineRemoteMediatorTest { val remoteMediator = CachedTimelineRemoteMediator( accountManager = accountManager, api = mock { - on { homeTimeline(sinceId = "6", limit = 20) } doReturn Single.just( - Response.success( - listOf( - mockStatus("9"), - mockStatus("8"), - mockStatus("7") - ) + onBlocking { homeTimeline(sinceId = "6", limit = 20) } doReturn Response.success( + listOf( + mockStatus("9"), + mockStatus("8"), + mockStatus("7") ) ) - on { homeTimeline(maxId = "8", sinceId = "6", limit = 20) } doReturn Single.just( - Response.success( - listOf( - mockStatus("8"), - mockStatus("7") - ) + onBlocking { homeTimeline(maxId = "8", sinceId = "6", limit = 20) } doReturn Response.success( + listOf( + mockStatus("8"), + mockStatus("7") ) ) }, @@ -515,13 +494,11 @@ class CachedTimelineRemoteMediatorTest { val remoteMediator = CachedTimelineRemoteMediator( accountManager = accountManager, api = mock { - on { homeTimeline(maxId = "5", limit = 20) } doReturn Single.just( - Response.success( - listOf( - mockStatus("3"), - mockStatus("2"), - mockStatus("1") - ) + onBlocking { homeTimeline(maxId = "5", limit = 20) } doReturn Response.success( + listOf( + mockStatus("3"), + mockStatus("2"), + mockStatus("1") ) ) },