Избегайте двойного вызова API из-за быстрой навигации по нижней части - PullRequest
0 голосов
/ 27 июня 2019

Я разработал приложение для Android с использованием Kotlin, и все его структура и функциональность завершены, но я заметил небольшую проблему, когда я нажимаю быстро, по крайней мере, дважды на кнопку, которая выполняет вызов API.

Длявызовы API Я использую комбинацию RetroFit2 и GsonConverterFactory.Вызов выглядит следующим образом:

    fun fetchInfo(id: Int) {
        val retrofit = Retrofit.Builder()
            .baseUrl("https://www.mysitesurl.com/api/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val api = retrofit.create(ApiService::class.java)

        api.getInfo(id).enqueue(object: Callback<DataType> {

            override fun onResponse(call: Call<DataType>, response: Response<DataType>) {
                var resp = response.body()!!
                my_image.setImageResource(resources.getIdentifier(resp.image, "drawable", context!!.packageName))
                my_image.visibility = View.VISIBLE

                my_label.text = resp.text
                my_label.visibility = View.VISIBLE
            }

            override fun onFailure(call: Call<FechaDia>, t: Throwable) {

            }

        })
    }

Я немного отредактировал код, чтобы избежать определенных имен переменных

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

java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ImageView.setImageResource(int)' on a null object reference`

Я попытался использовать try / catch, но он все еще делает вызов и все еще получает пустой запрос.Есть ли способ предотвратить это или что я здесь упускаю в своем процессе?

Основная проблема заключается в том, что не просто отображается ошибка, приложение закрывается и отображается сообщение App has stopped. Open app again.

Ответы [ 3 ]

1 голос
/ 27 июня 2019

Используйте глобальный флаг, подобный следующему:

private boolean clicked = false;

В onClick:

if(false){
     callApi();
     clicked = true;
}

А в ответе об успешном выполнении или ошибке установите значение false:

clicked = false;
0 голосов
/ 27 июня 2019

Я чувствую, что это отличная ситуация для дебошира.

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

Первая найденная ссылка: Kotlin Android debounce

Я бы посоветовал взглянуть на ответ SANAT. Это выглядит как очень чистая реализация, которая поможет вам обрабатывать несколько кликов без нескольких срабатываний функций.

0 голосов
/ 27 июня 2019

Как упомянуто в комментариях, старайтесь не принудительно обнулять тип, который не может обнуляться, поскольку будут побочные эффекты (исключения).

В идеале, вы также хотели бы немного отделить вещи для лучшей читаемости кода:

fun retrofit(): Retrofit = Retrofit.Builder()
    .baseUrl("https://www.mysitesurl.com/api/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

fun apiService(): ApiService = retrofit().create(ApiService::class.java)

fun fetchInfo(id: Int) =
    apiService().getInfo(id).enqueue(object : Callback<Element.DataType> {
        override fun onResponse(
            call: Call<Element.DataType>,
            response: Response<Element.DataType>
        ) {
            response.body()?.run { renderView(this) }
        }
        override fun onFailure(call: Call<FechaDia>, t: Throwable) {}
    })

fun renderView(response: DataType) = view?.apply {
    val image = resources.getIdentifier(response.image, "drawable", context.packageName))
    my_image.setImageResource(image)
    my_image.visibility = View.VISIBLE
    my_label.text = response.text
    my_label.visibility = View.VISIBLE
}

Если у вас есть адаптер, вам не нужно назначать его в renderView, так как вы, вероятно, будете обновлять адаптер только после получения данных от API.

Имейте адаптер в качестве свойства либо в Activity, либо во Fragment, а затем, как только вы получите ответ, вызовите адаптер и отправьте список.

Чтобы дополнить вопрос, так как не так много кода для просмотра, я полагаю, если вы используете нижнюю навигацию без использования библиотеки навигации jetpack, вы можете использовать нижнюю навигацию с ViewPager и использовать OnNavigationItemSelectedListener на нижняя панель навигации для переключения между страницами на адаптере ViewPager.

И если бы у вас было retainInstance = true, фрагмент не был бы воссоздан, поэтому был бы сделан только один вызов API.

...