Generics, companion object, and inheritance in kotlin - android

I'm working with ViewBindings on Android, with code generated by the compiler from the xml that must look like that
class ViewBinding {...}
class MyBinding : ViewBinding {
companion object {
fun inflate (...) {
...
}
}
}
To avoid copy/pasting code, I want to create a class, accepting a class child of ViewBinding as a generic type argument, and with a companion object having the method inflate.
class MyClass<T:ViewBinding having the method inflate in the companion object of the class> { ... }
What's the smartest way to do that ?

As it is, what you're trying to do is impossible. The type parameter restrictions apply on the type itself, not its companion object, which isn't actually related to the class (it gets compiled as a separate class with no relation with the original class). The Java equivalent, implementing a static method from an interface, also isn't possible.
What you could do however, is to use the companion itself as a type parameter, the companion implementing an interface with the inflate method:
interface ViewBindingCompanion {
fun inflate(): ViewBinding
}
class MyBinding : ViewBinding {
companion object : ViewBindingCompanion {
fun inflate(): ViewBinding {
...
}
}
}
class MyClass<T : ViewBindingCompanion>
And then:
MyClass<MyBinding.Companion>()
Leave a comment if I missed something.

Related

How to Inject field in an object module that provides - Hilt Android [duplicate]

After spending a ludicrous amount of time trying to figure out why my dagger injections weren't working; I realised that the "object" type in Kotlin was the problem.
The following did not work, the injected "property" was null.
object SomeSingleton {
#Inject
lateinit var property: Property
init {
DaggerGraphController.inject(this)
}
}
However, the following DID work just fine:
class NotSingleton {
#Inject
lateinit var property: Property
init {
DaggerGraphController.inject(this)
}
}
I tried google, I tried the documentation but I could not pin point the reason behind this. Also note that I havent tried this with JAVA, JAVA doesnt have the concept of singletons built in anyway.
Why is this the case? Why is a kotlin singleton unable to inject members but a regular non-singleton class can?
If you look into kotlin bytecode you'll find that the code you've written is translated into following:
public final class SomeSingleton {
public static LProperty; property // <- Notice static field here
public final getProperty()LProperty
...
public final setProperty(LProperty)V
...
}
As you can see the actual field is static which makes it uneligible for instance injection. You may try to move #Inject annotation onto setter method by doing so:
object SomeSingleton {
#set:Inject
lateinit var property: Property
...
}
A workaround for this can be to extend a BaseClass that consists of the fields to be injected.
object SomeSingleton : BaseClass {
...
...
}
open class BaseClass{
#Inject
lateinit var property: Property
init{
YourDaggerComponent.inject(this)
}
}
This does work, although this would leak this, which comes up as an android studio warning, to get rid of that make the Base class abstract and instead inject the fields in your original object class
object SomeSingleton : BaseClass {
...
...
// Add the init block here instead of the base class
init{
YourDaggerComponent.inject(this)
}
}
abstract class BaseClass{
#Inject
lateinit var property: Property
//Remove the init block from here
}
And you Dagger AppComponent interface can be like, any of those function def should work
interface Component{
fun inject(someSingleton : SomeSingleton)
//OR
fun inject(baseClass: BaseClass)
}
I hope this helps....
I tried to use dagger.Lazy<YourClass> and it works
#set:Inject
lateinit var authClient: dagger.Lazy<PlatformAuthClient>
You may still need the #Singleton decorator on top of your object definition. That decorator doesn't make your class 'singleton', it's just used by Dagger to get all the dependencies in the same spot.
This was previously allowed by a bug in Dagger. As others described, it is because the properties in a kotlin object are backed by static fields.
See https://github.com/google/dagger/issues/1665 for the fix. It was fixed in 2.27.

Is it possible to use view binding in a static method?

My question is, if it is possible to use view binding in a static method:
class SomeFragment : FragmentBinding<SomeFragmentBinding>() {
companion object {
fun someRandomFunction() {
// Use view binding here
binding.textView.text = "Test"
}
}
}
binding is a non-static property of SomeFragment class, where as companion object are static. Static methods cannot access non-static properties, because each instance of the class would have their own instances of that non-static property.
So, you cannot access binding or any other non-static property inside companion object.

Generic Static Constant

It's hard to explain but I basically want to be able to have an interface which my subclasses need to implement, which mandates a static constant. How would I achieve this in Kotlin?
I have the following class/interface:
interface BaseFragmentInterface {
val TAG: String
}
class BaseFragment: Fragment() {
/* this, of course, doesn't compile right now: */
companion object: BaseFragmentInterface {}
func<T: BaseFragment> push(fragment: T) {
/* Here I want to access TAG */
Log.d(T.TAG, "Push Fragment")
}
}
My subclasses need to be required to implement the interface. And my base companion object would need to be somehow abstract.
Any idea how to achieve this?
Let me precise few things.
My subclasses need to be required to implement the interface.
There's no way to achieve this, you can't force a subclass' companion object to implement your interface since the companion object of a class is not related to the companion object of its superclass.
And my base companion object would need to be somehow abstract.
Since the companion object is an object, it can't be abstract, because an object can't be abstract too.
Said that, you have two options to still achieve your desired behavior:
Use TAG as a non-static final property inside BaseFragment:
abstract class BaseFragment : Fragment() {
protected abstract val TAG: String
fun <T : BaseFragment> push(fragment: T) {
/* Here I want to access TAG */
Log.d(TAG, "Push Fragment")
}
}
class FragmentImpl : BaseFragment() {
override val TAG: String = "fragment-impl"
}
Use companion objects to hold the tag, make the type T of push() as reified and access the TAG property inside the companion object of T using the reflection.

Dagger injection not working for "object" in Kotlin

After spending a ludicrous amount of time trying to figure out why my dagger injections weren't working; I realised that the "object" type in Kotlin was the problem.
The following did not work, the injected "property" was null.
object SomeSingleton {
#Inject
lateinit var property: Property
init {
DaggerGraphController.inject(this)
}
}
However, the following DID work just fine:
class NotSingleton {
#Inject
lateinit var property: Property
init {
DaggerGraphController.inject(this)
}
}
I tried google, I tried the documentation but I could not pin point the reason behind this. Also note that I havent tried this with JAVA, JAVA doesnt have the concept of singletons built in anyway.
Why is this the case? Why is a kotlin singleton unable to inject members but a regular non-singleton class can?
If you look into kotlin bytecode you'll find that the code you've written is translated into following:
public final class SomeSingleton {
public static LProperty; property // <- Notice static field here
public final getProperty()LProperty
...
public final setProperty(LProperty)V
...
}
As you can see the actual field is static which makes it uneligible for instance injection. You may try to move #Inject annotation onto setter method by doing so:
object SomeSingleton {
#set:Inject
lateinit var property: Property
...
}
A workaround for this can be to extend a BaseClass that consists of the fields to be injected.
object SomeSingleton : BaseClass {
...
...
}
open class BaseClass{
#Inject
lateinit var property: Property
init{
YourDaggerComponent.inject(this)
}
}
This does work, although this would leak this, which comes up as an android studio warning, to get rid of that make the Base class abstract and instead inject the fields in your original object class
object SomeSingleton : BaseClass {
...
...
// Add the init block here instead of the base class
init{
YourDaggerComponent.inject(this)
}
}
abstract class BaseClass{
#Inject
lateinit var property: Property
//Remove the init block from here
}
And you Dagger AppComponent interface can be like, any of those function def should work
interface Component{
fun inject(someSingleton : SomeSingleton)
//OR
fun inject(baseClass: BaseClass)
}
I hope this helps....
I tried to use dagger.Lazy<YourClass> and it works
#set:Inject
lateinit var authClient: dagger.Lazy<PlatformAuthClient>
You may still need the #Singleton decorator on top of your object definition. That decorator doesn't make your class 'singleton', it's just used by Dagger to get all the dependencies in the same spot.
This was previously allowed by a bug in Dagger. As others described, it is because the properties in a kotlin object are backed by static fields.
See https://github.com/google/dagger/issues/1665 for the fix. It was fixed in 2.27.

Companion Objects in Kotlin Interfaces

I am trying to make a interface Parcelable, as such I need a interface like this
interface AB : Parcelable {
companion object {
val CREATOR : Parcelable.Creator<AB>
}
}
and my two classes A and B looking like
data class A (...): Parcelable{
...
companion object {
val CREATOR : Parcelable.Creator<AB> = object : Parcelable.Creator<AB> {
override fun newArray(size: Int): Array<AB?> {
return arrayOfNulls(size)
}
override fun createFromParcel(parcel: Parcel): AB {
return A(parcel)
}
}
}
I am struggling to implement such a interface in kotlin. It seems the interface class does not allow for the CREATOR
Perhaps I am taking the wrong approach,
I have a parcelable that contains a list of classes that are either A or B
so I am doing
parcel.readTypedList(this.list, AB.CREATOR)
I require that the list be either A or B and that is why I am using an interface.
Anyone have any advice or a possible solution?
In Kotlin, an interface can have a companion object but it is not part of the contract that must be implemented by classes that implement the interface. It is just an object associated to the interface that has one singleton instance. So it is a place you can store things, but doesn't mean anything to the implementation class.
You can however, have an interface that is implemented by a companion object of a class. Maybe you want something more like this:
interface Behavior {
fun makeName(): String
}
data class MyData(val data: String) {
companion object: Behavior { // interface used here
override fun makeName(): String = "Fred"
}
}
Note that the data class does not implement the interface, but its companion object does.
A companion object on an interface would be useful for storing constants or helper functions related to the interface, such as:
interface Redirector {
fun redirectView(newView: String, redirectCode: Int)
companion object {
val REDIRECT_WITH_FOCUS = 5
val REDIRECT_SILENT = 1
}
}
// which then can be accessed as:
val code = Redirector.REDIRECT_WITH_FOCUS
By convention classes implementing the Parcelable interface must also have a non-null static field called CREATOR of a type that implements the Parcelable.Creator interface.
You need to annotate CREATOR property with #JvmField annotation to expose it as a public static field in containing data class.
Also you can take a look at https://github.com/grandstaish/paperparcel — an annotation processor that automatically generates type-safe Parcelable wrappers for Kotlin and Java.

Categories

Resources