Kotlin annotate receiver of extension function - android

I would like to restrict on which constant value extension function can be called. For example function like:
#IdRes
fun <T : View> Int.find() = findViewById<T>(this)
If this was called on real id, it's fine:
R.id.someView.find<TextView>() // ok
But this should make compilation error:
42.find<TextView>() // should be compile error
Is annotating extension receiver supported in Kotlin?

As described in the documentation, you can use the following syntax:
fun #receiver:IdRes <T : View> Int.find() = ...
However, note that the Kotlin compiler is not aware of the semantics of the Android annotations, so their incorrect use is never a compilation error; it's at best a failed lint check.

Related

How to use a Kotlin extension function on a class?

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>()

If there is no attribute namespace in the data binding, intellisense will not work

There is a very simple bindingadapter function.
#JvmStatic
#BindingAdapter("app:test")
fun testBind(v: View, test: Int) {
//test...
}
If you apply this code in xml, it will usually look like this:
autocomplete function works fine, and xml does not print any warnings.
However, this bindingadapter function outputs a warning at compile time.
warning: Application namespace for attribute app:test will be ignored.
Many other posts say remove namepsace for this warning.
I removed the namespace from the bindingadapter function along with it.
#JvmStatic
#BindingAdapter("test")
fun testBind(v: View, test: Int) {
//test...
}
Doing so will not print out the warning at compile time.
But this time, xml prints a warning.
Also, when the namespace exists, the autocomplete function that worked normally does not work at all.
Of all the methods I've tried, the only way to resolve all two warnings is to specify the namespace as android.
is there any other way? android namespace seems to be a misunderstanding as this is a basic binding feature in Android, not a custom binding function.
If you are using single argument in binding adapter method, remove namespace in #BindingAdapter(...) string, after that add bind: namespace before calling string in xml.
#JvmStatic
#BindingAdapter("icon")
fun setImage(view: ImageView, imageID: Int) {
}
and bind:icon="#{vm.iconID}"
If I'm using several attributes it's not worked.

Using mockk to match any varargs

I'm trying to mock an Android Context to return a string from a resource id. However I have trouble matching the stub to the call, I assume it is because of the varargs. However I am new to mockk so I might just miss something very easy.
I mock the context this way:
val context = mockk<Context>()
every { context.getString(any(), any()) } returns stringToReturn
But when calling getString on the object it throws the following exception:
io.mockk.MockKException: no answer found for: Context(#1).getString(2131689544, [])
If it is important, I call the function in the class under test similar to this. formatArgs may be empty but doesn't have to:
protected fun foo(stringResource: Int, vararg formatArgs: Any) {
val s = context.getString(errorMessageStringResource, *formatArgs)
Any idea how I can fix this?
You can check the project and reproduce the exception here: Github Project
Version 1.9.1 introduces few additional matchers to match varargs.
https://mockk.io/#varargs
There is a related open issue in mockk v1.9: https://github.com/mockk/mockk/issues/224 (see referenced issues as well)
I tried several solutions but I ended up creating overloaded functions just for testing with mockk, eg.
class Context {
// Renamed because of same JVM signature
fun foo2(stringResource: Int, vararg formatArgs: Any) = foo(stringResource, formatArgs)
// Function accepts
fun foo(stringResource: Int, formatArgs: args: Array<out Any>) = ...
}
then test the non-vararg foo() function with mockk.
I know it's an ugly workaround but if you find a better one please let me know :)

Type mismatch using inject()-function from Koin

I'm using the dependency injection framework Koin in my app. The following line of code works perfectly in my MainActivity:
private val auth: FirebaseAuth by inject()
Unfortunately, the same line of code does not work in a custom BroadcastReceiver.
Android Studio marks the "inject()"-function red and tells me it is an unresolved reference (import of "org.koin.android.ext.android.inject" is marked as unused).
When I try to build it nevertheless, I got the following exception:
Error:(14, 39) Unresolved reference. None of the following candidates
is applicable because of receiver type mismatch: public inline fun
ComponentCallbacks.inject(name: String = ...):
Lazy defined in org.koin.android.ext.android
How can I make the injection work in this class and why does it fail?
The inject method you use in Activities is defined here like so:
/**
* inject lazily given dependency for Android component
* #param name - bean name / optional
*/
inline fun <reified T> ComponentCallbacks.inject(name: String = "")
= lazy { (StandAloneContext.koinContext as KoinContext).get<T>(name) }
So you can call it in classes that implement the ComponentCallbacks interface - these would be application components, like Activities or Services.
If you wish to use Koin the same way in your BroadcastReceiver, you can define another inject extension on that class with the same implementation:
inline fun <reified T> BroadcastReceiver.inject(name: String = "")
= lazy { (StandAloneContext.koinContext as KoinContext).get<T>(name) }

Kotlin type inference faild

When I use both Kotlin and Java in my project
In Java BaseActivity.class:
public abstract <T extends BaseViewModel> Class<T> bindViewModel();
And when I extend BaseActivity In Kotlin :
override fun <T : BaseViewModel<*, out IBaseView<*>>?> bindViewModel(): Class<T> {
return ArchViewModel::class.java
}
the Kotlin remind me the return is type inference faild
Type inference failed. Expected type mismatch:
required:Class<T>
found:Class<ArchViewModel>
How to fix this issue?
P.S. the ArchViewModel.class extends BaseViewModel
Type inference isn't failing. Your method signature says it can return Class<T> for any T (which extends BaseViewModel) you ask for, so it can be called e.g. as
activity.bindViewModel<SomeRandomModel>()
and must return a Class<SomeRandomModel>. Since ArchViewModel::class.java isn't a Class<SomeRandomModel>, the implementation is incorrect.
It's actually impossible to implement correctly whether in Kotlin or in Java. If return ArchViewModel.class compiles in Java, that's because you use the raw type BaseViewModel, so the compiler gives up on typechecking and doesn't report the error.
So you need to fix the method so it can be implemented. How, depends on what you actually want from it.
Alternately, you can "fix" it by casting return ArchViewModel::class.java as Class<T>. The compiler will correctly warn you that this cast is unsafe.

Categories

Resources