避免从底部导航中快速点击两次API

时间:2019-06-27 16:25:18

标签: android kotlin nullpointerexception gson retrofit2

我使用Kotlin开发了一个Android应用程序,并且所有结构和功能都完整,但是当我反复快速点击(至少两次在执行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响应并获得null响应之前进行另一个API调用,因此我基本上尝试用null资源替换图像,并且向我显示此错误:

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 个答案:

答案 0 :(得分:1)

使用这样的全局标志:

private boolean clicked = false;

在onClick中:

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

在“成功”或“错误”响应中,将其设置为false:

clicked = false;

答案 1 :(得分:0)

如评论中所述,请尽量不要将可为null的类型强制为非可为null的类型,因为这样会产生副作用(例外)。

理想情况下,您还希望对某些事物进行解耦以提高代码的可读性:

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返回数据后才更新适配器。

在“活动”或“片段”中将适配器作为属性,然后在获得响应后调用适配器并发送列表。

为补充这个问题,因为没有太多代码要看,我想如果您使用底部导航而不使用jetpack导航库,则可以将底部导航与ViewPager一起使用,并使用{{ 1}}在底部导航上,可在OnNavigationItemSelectedListener适配器上的页面之间进行切换。

如果您拥有ViewPager,则该片段将不会重新创建,因此仅会对API进行一次调用。

答案 2 :(得分:0)

对于去弹子来说,我觉得这是一个很好的情况。

去抖是一种模式,可以防止针对同一功能的多个信号连续触发得太快,并且如果在给定的时间范围内按下则只会触发一次。

我找到的第一个参考文献:Kotlin Android debounce

我建议您看一下SANAT的答案,这看起来是一个非常干净的实现,它将帮助您处理多次单击而不会触发多个功能。