Как обрабатывать ответы API (успех/ошибка) в Android?

Зачем нам нужен API в Java Изучение

Может быть много способов обработки ответов API, поступающих от серверов в Android, но используете ли вы хороший способ обработки? В этой статье мы увидим, как ответы API обрабатываются с помощью Kotlin Sealed Class при использовании библиотеки Retrofit для вызовов API. Этот подход с закрытым классом идеально подходит в этом случае: если пользователь ожидает данные в пользовательском интерфейсе, нам нужно отправить ошибки в наш пользовательский интерфейс, чтобы уведомить пользователя и убедиться, что пользователь не просто увидит пустой экран. или столкнуться с неожиданным пользовательским интерфейсом.

Сделать запечатанный класс

Просто создайте универсальный запечатанный класс с именем Resource в отдельном файле в вашем пакете данных или пакете utils/others. Мы назвали его Resource и сделали его универсальным, потому что мы будем использовать этот класс для переноса наших различных типов ответов API. По сути, нам нужно обновить наш пользовательский интерфейс для этих трех событий, т. е. Success, Error, Loading. Итак, мы создали их как дочерние классы ресурсов для представления различных состояний пользовательского интерфейса. Теперь, в случае успеха, мы получим данные, поэтому обернем их Resource. Успех и в случае ошибки мы завернем сообщение об ошибке в Resource. Ошибка, и в случае загрузки мы просто вернем объект Resource.Loading (вы также можете изменить его, чтобы обернуть данные загрузки в соответствии с вашими потребностями).

Kotlin

sealed class Resource<T>(
    val data: T? = null,
    val message: String? = null
) {
    // We'll wrap our data in this 'Success'
    // class in case of success response from api
    class Success<T>(data: T) : Resource<T>(data = data)
    // We'll pass error message wrapped in this 'Error'
    // class to the UI in case of failure response
    class Error<T>(errorMessage: String) : Resource<T>(message = errorMessage)
    // We'll just pass object of this Loading
    // class, just before making an api call
    class Loading<T> : Resource<T>()
}

Давайте также взглянем на наш интерфейс ApiService (см. чуть ниже), здесь в каждой приостановленной функции вызова API мы оборачиваем наш ответ на вызов API классом Retrofit Response, потому что он предоставляет нам дополнительную информацию о вызовах API. Например: будут ли вызовы успешными или нет, код ошибки и т. д. Мы будем вызывать эти приостановленные функции из наших репозиториев… мы увидим это через какое-то время. Не запутайтесь в нашем пользовательском классе Resource и классе Response Retrofit. Класс Resource просто представляет различные состояния пользовательского интерфейса, в то время как класс Response Retrofit дает нам дополнительную информацию о вызовах API.

Читайте также:  Начало работы с Flask, микрофреймворком Python

Kotlin

interface ExampleApiService {
    @GET("example/popular_articles")
    suspend fun fetchPopularArticles(): Response<PopularArticlesResponse>
    // We have wrapped our api response in
    // Retrofit's Response class. So that we can have
    // some extra information about api call response
    // like weather it succeed or not, etc
  
    @GET("example/new_articles")
    suspend fun fetchNewArticles(): Response<NewArticlesResponse>
}

Теперь давайте перейдем к основной части обработки успеха/ошибки API.

У вас должны быть разные репозитории в вашем проекте в соответствии с вашими потребностями, и все ваши вызовы API должны происходить через эти репозитории, нам нужно выполнять обработку ошибок каждого вызова API. Итак, нам нужно обернуть каждый вызов API внутри блока try-catch. Но подождите… писать блок try-catch для каждого вызова API — плохая идея. Итак, давайте создадим BaseRepository и напишем там общую функцию приостановки с именем safeApiCall (любое имя, которое вам нравится), которая будет отвечать за обработку ответов API на каждый вызов API.

Kotlin

abstract class BaseRepo() {
    // we'll use this function in all
    // repos to handle api errors.
    suspend fun <T> safeApiCall(apiToBeCalled: suspend () -> Response<T>): Resource<T> {
        // Returning api response
        // wrapped in Resource class
        return withContext(Dispatchers.IO) {
            try {
              
                // Here we are calling api lambda
                // function that will return response
                // wrapped in Retrofit's Response class
                val response: Response<T> = apiToBeCalled()
                
                if (response.isSuccessful) {
                    // In case of success response we
                    // are returning Resource.Success object
                    // by passing our data in it.
                    Resource.Success(data = response.body()!!)
                } else {
                    // parsing api's own custom json error
                    // response in ExampleErrorResponse pojo
                    val errorResponse: ExampleErrorResponse? = convertErrorBody(response.errorBody())
                    // Simply returning api's own failure message
                    Resource.Error(errorMessage = errorResponse?.failureMessage ?: "Something went wrong")
                }
                
            } catch (e: HttpException) {
                // Returning HttpException's message
                // wrapped in Resource.Error
                Resource.Error(errorMessage = e.message ?: "Something went wrong")
            } catch (e: IOException) {
                // Returning no internet message
                // wrapped in Resource.Error
                Resource.Error("Please check your network connection")
            } catch (e: Exception) {
                // Returning 'Someting went wrong' in case
                // of unknown error wrapped in Resource.Error
                Resource.Error(errorMessage = "Something went wrong")
            
        }
    }
 
    // If you don't wanna handle api's own
    // custom error response then ignore this function
    private fun convertErrorBody(errorBody: ResponseBody?): ExampleErrorResponse? {
        return try {
            errorBody?.source()?.let {
                val moshiAdapter = Moshi.Builder().build().adapter(ExampleErrorResponse::class.java)
                moshiAdapter.fromJson(it)
            }
        } catch (exception: Exception) {
            null
        }
    }
}

safeApiCall имеет приостановленную лямбда-функцию с именем «api», которая возвращает данные, завернутые в Resource, чтобы safeApiCall мог получать каждую нашу функцию вызова API. Поскольку safeApiCall является функцией приостановки, мы используем область сопрограммы withContext с Dispatchers.IO, таким образом, все сетевые вызовы будут выполняться в фоновом потоке ввода/вывода. Всякий раз, когда сетевой вызов терпит неудачу, он выдает исключение, поэтому нам нужно вызвать нашу лямбда-функцию «apiToBeCalled» внутри блока try-catch, и здесь, если вызов API будет успешным, мы завернем наши данные об успешном ответе в Resource. Объект Success, и мы вернем его через safeApiCall, и если вызов API завершится неудачно, мы перехватим это конкретное исключение в одном из следующих блоков catch:

  • Когда что-то пойдет не так на сервере при обработке HTTP-запроса, он выдаст исключение HTTP.
  • Когда у пользователя не будет действительного подключения к Интернету/Wi-Fi, он выдаст исключение ввода-вывода Java.
  • Если произойдет какое-либо другое исключение, оно просто появится в общем блоке исключений (поместите этот блок последним). Вы также можете добавить другой блок перехвата исключений в соответствии с вашим конкретным случаем перед последним общим блоком перехвата исключений.

Теперь в блоках catch мы можем просто отправлять собственные сообщения об ошибках или сообщения об ошибках, полученные из исключений в объекте Resource.Error.

Примечание. В случае, если сетевой вызов не завершается ошибкой и вместо этого не выдает исключение, если он отправляет свой собственный ответ json об ошибке (иногда это происходит в случае сбоя аутентификации / истечения срока действия токена / недействительного ключа API / и т. д.), тогда мы выиграли ’ не получите исключение, вместо этого класс Response Retrofit поможет нам определить, возвращая true/false в response.isSuccessful. В приведенном выше коде мы также обрабатываем этот внутренний блок try, если он возвращает false, то внутри другого случая мы будем анализировать собственный пользовательский ответ json об ошибке API (см. Пример ниже) в созданный нами класс pojo, т.е. ExampleErrorResponse. Для разбора этого json-ответа на наш pojo ExampleErrorResponse мы используем библиотеку Moshi.в нашей функции convertErrorBody, которая вернет объект ExampleErrorResponse, а затем мы сможем получить собственный ответ API об ошибке.

{
   “status”: ”UnSuccessful”,
   “failure_message” : ”Invalide api key or api key expires”
}

Итак, в конечном итоге наша функция safeApiCall вернет наш ответ, завернутый в класс Resource. это может быть либо успех, либо ошибка. Если вы поняли до этого, то привет вам. Вы выиграли большую часть битвы.

Теперь давайте изменим способ вызова Apis в ваших текущих репозиториях.

Мы расширим все наши репозитории из BaseRepo, чтобы мы могли получить доступ к функции safeApiCall. И внутри функций нашего репозитория, таких как getPopularArticles, мы передадим нашу функцию ApiService (например: apiService.fetchPopularArticles()) в качестве лямбда-аргумента в safeApiCall, а затем safeApiCall просто обработает свою часть успеха/ошибки, после чего в конечном итоге вернет Resource это дойдет до пользовательского интерфейса через ViewModel.

Примечание. Помните, что когда мы будем вызывать функцию getPopularArticles в модели представления (см. пример ниже), то сначала мы разместим объект Resource.Loading в изменяемых оперативных данных, чтобы наш фрагмент/активность мог наблюдать за этим состоянием загрузки и мог обновить пользовательский интерфейс, чтобы показать загрузку. состояние, а затем, когда мы подготовим наш ответ API, он будет снова опубликован в изменяемых оперативных данных, чтобы пользовательский интерфейс мог снова получить этот ответ (успех/ошибка) и может отображать данные в пользовательском интерфейсе или обновлять себя соответственно.

Kotlin

class ExampleRepo(private val apiService: ExampleApiService) : BaseRepo() {
    suspend fun getPopularArticles() : Resource<PopularArticlesResponse> {
        // Passing 'api.fetchPopularArticles()' function
        // as an argument in safeApiCall function
        return safeApiCall { apiService.fetchPopularArticles() }
    }
    suspend fun getPublishedArticles() : Resource<NewlyPublishedArticlesResponse> = safeApiCall { apiService.fetchPublishedArticles() }
}

ViewModel

Kotlin

class ExampleViewModel (private val exampleRepo: ExampleRepo) {
    private val _popularArticles = MutableLiveData<Resource<PopularArticlesResponse>>()
    val popularArticles: LiveData<Resource<PopularArticlesResponse>> = _popularArticles
    init {
        // Calling this function in init{}
        // block so that it'll automatically
        // get called on initialization of viewmodel
        getPopularArticles()
    }
    private fun getPopularArticles() = viewModelScope.launch {
         // Firstly we are posting
         // Loading state in mutableLiveData
        _popularArticles.postValue(Resource.Loading())
        
         // Posting success response
         // as it becomes ready
        _popularArticles.postValue(exampleRepo.getPopularArticles())
    }
}

Вот и все, теперь мы можем легко наблюдать все состояния, т.е. Успех, Ошибка, Загрузка в нашем пользовательском интерфейсе. Это было об обработке ошибок API или об обработке ответов API в лучшем виде.

Оцените статью
bestprogrammer.ru
Добавить комментарий