Преобразовать ад обратного вызова в отложенный объект

1

Предыстория: Итак, у меня довольно большой проект с множеством API-функций. Я думаю о том, чтобы полностью перейти к сопрограммам, но так как они реализованы как Callback а не Deferred, я не могу использовать их эффективно. Например: я хотел бы сделать apiCallOne(), apiCallTwo() и apiCallThree() async и вызвать .await() чтобы дождаться завершения последнего запроса перед изменением пользовательского интерфейса.

Теперь проект структурирован так:

В самом низу (или вверху) находится ApiService.java:

interface ApiService {
    @GET("...")
    Call<Object> getData();
    ...
}

Затем у меня есть ClientBase.java: функция createRequest() является основной функцией для анализа ответа на модификацию.

void getUserName(String name, ApiCallback<ApiResponse<...>> callback) {
    createRequest(ApiService.getData(...), new ApiCallback<ApiResponse<?>>() {
        @Override
        public void onResult(ServiceResponse response) {
            callback.onResult(response);
        }
    });
}

private void createRequest(Call call, final ApiCallback<ApiResponse<?>> callback) {

    call.enqueue(new Callback() {
        @Override
        public void onResponse(Call call, retrofit2.Response response) {
                //heavy parsing
            }

            // return request results wrapped into ApiResponse object
            callback.onResult(new ApiResponse<>(...));
        }

        @Override
        public void onFailure(Call call, Throwable t) {
            // return request results wrapped into ApiResponse object
            callback.onResult(...);
        }
    });
}

ApiCallback и ApiResponse выглядит следующим образом:

public interface ApiCallback<T> {
    void onResult(T response);
}

public class ApiResponse<T> {
    private T mResult;
    private ServiceError mError;
    ...
}

Итак, перед всем этим у меня также есть ApiClient.java который использует ClientBase.createRequest():

public void getUserName(String name, ApiCallback<ApiResponse<..>> callback) {
    ClientBase.getUserName(secret, username, new ServiceCallback<ServiceResponse<RegistrationInvite>>() {
        @Override
        public void onResult(ServiceResponse<RegistrationInvite> response) {
            ...
            callback.onResult(response);
        }
    });
}

Как видите, это очень и очень плохо. Как я могу передать часть этого кода хотя бы для того, чтобы функция ApiClient.java возвращала Deferred объекты? (Я готов создать для этого еще один класс-обертку)

Теги:
kotlin
coroutine
kotlin-coroutines

2 ответа

0
  1. Сначала вы можете конвертировать ApiService.java в ApiService.kt в Котлине:

    interface ApiService {
        @GET("…")
        fun getData ( … : Call<Object>)
    }
    

    Чтобы изменить тип возврата методов обслуживания с Call на Deferred, вы можете изменить приведенную выше строку на:

    fun getData ( … : Deferred<Object>)
    

  1. Чтобы настроить запрос на разбор ответа о модификации в Kotlin, вы можете уменьшить его до нескольких строк в Kotlin.

    В вашем onCreate() в override fun onCreate(savedInstanceState: Bundle?){ В MainActivity.kt:

    val retrofit = Retrofit.Builder()
    // Below to add Retrofit 2 ‘s Kotlin Coroutine Adapter for Deferred
              .addCallAdapterFactory(CoroutineCallAdapterFactory()) 
              .baseUrl("YOUR_URL")
              .build()
    
    val service = retrofit.create(ApiService::class.java) 
    // Above using :: in Kotlin to create a class reference/ member reference
    
    val apiOneTextView = findViewById<TextView>(R.id.api_one_text_view)
    // to convert cast to findViewById with type parameters
    

  1. Я не знаю сценарий использования вашего API, но если ваш API будет возвращать длинный текстовый блок, вы также можете рассмотреть возможность использования предложенного подхода в нижней части этого поста.

    Я включил подход к передаче текстовых вычислений в PrecomputedTextCompat.getTextFuture, который, согласно документации Android, является помощником для PrecomputedText который возвращает будущее, которое будет использоваться с AppCompatTextView.setTextFuture(Future).


  1. Опять же, внутри вашего MainActivity.kt:

    // Set up a Coroutine Scope
    GlobalScope.launch(Dispatchers.Main){
    
    val time = measureTimeMillis{ 
    // important to always check that you are on the right track 
    
    try {
    
        initialiseApiTwo()
    
        initialiseApiThree()
    
        val createRequest = service.getData(your_params_here)
    
        apiOneTextView.text="Your implementation here with api details using ${createRequest.await().your_params_here}"
    
     } catch (exception: IOException) {
    
           apiOneTextView.text="Your network is not available."
        }
      }
        println("$time") 
        // to log onto the console, the total time taken in milliseconds taken to execute 
    
    }
    

    Отложено + Await= приостановить для ожидания результата, не блокирует основной поток пользовательского интерфейса


  1. Для ваших initializeApiTwo() и initializeApiThree() вы можете использовать для них private suspend fun в режиме GlobalScope.launch(Dispatchers.Main){, используя аналогичный GlobalScope.launch(Dispatchers.Main){... & val createRequestTwo = initializeApiTwo(), где: private suspend fun initializeApiTwo() = withContext(Dispatchers.Default) {//область действия сопрограммы, следуя тому же подходу, который описан в пункте 2 обсуждения.

  1. Когда я использовал описанный выше метод, моя реализация заняла 1863 мс.

    Чтобы еще больше упростить этот метод (от последовательного к параллельному), вы можете добавить следующие модификации в желтом цвете, чтобы перейти к Параллельному, используя Async (тот же код из пункта 4), что в моем случае дало улучшение в 50% времени и сокращение продолжительность до 901мс.

    Согласно документации Kotlin, Async возвращает Deferred - легкое неблокирующее будущее, которое представляет собой обещание предоставить результат позже. Вы можете использовать .await() для отложенного значения, чтобы получить его конечный результат.

    Внутри вашего MainActivity.kt:

    // Set up a Coroutine Scope
    GlobalScope.launch(Dispatchers.Main){
    
    val time = measureTimeMillis{ 
    // important to always check that you are on the right track 
    
    try {
    

    val apiTwoAsync = async {initialiseApiTwo()}

    val apiThreeAsync = async {initialiseApiThree()}

    val createRequest = async {service.getData(your_params_here)}

    val dataResponse = createRequest.await()

    apiOneTextView.text = "Ваша реализация здесь с деталями API, используя $ {dataResponse.await(). your_params_here}"

     } catch (exception: IOException) {
    
           apiOneTextView.text="Your network is not available."
        }
      }
        println("$time") 
        // to log onto the console, the total time taken in milliseconds taken to execute 
    
    }
    

    Чтобы узнать больше о создании функций приостановки в этом разделе, вы можете посетить этот раздел по параллельному использованию Async, предоставленному документацией Kotlin здесь.


  1. Предлагаемый подход для обработки PrecomputedTextCompat.getTextFuture:

    if (true) {
       (apiOneTextView as AppCompatTextView).setTextFuture(
          PrecomputedTextCompat.getTextFuture(
              apiOneTextView.text,
              TextViewCompat.getTextMetricsParams(apiOneTextView), null)
     )
    }
    

Надеюсь, это полезно.

0

В общем, простой способ сделать это - вернуть suspendCancellableCoroutine из функции приостановки, которую затем можно выполнить асинхронно. Так что в вашем случае вы можете написать что-то вроде:

suspend fun getUserName(name: String): ApiResponse<...> {
    return suspendCancellableCoroutine { continuation ->
        createRequest(ApiService.getData(...), new ApiCallback<ApiResponse<...>>() {
            @Override
            public void onResult(ApiResponse<...> response) {
                continuation.resume(response)
            }
        });
    }
}

Вы в основном возвращаете эквивалент SettableFuture и затем отмечаете его как завершенный, когда получаете успех или неудачу. Также имеется continueWithException(Throwable) если вы хотите обрабатывать ошибки посредством обработки исключений.

Это говорит:

Поскольку вы используете Retrofit, я бы порекомендовал просто добавить в зависимость retrofit2-kotlin-coroutines-adapter, которая изначально добавляет вам эту поддержку.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню