use KotlinResultCallAdapter instead of NetworkCallAdapter
This commit is contained in:
parent
466dba6096
commit
f1072e0e6b
|
@ -65,6 +65,8 @@ dependencies {
|
|||
val daggerVersion = "2.40.5"
|
||||
val jUnitVersion = "5.8.2"
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
|
||||
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
implementation("androidx.appcompat:appcompat:1.4.1")
|
||||
implementation("androidx.activity:activity-ktx:1.4.0")
|
||||
|
@ -98,7 +100,7 @@ dependencies {
|
|||
implementation("com.squareup.moshi:moshi-adapters:$moshiVersion")
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion")
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
|
||||
implementation("at.connyduck:kotlin-result-calladapter:1.0.0")
|
||||
|
||||
implementation("com.fxn769:pix:1.5.6")
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ import at.connyduck.pixelcat.components.util.getMimeType
|
|||
import at.connyduck.pixelcat.db.AccountManager
|
||||
import at.connyduck.pixelcat.model.NewStatus
|
||||
import at.connyduck.pixelcat.network.FediverseApi
|
||||
import at.connyduck.pixelcat.network.calladapter.NetworkResponseError
|
||||
import dagger.android.DaggerService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -49,6 +48,7 @@ import okhttp3.MediaType.Companion.toMediaType
|
|||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.UUID
|
||||
|
@ -173,7 +173,7 @@ class SendStatusService : DaggerService(), CoroutineScope {
|
|||
account.domain,
|
||||
statusToSend.idempotencyKey,
|
||||
newStatus
|
||||
).fold<Any?>(
|
||||
).fold(
|
||||
{
|
||||
statusesToSend.remove(id)
|
||||
stopSelfWhenDone()
|
||||
|
@ -192,20 +192,8 @@ class SendStatusService : DaggerService(), CoroutineScope {
|
|||
val statusToSend = statusesToSend[id] ?: return
|
||||
|
||||
when (error) {
|
||||
is NetworkResponseError.ApiError, is UnrecoverableError -> {
|
||||
// the server refused to accept the status, save toot & show error message
|
||||
// TODO saveToDrafts
|
||||
|
||||
val builder = NotificationCompat.Builder(this@SendStatusService, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_cat)
|
||||
.setContentTitle(getString(R.string.send_status_notification_error_title))
|
||||
// .setContentText(getString(R.string.send_toot_notification_saved_content))
|
||||
.setColor(getColorForAttr(android.R.attr.colorPrimary))
|
||||
|
||||
notificationManager.cancel(id)
|
||||
notificationManager.notify(errorNotificationId--, builder.build())
|
||||
}
|
||||
else -> {
|
||||
is IOException -> {
|
||||
// possibly a network problem, we might still have a chance sending the status
|
||||
var backoff = TimeUnit.SECONDS.toMillis(statusToSend.retries.toLong())
|
||||
if (backoff > MAX_RETRY_INTERVAL) {
|
||||
backoff = MAX_RETRY_INTERVAL
|
||||
|
@ -220,6 +208,19 @@ class SendStatusService : DaggerService(), CoroutineScope {
|
|||
backoff
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// the server refused to accept the status, save toot & show error message
|
||||
// TODO saveToDrafts
|
||||
|
||||
val builder = NotificationCompat.Builder(this@SendStatusService, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_cat)
|
||||
.setContentTitle(getString(R.string.send_status_notification_error_title))
|
||||
// .setContentText(getString(R.string.send_toot_notification_saved_content))
|
||||
.setColor(getColorForAttr(android.R.attr.colorPrimary))
|
||||
|
||||
notificationManager.cancel(id)
|
||||
notificationManager.notify(errorNotificationId--, builder.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import at.connyduck.pixelcat.db.entitity.StatusEntity
|
|||
import at.connyduck.pixelcat.db.entitity.toEntity
|
||||
import at.connyduck.pixelcat.model.Status
|
||||
import at.connyduck.pixelcat.network.FediverseApi
|
||||
import at.connyduck.pixelcat.network.calladapter.NetworkResponse
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -43,8 +42,8 @@ class TimelineUseCases @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun NetworkResponse<Status>.updateStatusInDb() {
|
||||
fold<Any?>(
|
||||
private suspend fun Result<Status>.updateStatusInDb() {
|
||||
fold(
|
||||
{ updatedStatus ->
|
||||
val accountId = accountManager.activeAccount()?.id!!
|
||||
val updatedStatusEntity = updatedStatus.toEntity(accountId)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package at.connyduck.pixelcat.dagger
|
||||
|
||||
import at.connyduck.calladapter.kotlinresult.KotlinResultCallAdapterFactory
|
||||
import at.connyduck.pixelcat.BuildConfig
|
||||
import at.connyduck.pixelcat.db.AccountManager
|
||||
import at.connyduck.pixelcat.model.Notification
|
||||
|
@ -26,7 +27,6 @@ import at.connyduck.pixelcat.network.FediverseApi
|
|||
import at.connyduck.pixelcat.network.InstanceSwitchAuthInterceptor
|
||||
import at.connyduck.pixelcat.network.RefreshTokenAuthenticator
|
||||
import at.connyduck.pixelcat.network.UserAgentInterceptor
|
||||
import at.connyduck.pixelcat.network.calladapter.NetworkResponseAdapterFactory
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapters.EnumJsonAdapter
|
||||
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
|
||||
|
@ -82,7 +82,7 @@ class NetworkModule {
|
|||
return Retrofit.Builder()
|
||||
.baseUrl("https://" + FediverseApi.PLACEHOLDER_DOMAIN)
|
||||
.client(httpClient)
|
||||
.addCallAdapterFactory(NetworkResponseAdapterFactory())
|
||||
.addCallAdapterFactory(KotlinResultCallAdapterFactory.create())
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import at.connyduck.pixelcat.model.Notification
|
|||
import at.connyduck.pixelcat.model.Relationship
|
||||
import at.connyduck.pixelcat.model.Status
|
||||
import at.connyduck.pixelcat.model.StatusContext
|
||||
import at.connyduck.pixelcat.network.calladapter.NetworkResponse
|
||||
import okhttp3.MultipartBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Field
|
||||
|
@ -57,7 +56,7 @@ interface FediverseApi {
|
|||
@Field("website") clientWebsite: String,
|
||||
@Field("redirect_uris") redirectUris: String,
|
||||
@Field("scopes") scopes: String
|
||||
): NetworkResponse<AppCredentials>
|
||||
): Result<AppCredentials>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("oauth/token")
|
||||
|
@ -68,7 +67,7 @@ interface FediverseApi {
|
|||
@Field("redirect_uri") redirectUri: String,
|
||||
@Field("code") code: String,
|
||||
@Field("grant_type") grantType: String = "authorization_code"
|
||||
): NetworkResponse<AccessToken>
|
||||
): Result<AccessToken>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("oauth/token")
|
||||
|
@ -78,17 +77,17 @@ interface FediverseApi {
|
|||
@Field("client_secret") clientSecret: String,
|
||||
@Field("refresh_token") refreshToken: String,
|
||||
@Field("grant_type") grantType: String = "refresh_token"
|
||||
): NetworkResponse<AccessToken>
|
||||
): Result<AccessToken>
|
||||
|
||||
@GET("api/v1/accounts/verify_credentials")
|
||||
suspend fun accountVerifyCredentials(): NetworkResponse<Account>
|
||||
suspend fun accountVerifyCredentials(): Result<Account>
|
||||
|
||||
@GET("api/v1/timelines/home")
|
||||
suspend fun homeTimeline(
|
||||
@Query("max_id") maxId: String? = null,
|
||||
@Query("since_id") sinceId: String? = null,
|
||||
@Query("limit") limit: Int? = null
|
||||
): NetworkResponse<List<Status>>
|
||||
): Result<List<Status>>
|
||||
|
||||
@GET("api/v1/accounts/{id}/statuses")
|
||||
suspend fun accountTimeline(
|
||||
|
@ -99,12 +98,12 @@ interface FediverseApi {
|
|||
@Query("exclude_replies") excludeReplies: Boolean? = false,
|
||||
@Query("only_media") onlyMedia: Boolean? = true,
|
||||
@Query("pinned") pinned: Boolean? = false
|
||||
): NetworkResponse<Status>
|
||||
): Result<Status>
|
||||
|
||||
@GET("api/v1/accounts/{id}")
|
||||
suspend fun account(
|
||||
@Path("id") accountId: String
|
||||
): NetworkResponse<Account>
|
||||
): Result<Account>
|
||||
|
||||
@GET("api/v1/accounts/{id}/statuses")
|
||||
suspend fun accountStatuses(
|
||||
|
@ -114,50 +113,50 @@ interface FediverseApi {
|
|||
@Query("limit") limit: Int? = null,
|
||||
@Query("only_media") onlyMedia: Boolean? = null,
|
||||
@Query("exclude_reblogs") excludeReblogs: Boolean? = null
|
||||
): NetworkResponse<List<Status>>
|
||||
): Result<List<Status>>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("api/v1/accounts/{id}/follow")
|
||||
suspend fun followAccount(
|
||||
@Path("id") accountId: String,
|
||||
@Field("reblogs") showReblogs: Boolean
|
||||
): NetworkResponse<Relationship>
|
||||
): Result<Relationship>
|
||||
|
||||
@POST("api/v1/accounts/{id}/unfollow")
|
||||
suspend fun unfollowAccount(
|
||||
@Path("id") accountId: String
|
||||
): NetworkResponse<Relationship>
|
||||
): Result<Relationship>
|
||||
|
||||
@POST("api/v1/accounts/{id}/block")
|
||||
suspend fun blockAccount(
|
||||
@Path("id") accountId: String
|
||||
): NetworkResponse<Relationship>
|
||||
): Result<Relationship>
|
||||
|
||||
@POST("api/v1/accounts/{id}/unblock")
|
||||
suspend fun unblockAccount(
|
||||
@Path("id") accountId: String
|
||||
): NetworkResponse<Relationship>
|
||||
): Result<Relationship>
|
||||
|
||||
@POST("api/v1/accounts/{id}/mute")
|
||||
suspend fun muteAccount(
|
||||
@Path("id") accountId: String
|
||||
): NetworkResponse<Relationship>
|
||||
): Result<Relationship>
|
||||
|
||||
@POST("api/v1/accounts/{id}/unmute")
|
||||
suspend fun unmuteAccount(
|
||||
@Path("id") accountId: String
|
||||
): NetworkResponse<Relationship>
|
||||
): Result<Relationship>
|
||||
|
||||
@GET("api/v1/accounts/relationships")
|
||||
suspend fun relationships(
|
||||
@Query("id[]") accountIds: List<String>
|
||||
): NetworkResponse<List<Relationship>>
|
||||
): Result<List<Relationship>>
|
||||
|
||||
@Multipart
|
||||
@POST("api/v1/media")
|
||||
suspend fun uploadMedia(
|
||||
@Part file: MultipartBody.Part
|
||||
): NetworkResponse<Attachment>
|
||||
): Result<Attachment>
|
||||
|
||||
@POST("api/v1/statuses")
|
||||
suspend fun createStatus(
|
||||
|
@ -165,42 +164,42 @@ interface FediverseApi {
|
|||
@Header(DOMAIN_HEADER) domain: String,
|
||||
@Header("Idempotency-Key") idempotencyKey: String,
|
||||
@Body status: NewStatus
|
||||
): NetworkResponse<Status>
|
||||
): Result<Status>
|
||||
|
||||
@POST("api/v1/statuses")
|
||||
suspend fun reply(
|
||||
@Body status: NewStatus
|
||||
): NetworkResponse<Status>
|
||||
): Result<Status>
|
||||
|
||||
@POST("api/v1/statuses/{id}/favourite")
|
||||
suspend fun favouriteStatus(
|
||||
@Path("id") statusId: String
|
||||
): NetworkResponse<Status>
|
||||
): Result<Status>
|
||||
|
||||
@POST("api/v1/statuses/{id}/unfavourite")
|
||||
suspend fun unfavouriteStatus(
|
||||
@Path("id") statusId: String
|
||||
): NetworkResponse<Status>
|
||||
): Result<Status>
|
||||
|
||||
@POST("api/v1/statuses/{id}/reblog")
|
||||
suspend fun reblogStatus(
|
||||
@Path("id") statusId: String
|
||||
): NetworkResponse<Status>
|
||||
): Result<Status>
|
||||
|
||||
@POST("api/v1/statuses/{id}/unreblog")
|
||||
suspend fun unreblogStatus(
|
||||
@Path("id") statusId: String
|
||||
): NetworkResponse<Status>
|
||||
): Result<Status>
|
||||
|
||||
@GET("api/v1/statuses/{id}")
|
||||
suspend fun status(
|
||||
@Path("id") statusId: String
|
||||
): NetworkResponse<Status>
|
||||
): Result<Status>
|
||||
|
||||
@GET("api/v1/statuses/{id}/context")
|
||||
suspend fun statusContext(
|
||||
@Path("id") statusId: String
|
||||
): NetworkResponse<StatusContext>
|
||||
): Result<StatusContext>
|
||||
|
||||
@GET("api/v1/notifications")
|
||||
suspend fun notifications(
|
||||
|
@ -208,5 +207,5 @@ interface FediverseApi {
|
|||
@Query("since_id") sinceId: String? = null,
|
||||
@Query("limit") limit: Int? = null,
|
||||
@Query("exclude_types[]") excludes: Set<String>? = null
|
||||
): NetworkResponse<List<Notification>>
|
||||
): Result<List<Notification>>
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Conny Duck
|
||||
*
|
||||
* This file is part of Pixelcat.
|
||||
*
|
||||
* Pixelcat is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Pixelcat is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package at.connyduck.pixelcat.network.calladapter
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class NetworkCallAdapter<S : Any>(
|
||||
private val successType: Type
|
||||
) : CallAdapter<S, Call<NetworkResponse<S>>> {
|
||||
|
||||
override fun responseType(): Type = successType
|
||||
|
||||
override fun adapt(call: Call<S>): Call<NetworkResponse<S>> {
|
||||
return NetworkResponseCall(call)
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Conny Duck
|
||||
*
|
||||
* This file is part of Pixelcat.
|
||||
*
|
||||
* Pixelcat is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Pixelcat is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package at.connyduck.pixelcat.network.calladapter
|
||||
|
||||
import java.io.IOException
|
||||
|
||||
sealed class NetworkResponse<out A : Any> {
|
||||
|
||||
data class Success<T : Any>(val body: T) : NetworkResponse<T>()
|
||||
|
||||
data class Failure(val reason: NetworkResponseError) : NetworkResponse<Nothing>()
|
||||
|
||||
inline fun <B> fold(onSuccess: (A) -> B, onFailure: (NetworkResponseError) -> B): B = when (this) {
|
||||
is Success -> onSuccess(body)
|
||||
is Failure -> onFailure(reason)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class NetworkResponseError : Throwable() {
|
||||
|
||||
data class ApiError(val code: Int) : NetworkResponseError()
|
||||
|
||||
/**
|
||||
* Network error
|
||||
*/
|
||||
data class NetworkError(val error: IOException) : NetworkResponseError()
|
||||
|
||||
/**
|
||||
* For example, json parsing error
|
||||
*/
|
||||
data class UnknownError(val error: Throwable?) : NetworkResponseError()
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Conny Duck
|
||||
*
|
||||
* This file is part of Pixelcat.
|
||||
*
|
||||
* Pixelcat is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Pixelcat is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package at.connyduck.pixelcat.network.calladapter
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
import retrofit2.Retrofit
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class NetworkResponseAdapterFactory : CallAdapter.Factory() {
|
||||
|
||||
override fun get(
|
||||
returnType: Type,
|
||||
annotations: Array<Annotation>,
|
||||
retrofit: Retrofit
|
||||
): CallAdapter<*, *>? {
|
||||
|
||||
// suspend functions wrap the response type in `Call`
|
||||
if (Call::class.java != getRawType(returnType)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// check first that the return type is `ParameterizedType`
|
||||
check(returnType is ParameterizedType) {
|
||||
"return type must be parameterized as Call<NetworkResponse<<Foo>> or Call<NetworkResponse<out Foo>>"
|
||||
}
|
||||
|
||||
// get the response type inside the `Call` type
|
||||
val responseType = getParameterUpperBound(0, returnType)
|
||||
// if the response type is not ApiResponse then we can't handle this type, so we return null
|
||||
if (getRawType(responseType) != NetworkResponse::class.java) {
|
||||
return null
|
||||
}
|
||||
|
||||
// the response type is ApiResponse and should be parameterized
|
||||
check(responseType is ParameterizedType) { "Response must be parameterized as NetworkResponse<Foo> or NetworkResponse<out Foo>" }
|
||||
|
||||
val successBodyType = getParameterUpperBound(0, responseType)
|
||||
|
||||
return NetworkCallAdapter<Any>(successBodyType)
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Conny Duck
|
||||
*
|
||||
* This file is part of Pixelcat.
|
||||
*
|
||||
* Pixelcat is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Pixelcat is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package at.connyduck.pixelcat.network.calladapter
|
||||
|
||||
import okhttp3.Request
|
||||
import okio.Timeout
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
internal class NetworkResponseCall<S : Any>(
|
||||
private val delegate: Call<S>
|
||||
) : Call<NetworkResponse<S>> {
|
||||
|
||||
override fun enqueue(callback: Callback<NetworkResponse<S>>) {
|
||||
return delegate.enqueue(
|
||||
object : Callback<S> {
|
||||
override fun onResponse(call: Call<S>, response: Response<S>) {
|
||||
val body = response.body()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
if (body != null) {
|
||||
callback.onResponse(
|
||||
this@NetworkResponseCall,
|
||||
Response.success(NetworkResponse.Success(body))
|
||||
)
|
||||
} else {
|
||||
// Response is successful but the body is null
|
||||
callback.onResponse(
|
||||
this@NetworkResponseCall,
|
||||
Response.success(
|
||||
NetworkResponse.Failure(
|
||||
NetworkResponseError.ApiError(
|
||||
response.code()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
callback.onResponse(
|
||||
this@NetworkResponseCall,
|
||||
Response.success(
|
||||
NetworkResponse.Failure(
|
||||
NetworkResponseError.ApiError(
|
||||
response.code()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<S>, throwable: Throwable) {
|
||||
val networkResponse = when (throwable) {
|
||||
is IOException -> NetworkResponse.Failure(
|
||||
NetworkResponseError.NetworkError(
|
||||
throwable
|
||||
)
|
||||
)
|
||||
else -> NetworkResponse.Failure(NetworkResponseError.UnknownError(throwable))
|
||||
}
|
||||
callback.onResponse(this@NetworkResponseCall, Response.success(networkResponse))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun isExecuted() = delegate.isExecuted
|
||||
|
||||
override fun clone() = NetworkResponseCall(delegate.clone())
|
||||
|
||||
override fun isCanceled() = delegate.isCanceled
|
||||
|
||||
override fun cancel() = delegate.cancel()
|
||||
|
||||
override fun execute(): Response<NetworkResponse<S>> {
|
||||
throw UnsupportedOperationException("NetworkResponseCall doesn't support synchronized execution")
|
||||
}
|
||||
|
||||
override fun request(): Request = delegate.request()
|
||||
|
||||
override fun timeout(): Timeout = delegate.timeout()
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Conny Duck
|
||||
*
|
||||
* This file is part of Pixelcat.
|
||||
*
|
||||
* Pixelcat is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Pixelcat is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package at.connyduck.pixelcat.network.calladapter
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import okhttp3.mockwebserver.SocketPolicy
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import java.io.IOException
|
||||
|
||||
class ApiTest {
|
||||
|
||||
private var mockWebServer = MockWebServer()
|
||||
|
||||
private lateinit var api: TestApi
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockWebServer.start()
|
||||
|
||||
val moshi = Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
|
||||
api = Retrofit.Builder()
|
||||
.baseUrl(mockWebServer.url("/"))
|
||||
.addCallAdapterFactory(NetworkResponseAdapterFactory())
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.client(OkHttpClient())
|
||||
.build()
|
||||
.create(TestApi::class.java)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun shutdown() {
|
||||
mockWebServer.shutdown()
|
||||
}
|
||||
|
||||
private fun mockResponse(responseCode: Int, body: String = "") = MockResponse()
|
||||
.setResponseCode(responseCode)
|
||||
.setBody(body)
|
||||
|
||||
@Test
|
||||
fun `should return the correct test object`() {
|
||||
val response = mockResponse(
|
||||
200,
|
||||
"""
|
||||
{
|
||||
"lets": "not",
|
||||
"test": 1
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
mockWebServer.enqueue(response)
|
||||
|
||||
val responseObject = runBlocking {
|
||||
api.testEndpoint()
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
NetworkResponse.Success(TestResponseClass("not", 1)),
|
||||
responseObject
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return a ApiError failure when the server returns error 500`() {
|
||||
val errorCode = 500
|
||||
val response = mockResponse(errorCode)
|
||||
|
||||
mockWebServer.enqueue(response)
|
||||
|
||||
val responseObject = runBlocking {
|
||||
api.testEndpoint()
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
NetworkResponse.Failure(NetworkResponseError.ApiError(errorCode)),
|
||||
responseObject
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return a NetworkError failure when the network fails`() {
|
||||
mockWebServer.enqueue(MockResponse().apply { socketPolicy = SocketPolicy.DISCONNECT_AFTER_REQUEST })
|
||||
val responseObject = runBlocking {
|
||||
api.testEndpoint()
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
NetworkResponse.Failure(
|
||||
NetworkResponseError.NetworkError(
|
||||
object : IOException() {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return (other is IOException)
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
responseObject
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Conny Duck
|
||||
*
|
||||
* This file is part of Pixelcat.
|
||||
*
|
||||
* Pixelcat is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Pixelcat is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package at.connyduck.pixelcat.network.calladapter
|
||||
|
||||
import com.squareup.moshi.Types
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
|
||||
class NetworkResponseAdapterFactoryTest {
|
||||
|
||||
private val retrofit = Retrofit.Builder().baseUrl("http://example.com").build()
|
||||
|
||||
@Test
|
||||
fun `should return a NetworkResponseCallAdapter when the type is supported`() {
|
||||
val networkResponseType =
|
||||
Types.newParameterizedType(NetworkResponse::class.java, TestResponseClass::class.java)
|
||||
val callType = Types.newParameterizedType(Call::class.java, networkResponseType)
|
||||
|
||||
val adapter = NetworkResponseAdapterFactory().get(callType, arrayOf(), retrofit)
|
||||
|
||||
assertEquals(TestResponseClass::class.java, adapter?.responseType())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return null if the type is not supported`() {
|
||||
|
||||
val adapter = NetworkResponseAdapterFactory().get(TestResponseClass::class.java, arrayOf(), retrofit)
|
||||
|
||||
assertNull(adapter)
|
||||
}
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Conny Duck
|
||||
*
|
||||
* This file is part of Pixelcat.
|
||||
*
|
||||
* Pixelcat is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Pixelcat is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package at.connyduck.pixelcat.network.calladapter
|
||||
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
class NetworkResponseCallTest {
|
||||
|
||||
private val backingCall = TestCall<String>()
|
||||
private val networkCall = NetworkResponseCall(backingCall)
|
||||
|
||||
@Test
|
||||
fun `should throw an error when invoking 'execute'`() {
|
||||
assertThrows<UnsupportedOperationException> {
|
||||
networkCall.execute()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should delegate properties to backing call`() {
|
||||
with(networkCall) {
|
||||
assertEquals(isExecuted, backingCall.isExecuted)
|
||||
assertEquals(isCanceled, backingCall.isCanceled)
|
||||
assertEquals(request(), backingCall.request())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return new instance when cloned`() {
|
||||
val clonedCall = networkCall.clone()
|
||||
assert(clonedCall !== networkCall)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should cancel backing call as well when cancelled`() {
|
||||
networkCall.cancel()
|
||||
assert(backingCall.isCanceled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should parse successful call as NetworkResponse Success`() {
|
||||
val body = "Test body"
|
||||
networkCall.enqueue(
|
||||
object : Callback<NetworkResponse<String>> {
|
||||
override fun onResponse(
|
||||
call: Call<NetworkResponse<String>>,
|
||||
response: Response<NetworkResponse<String>>
|
||||
) {
|
||||
assertTrue(response.isSuccessful)
|
||||
assertEquals(
|
||||
response.body(),
|
||||
NetworkResponse.Success(body)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<NetworkResponse<String>>, t: Throwable) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
)
|
||||
backingCall.complete(body)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should parse call with 404 error code as ApiError`() {
|
||||
val errorCode = 404
|
||||
val errorBody = "not found"
|
||||
networkCall.enqueue(
|
||||
object : Callback<NetworkResponse<String>> {
|
||||
override fun onResponse(
|
||||
call: Call<NetworkResponse<String>>,
|
||||
response: Response<NetworkResponse<String>>
|
||||
) {
|
||||
assertEquals(
|
||||
response.body(),
|
||||
NetworkResponse.Failure(NetworkResponseError.ApiError(errorCode))
|
||||
)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<NetworkResponse<String>>, t: Throwable) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
backingCall.complete(Response.error(errorCode, errorBody.toResponseBody()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should parse call with IOException as NetworkError`() {
|
||||
val exception = IOException()
|
||||
networkCall.enqueue(
|
||||
object : Callback<NetworkResponse<String>> {
|
||||
override fun onResponse(
|
||||
call: Call<NetworkResponse<String>>,
|
||||
response: Response<NetworkResponse<String>>
|
||||
) {
|
||||
assertEquals(
|
||||
response.body(),
|
||||
NetworkResponse.Failure(NetworkResponseError.NetworkError(exception))
|
||||
)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<NetworkResponse<String>>, t: Throwable) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
backingCall.completeWithException(exception)
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Conny Duck
|
||||
*
|
||||
* This file is part of Pixelcat.
|
||||
*
|
||||
* Pixelcat is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Pixelcat is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package at.connyduck.pixelcat.network.calladapter
|
||||
|
||||
import retrofit2.http.GET
|
||||
|
||||
interface TestApi {
|
||||
|
||||
@GET("testpath")
|
||||
suspend fun testEndpoint(): NetworkResponse<TestResponseClass>
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Conny Duck
|
||||
*
|
||||
* This file is part of Pixelcat.
|
||||
*
|
||||
* Pixelcat is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Pixelcat is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package at.connyduck.pixelcat.network.calladapter
|
||||
|
||||
import okhttp3.Request
|
||||
import okio.Timeout
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.io.InterruptedIOException
|
||||
|
||||
class TestCall<T> : Call<T> {
|
||||
private var executed = false
|
||||
private var canceled = false
|
||||
private var callback: Callback<T>? = null
|
||||
private var request = Request.Builder().url("http://example.com").build()
|
||||
|
||||
fun completeWithException(t: Throwable) {
|
||||
synchronized(this) {
|
||||
callback?.onFailure(this, t)
|
||||
}
|
||||
}
|
||||
|
||||
fun complete(body: T) = complete(Response.success(body))
|
||||
|
||||
fun complete(response: Response<T>) {
|
||||
synchronized(this) {
|
||||
callback?.onResponse(this, response)
|
||||
}
|
||||
}
|
||||
|
||||
override fun enqueue(callback: Callback<T>) {
|
||||
synchronized(this) {
|
||||
this.callback = callback
|
||||
}
|
||||
}
|
||||
|
||||
override fun isExecuted() = synchronized(this) { executed }
|
||||
override fun isCanceled() = synchronized(this) { canceled }
|
||||
override fun clone() = TestCall<T>()
|
||||
|
||||
override fun cancel() {
|
||||
synchronized(this) {
|
||||
if (canceled) return
|
||||
canceled = true
|
||||
|
||||
val exception = InterruptedIOException("canceled")
|
||||
callback?.onFailure(this, exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun execute(): Response<T> {
|
||||
throw UnsupportedOperationException("Network call does not support synchronous execution")
|
||||
}
|
||||
|
||||
override fun request() = request
|
||||
override fun timeout() = Timeout()
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Conny Duck
|
||||
*
|
||||
* This file is part of Pixelcat.
|
||||
*
|
||||
* Pixelcat is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Pixelcat is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package at.connyduck.pixelcat.network.calladapter
|
||||
|
||||
data class TestResponseClass(
|
||||
val lets: String,
|
||||
val test: Int
|
||||
)
|
|
@ -14,6 +14,7 @@ buildscript {
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven(url = "https://jitpack.io")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue