Android 01/08/2017 Maciej No comments

Fragment.setArguments() misusage

Recently I was focused on increasing crash-free session rate in an Android app I’m working on. We use fabric.io, which gathers and keeps track of all crashes that occurred in the app. One of them looked like this:

Caused by java.util.NoSuchElementException
com.myapp.PaymentsProviderService$getPaymentProviderByIdentifier$1.call (PaymentsProviderService.kt:54)
com.myapp.payment.PaymentsProviderService$getPaymentProviderByIdentifier$1.call (PaymentsProviderService.kt:29)
rx.internal.operators.SingleOnSubscribeMap$MapSubscriber.onSuccess (SingleOnSubscribeMap.java:66)

The code that was causing crash fetched PaymentProvider object by its name. That data is guaranteed to be available locally: we fetch it during app initialization and store it in the database. Yet, somehow the app was crashing due to NoSuchElementException. At first glance, it seemed impossible to happen.

I started excessively testing faulty part of the app, and I managed to reproduce the issue. In order to do that, I had to enable “Don’t keep activities” developer options. Steps to reproduce were:

  1. Open faulty dialog
  2. Press home
  3. Return to the app

I was not observing that issue when Fragment was kept in memory – its recreation was necessary, that’s why above developer option was very handy. After few more minutes, I found a root of the problem:


override fun setArguments(args: Bundle?) {
super.setArguments(args)

paymentProvider = args?.getString(ARG_PAYMENT_PROVIDER) ?: ""
paymentMethod = args?.getString(ARG_PAYMENT_METHOD) ?: ""
}

setArguments() is Android’s way to pass some params to a Fragment. It can’t be done by passing data directly in constructor, because when system recreates our Fragment, it will always use non-argument constructor. The problem with the code above is that setArguments() will not be called when Fragment is recreated, so property paymentProvider will have its default value (in my case it was empty string).

Fortunately, the fix was quite easy. Instead of overriding setArguments(), we have to retrieve arguments in onCreate(). If you happen to use Kotlin, there is more elegant way to handle this:

val paymentProvider: String by lazy { arguments?.getString(ARG_PAYMENT_PROVIDER) ?: "" }

Leave a Reply

Your email address will not be published. Required fields are marked *