避免来自底部导航快速点击的双重 API 调用

Posted

技术标签:

【中文标题】避免来自底部导航快速点击的双重 API 调用【英文标题】:Avoid double API call from bottom navigation fast tap 【发布时间】:2019-11-09 17:22:02 【问题描述】:

我使用 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 响应之前尝试进行另一个 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 消息。

【问题讨论】:

您正试图强制可空的response.body()!! 为非空,因此请尝试执行response.body()?.run // code goes here 之类的操作。 只是为了确认@RodrigoQueiroz,如果我这样做,所有的 UI 设置器只会在响应不为空时运行?否则它什么都不做? 确实,除非你有一个真实的身体,否则什么都不会设置!我不认为使用标志是一个可行的解决方案,所以如果有更多的代码或上下文,可能会提出更好的解决方案! @RodrigoQueiroz 你的解决方案在没有修改太多代码的情况下成功了,你可以把它作为答案:) 还有一件事,它适用于常规片段,但有时当我使用适配器时应用程序仍然关闭。我所做的是将适配器分配放在运行块中。这是正确的还是在这种情况下有所不同? 【参考方案1】:

使用这样的全局标志:

private boolean clicked = false;

在点击中:

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

在 Success 或 Error 响应中,将其设为 false:

clicked = false;

【讨论】:

问题是我的底部导航会发生这种情况,它会影响主要片段,所以您是说我在主要活动中创建了一个全局变量并更改它?当本地(特定于片段的)按钮也进行 API 调用时会发生什么? 在活动中制作标志并从任何地方更改它(活动以及片段)【参考方案2】:

如 cmets 中所述,尽量不要将可空类型强制为不可空类型,因为会有副作用(异常)。

理想情况下,您还希望将事物解耦以提高代码可读性:

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 中将适配器作为属性,然后在获得响应后调用适配器并发送列表。

为了补充这个问题,因为没有太多代码可看,我想如果您使用底部导航而不使用喷气背包导航库,您可以使用底部导航和ViewPager 并使用OnNavigationItemSelectedListener on在ViewPager 适配器上的页面之间切换的底部导航。

如果您有retainInstance = true,则片段不会重新创建,因此只会对 API 进行一次调用。

【讨论】:

你的解决方案对我有用,我没有像你建议的那样划分它,但我确实使用了response.body()?.run ... :)【参考方案3】:

我觉得这对于去抖动器来说是一个很好的情况。

去抖动是一种模式,可防止同一函数的多个信号连续触发过快,并且如果在给定时间范围内按下只会触发一次。

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

我建议查看 SANAT 的答案,这看起来是一个非常干净的实现,并且将帮助您处理多次点击而不会触发多个函数。

【讨论】:

以上是关于避免来自底部导航快速点击的双重 API 调用的主要内容,如果未能解决你的问题,请参考以下文章

Android如何隐藏底部虚拟按键

mui tab bar 底部导航栏跳转页面。不用pluseady 因为不支持

通过底部导航栏更改片段时恢复片段状态

底部导航栏的 onItemSelectedListener

Android 仿微信调用第三方应用导航(百度,高德腾讯)

Android仿微信调用第三方地图应用导航(高德百度腾讯)