避免来自底部导航快速点击的双重 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 调用的主要内容,如果未能解决你的问题,请参考以下文章