How to use a Kotlin extension function on a class? - android

I have a pretty short question about an extension function that would help clear some of my code. Basically I have some transformations on the hashCode of a class name and I want an extension function to do the transformations.
Example:
Getting the name hashCode: StateA::class.java.name.hashCode() where StateA is a simple class.
I want to the extension function like:
fun Class<*>.transformName(): String {
var hashString = this.javaClass.name.hashCode()
//Do my stuff on that hashString
return hashString
}
But this doesn't seem to work. When I apply the extension function with StateA.transformName(), the function gives me an error with Unresolved Reference.
I tried various things like applying the function to StateA::class or having the hashString equal to this::class.java.name.hashCode() but nothing works. Any tips?

You can't really achieve the StateA.transformName() syntax, as StateA just on its own refers to the companion object inside that class. So to get that syntax, you'd need to have a companion object inside every class that you want to use this extension on.
What you can do in a very general way is get the KClass that describes your class first. This gives you an object (the KClass instance) that you can then call an extension on:
fun KClass<*>.transformName() {
val clazz: Class<*> = this.java
clazz.name.hashCode()
}
StateA::class.transformName()
Another approach, which is less verbose on the call site could be a generic function like this, where the reified keyword allows you to access the concrete class that was used as the generic type parameter inside the function:
inline fun <reified T> transformName() {
val clazz: Class<*> = T::class.java
clazz.name.hashCode()
}
transformName<StateA>()

Related

Kotlin - Issue Extending Kotlin String Class

I am currently trying to extend Kotlins String class with a method in a file StringExt.kt
fun String.removeNonAlphanumeric(s: String) = s.replace([^a-ZA-Z0-9].Regex(), "")
But Kotlin in not allowing me to use this method in a lambda:
s.split("\\s+".Regex())
.map(String::removeNonAlphanumeric)
.toList()
The error is:
Required: (TypeVariable(T)) -> TypeVariable(R)
Found: KFunction2<String,String,String>
What confuses me about this is that Kotlins Strings.kt has very similar methods and
I can call them by reference without Intellij raising this kind of issue. Any advice is appreciated.
This is because you have declared an extension function that accepts an additional parameter and should be used as s.replace("abc").
I think what you meant is the following:
fun String.removeNonAlphanumeric(): String = this.replace("[^a-ZA-Z0-9]".toRegex(), "")
This declaration doesn't have an extra parameter and uses this to refer to the String instance it is called on.
I thing this is because a lambda is an anonymous function and dose not access to the scope of a extension file.
Check this link maybe contains some useful information:
https://kotlinlang.org/docs/reference/extensions.html

How to specify in a lambda "accepts generic sub-type of a generic type"?

This question is a derivative of a recent one I made here
While that one was resolved, I immediately faced a new one of a similar nature, but I am also not able to find a solution for it.
Sample example:
abstract class Endpoint<T>() {
private val myList: MutableList<(T) -> Unit> = mutableListOf()
fun <E : T> addToList(cbk: (E) -> Unit) { <-- E extends T
myList.add(cbk)
}
}
Usage example would be
Some sealed class
sealed class MainSealedClass {
data class ChildClass(val someParam: Int): MainSealedClass()
}
And the function call
anEndpointInstance.addToList<ChildClass>{it: ChildClass ->
// do something here
}
I tried doing the following, but it looks like it is not is not allowed
val myList: MutableList<(out T) -> Unit> <--- Unsupported error shows up
Is there a way to do this without having to add an extra declaration at the class level?
I considered using inline reified but the function needs to access private fields of the Endpoint instance, so I do not want to use that option.
I don't think the language supports specifying variance of the parameters of a functional type, or references to functions with generic types that aren't defined. For example, if you want to get a reference to a generic function, you have to specify its generic types in the functional type declaration.
There is dubious usefulness here anyway. If you had a list of functions that each have different input types, then when you retrieve a function from the list, you won't be able to call it because you won't know which input type it supports. You might as well have a MutableList<Any>.
Thanks to Tenfour04's knowledge, I was able to find a workaround to my issue. I do not consider this to be a fix for my issue, but given that it apparently cannot be done as I expected, the next best thing was to work around the issue with the help of a wrapper.
Here is how my code turned out with the work using the sample in my question
abstract class Endpoint<T>() {
private val myList: MutableList<(T) -> Unit> = mutableListOf()
fun add(cbk: (T) -> Unit) {
myList.add(cbk)
}
inline fun <reified N : T> addToList(crossinline callback: (N) -> Unit) {
add { if (it is N) callback.invoke(it) }
}
}
And its usage is the way I wanted in my question, nothing changes at this point.

How to make a function that returns a generic object?

I am trying to make a function that returns a T/Any object. The only thing is that i am not quite sure how to do it.
fun readMockData(context: Context, filename: String): Any {
val json = context.assets.open("$filename.json").bufferedReader().use(BufferedReader::readText)
return RetrofitSingleton.GSON.fromJson(json, object : TypeToken<Any>() {}.type)
}
This is what i tried at first, but i am getting an error.
How am i supposed to make this function properly?
If I understand your problem correctly, this is what you need:
#OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> readMockData(context: Context, filename: String): T {
val json = context.assets.open("$filename.json").bufferedReader().use(BufferedReader::readText)
return RetrofitSingleton.GSON.fromJson(json, typeOf<T>().javaType)
}
Note it has to be inline, because otherwise there is no way to acquire a type. If your function is longer than above, it may be a good idea to split it into an inline reified wrapper and the main function that accepts Type as a parameter.
It uses reflection, so you need to add a dependency to it. Also, typeOf() is experimental, but from my experience it just works (at least on JVM) and it is there for a long time, so I guess it won't change.

Prerequisite to create extension function in kotlin

I am learning Kotlin. As per I learnt, extension functions provides the ability to extend a class with new functionality without having to inherit from the class. I am creating extension function for okhttp3.RequestBody. But I am not able to get method in my activity.
Here is my extension function:
fun RequestBody.createPlainRequestBody(message: String): RequestBody = RequestBody.create(MediaType.parse("text/plain"), message)
while calling function as below I am getting unresolved function
RequestBody.createPlainRequestBody()
while I am creating extension function for toast I am getting perfect result as below:
fun Context.showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
by calling:
this#MainActivity.showToast("Upload successfully")
Can any one guide for how to resolve this?
Extension functions can be applied to instances of a particular class, but you're trying to call it on a class, as if it were a static method. Moreover, your extension function expects an argument and you're not providing any.
What you need in your case is a simple function that creates a RequestBody as you're not acting on any particular instance of RequestBody.
Under the hood an extension function is simply a static method where the first argument is the receiver object and any other argument is shifted by one. Your showToast extension function is equivalent to the following Java snippet:
public static void showToast(Context receiver, String message) {
Toast.makeText(receiver, message, ...).show();
}
That's why you can call Kotlin extension functions from Java.
Unfortunately, you cannot do this in OkHttp version 3, however, you
will able to do this in OkHttp4 which is being rewritten completely in
Kotlin, so all the classes will be compatible with Koltin.
You have to extends to its companion object. (You need to make sure that class has a companion object associated with it)
fun RequestBody.Companion.createPlainRequestBody(message: String): RequestBody {
return RequestBody.create(MediaType.parse("text/plain"), message)
}
And after that, you will be able to call it directly from its class.
RequestBody.createPlainRequestBody("message")
Or
RequestBody.Companion.createPlainRequestBody("message")
A companion object is a normal object that associated with or belongs to the class it's similar to static object in Java. In Kotlin, it's called companion object.

How to make an extension functions about getInteger for Context in Kotlin?

I'm a beginner of Kotlin, the Code A get a int value from resource file.
I hope to use an extension functions to do it, and invoke it just like this.getInteger(R.integer.ActivityEditBackup)
But Code B I made is incorrect, how can I fix it?
Code A
mContext.resources.getInteger(R.integer.ActivityEditBackup))
Code B
inline fun <reified T : Activity>Context.getInteger(int id): int {
return T.resources.getInteger(id)
}
You're overcomplicating it a bit.
You won't use the specific type of Context in any way, you don't need to make your extension generic.
In the parameter list, the name of the parameter comes first, and the type after.
The integer type's name in Kotlin is Int, with a capital I.
You can refer to your Context inside the extension function with this.
You can use support annotations to specify that your parameter is always an integer resource ID.
Overall, with these changes:
inline fun Context.getInteger(#IntegerRes id: Int): Int {
return this.resources.getInteger(id)
}
There was also some general confusion about syntax, you should look into the documentation for functions and then extensions.
Additionally, you can convert the function to an expression body and omit the explicit this:
inline fun Context.getInteger(#IntegerRes id: Int) = resources.getInteger(id)

Categories

Resources