Java – Kotlin-Java interop does not work with variadic parameters

Kotlin-Java interop does not work with variadic parameters… here is a solution to the problem.

Kotlin-Java interop does not work with variadic parameters

I

wanted to abstract away getting resources in Android, so I wrote a class called ResourceProvider:, which actually provides resources

@Singleton
class ResourceProvider @Inject constructor(private val context: Context) {
fun getString(@StringRes id: Int): String {
    return context.getString(id)
}

fun getString(@StringRes id: Int, vararg formatArgs: Any): String {
    return context.getString(id, formatArgs)
}
...
}

There’s nothing special here, just calling methods on the Context.
When I ran into trouble getting a string with parameters, I created the following example:

var fromContext = requireContext().getString(R.string.one_parameter_string, "Text")
Log.i("fromContext", fromContext)
var fromWrapper = resourceProvider.getString(R.string.one_parameter_string, "Text")
Log.i("fromWrapper", fromWrapper)

fromContext = requireContext().getString(R.string.two_parameter_string, "Text", "Text")
Log.i("fromContext", fromContext)
fromWrapper = resourceProvider.getString(R.string.two_parameter_string, "Text", "Text")
Log.i("fromWrapper", fromWrapper)

Here is the string resource:

<string formatted="false" name="two_parameter_string">Text with parameters: %s, %s</string>
<string formatted="false" name="one_parameter_string">Text with parameter: %s</string>

As you can see, I call the same method directly on the Context and on my ResourceProvider class. I was expecting the same result, but actually this is what is printed in the console :

I/fromContext: Text with parameter: Text
I/fromWrapper: Text with parameter: [Ljava.lang.Object; @6d43f06
I/fromContext: Text with parameters: Text, Text
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: xxx.xxx.xxx, PID: 22963
    java.util.MissingFormatArgumentException: Format specifier '%s'
        at java.util.Formatter.format(Formatter.java:2522)
        at java.util.Formatter.format(Formatter.java:2458)
        at java.lang.String.format(String.java:2814)
        at android.content.res.Resources.getString(Resources.java:472)
        at android.content.Context.getString(Context.java:572)
        at xxx.xxx.xxx.utils.ResourceProvider.getString(ResourceProvider.kt:21)
        at xxx.xxx.xxx.views.trial.TrialFragment.onViewCreated(TrialFragment.kt:45)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1471)
        at androidx.fragment.app.FragmentManagerImpl.addAddedFragments(FragmentManager.java:2646)
        at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2416)
        at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2372)
        at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2273)
        at androidx.fragment.app.FragmentManagerImpl$1.run(FragmentManager.java:733)
        at android.os.Handler.handleCallback(Handler.java:789)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6944)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

As you can see, calling it directly on the Context doesn’t have any flaws, but calling the same method with my wrapper makes it print Object.toString() and in case it crashes in the second.

This is a decompiled version of the getString(@StringRes id: Int, vararg formatArgs: Any) method:

@NotNull
public final String getString(@StringRes int id, @NotNull Object... formatArgs) {
  Intrinsics.checkParameterIsNotNull(formatArgs, "formatArgs");
  String var10000 = this.context.getString(id, new Object[]{formatArgs});
  Intrinsics.checkExpressionValueIsNotNull(var10000, "context.getString(id, formatArgs)");
  return var10000;
}

What is the problem and how to solve it?

Solution

You need to use

the extension operator (*) to call context.getString, i.e. you need to use *formatArgs:

@Singleton
class ResourceProvider @Inject constructor(private val context: Context) {
  fun getString(@StringRes id: Int): String {
    return context.getString(id)
  }

fun getString(@StringRes id: Int, vararg formatArgs: Any): String {
    return context.getString(id, *formatArgs)
  }
...
}

You can find kotlin reference

regarding in kotlin reference Read more about this in variable number of arguments (varargs).

If you don’t, the given object (in this case, the formatArgs array) will be treated as a single object-method that you want to pass to vararg, and will therefore be wrapped in Object[] { formatArgs }

Related Problems and Solutions