Moshi ArrayOutOfBoundsException when adding factory

Viewed 126

I'm finding this exception related with Moshi sometimes when opening the app:

Caused by java.lang.ArrayIndexOutOfBoundsException: length=33; index=33
       at java.util.ArrayList.add(ArrayList.java:468)
       at com.squareup.moshi.Moshi$Builder.add(Moshi.java:231)

We initialise a repository in the BaseApplication which, sometimes, results in the mentioned crash when initialising Moshi. I'm finding this error in the app reports but I'm not able to reproduce it. Let's jump to the what we have and see if you might have a clue on it.

This factory is used to create Moshi instances, getting the crash when adding KotlinJsonAdapterFactory:

object MyMoshiConverterFactory {

    fun create(setup: (Moshi.Builder.() -> Unit)? = null): Converter.Factory {
        val moshi = MoshiUtil.createMoshi()
        setup?.let { moshi.it() }
        moshi.add(KotlinJsonAdapterFactory()) // Here is the crash!
        return MoshiConverterFactory.create(moshi.build())
    }
}

Here we have a class where we have all the converters we use. It really has a lot more of converters, but I've removed a few of them for simplicity:

object MoshiUtil {

    private val lazyMoshi by lazy {
        Moshi.Builder().apply {
            add(DateAdapter())
            add(DefaultOnDataMismatchAdapter.newFactory(FeedItem::class.java, null))
            add(SkipListNullValuesAdapter.createFactory(Element::class.java))
            add(SkipListNullValuesAdapter.createFactory(Post::class.java))
            add(SkipListNullValuesAdapter.createFactory(MetadataItem::class.java))
            add(GeoGeometry::class.java, GeometryAdapter())
        }
    }

    fun createMoshi() = lazyMoshi
}

And finally, in our BaseApplication, we have something like this:

class BaseApplication {

   @Override
    public void onCreate() {
        super.onCreate();

        val myService = getMyService(applicationContext)

    }

    private fun getMyService(appContext: Context): MyService {
       val converterFactory = MyMoshiConverterFactory.create()

       return Retrofit.Builder().baseUrl(baseUrl).apply {
                addConverterFactory(converterFactory)
                client(okHttpClientBuilder.build())
            }.build().create(MyService::class.java)
        }
    }
 }

So, do you see anything that could be causing it? Do you think it might be a concurrency issue happening at startup when the several places in the app are creating the MoshiUtils object at the same time?. Looking forward to hear from you guys, thanks!

1 Answers

Moshi.Builder is mutable and not thread-safe, so this error you're getting sometimes is a race condition as a result of that. You should call .build() on that base MoshiUtil instance to get an immutable Moshi instance, then make the return value of MoshiUtil.createMoshi be moshi.newBuilder() (creates a Moshi.Builder already configured like the existing Moshi instance), like so:

object MoshiUtil {
    private val baseMoshi: Moshi = Moshi.Builder().apply {
        // ...
    }.build()

    fun createMoshi(): Moshi.Builder = baseMoshi.newBuilder()
}

Since every person that calls createMoshi now gets their own instance of Moshi.Builder, there shouldn't be any concurrency problems anymore.

Related