How to access the properties of an interface from object inside that interface?

How to access the properties of an interface from object inside that interface?

example :

interface FeatureFlags {
    val version: String
    object FF {
        fun instanceBlock(): Boolean {
            return compareVersions(version, "0.19") >= 0
        }
    }
}

Or I tried this

interface FeatureFlags {
    val version: String
    fun instanceBlock(): Boolean {
        return compareVersions(version, "0.19") >= 0
    }
}

interface LemmyApiBase : PictrsAPI, OldRoutes {
    val version: String
    override var auth: String?

    object FF : FeatureFlags {
        override val version: String = this.version // need to access LemmyApiBase.version
    }
}

Basically I just want to have FeatureFlags to be namespaced FF onto another interface, while still being able to pass some properties from that other interface

interface FeatureFlags {
    val version: String
    fun instanceBlock(): Boolean {
        return compareVersions(version, "0.19") >= 0
    }
}

interface LemmyApiBase : PictrsAPI, OldRoutes {
    val version: String
    override var auth: String?

    val FF get() = object: FeatureFlags {
        override val version: String = this@LemmyApiBase.version // need to access LemmyApiBase.version
    }
}
1 Like

Why did you respond with a copy paste of my example?

Look again. It’s not a copy paste. Instead of defining it as a “static” object, instead we define it as an anonymous object (basically a local implementation of an interface that depends on our context).
An alternative way to define it would be:

interface LemmyApiBase : PictrsAPI, OldRoutes {
    val version: String
    override var auth: String?
    val FF get() = Flags()
    inner class Flags: FeatureFlags {
        override val version: String = this@LemmyApiBase.version
    }
}
2 Likes

That works, Thanks for the help! :slightly_smiling_face:

I can see you just edited that :wink:

I think the problem with your first example is that you don’t have an instance in scope.

Doing object FF {} inside your interface simply creates an object that you have to access via the FeatureFlags interface. IE, you would call FeatureFlags.FF.instanceBlock(). If you created the object outside the interface, you would just call FF.instanceBlock(). The point is that there’s nothing special about FF existing inside FeatureFlags, and the only thing it does is mean you have to use FeatureFlags first to access it.

I don’t really understand exactly what you’re trying to do, but basically you need an instance of FeatureFlags to operate on. So you could change your function to be instanceBlock(flag: FeatureFlag): Boolean = compareVersions(flag.version, "0.19") >= 0, or you could write an extension function on FeatureFlag, or even just define the function on FeatureFlag, but the important point is that you need an instance of FeatureFlag. The FF object has no instance of FeatureFlag to look at.

EDIT: Actually now that I look at your second example, it’s the same thing; creating an object inside an interface is just a namespace difference; the object doesn’t magically get an instance of the interface to operate on.

1 Like

The goal is to make it very clear which methods are featureflags. ApiBase has indirectly over 1000 methods, featureflags around 100. So I want to namepsace all the featureflags to FF.

So the concrete impl will be apiClient.FF.instanceBlock(). How I achieve this doesn’t really matter to me. But apiClient.FF.instanceBlock(ApiClient) is not option that I am going for.

The only problem I have with above solution that was provided, is that it constantly recreates a new instance on retrieval of FF. Which I would like to avoid. Preferably I would like to keep it as an interface but It does seem like abstract class its the only way. Since interface can’t keep state. Unless there is a way?

As you said, your requirements are somewhat contradicting:

  • Have a FeatureFlags instance, which is different than LemmyApiBase instance.
  • Do not store the FeatureFlags instance anywhere (because LemmyApiBase is an interface).
  • Do not instantiate it on-demand.

I don’t want to mention very dirty solutions like creating a global map: LemmyApiBase → FeatureFlags. Still there is one potentially sufficient, but a little restricted solution - value/inline class:

interface LemmyApiBase : PictrsAPI, OldRoutes {
    val version: String
    override var auth: String?

    val FF get() = LemmyApiBaseFeatureFlags(this)
}

@JvmInline
value class LemmyApiBaseFeatureFlags(private val lemmy: LemmyApiBase) : FeatureFlags {
    override val version get() = lemmy.version
}

If we look into the bytecode, lemmy.FF.version becomes:

       7: invokeinterface #51,  1           // InterfaceMethod LemmyApiBase."getFF-B0RrkU0":()LLemmyApiBase;
      12: invokestatic  #57                 // Method LemmyApiBaseFeatureFlags."getVersion-impl":(LLemmyApiBase;)Ljava/lang/String;

We don’t create another instance, we still use LemmyApiBase. Both methods are practically empty, JIT will probably remove them.

Some caveats:

  • We can’t pass this FF object anywhere as FeatureFlags, because then it will have to be boxed into a LemmyApiBaseFeatureFlags, so it will create an instance. This shouldn’t be a problem if you plan to mostly do: x.FF.foo().
  • We have to expose LemmyApiBaseFeatureFlags type and FF member has to be typed as it, we can’t type it as FeatureFlags - for the same reason as above. Kotlin can only inline if we use the inlined typed directly.
  • We can’t access private members of LemmyApiBase, but as long as it is an interface, that shouldn’t be the case.

edit:
We can even mark override val version get() as inline and then I see it no longer calls LemmyApiBaseFeatureFlags.getVersion-impl(), but LemmyApiBase.getVersion() directly. It shows a warning though. I suspect the warning is only about the fact virtual methods can’t be inlined if used from the supertype. If this is the case, we can safely suppress the warning. But it would require some digging on whether there are other consequences of this.

2 Likes

Thanks for the detailed response.

Have a FeatureFlags instance, which is different than LemmyApiBase instance.

This is not required unless, you mean that I want FeatureFlags to be separate interface.

There is an additional requirement, this is part of a Kotlin Multiplatform project. Not sure if the @JvmInline will work but it does seem to compile

So if I’m understanding, you have two interfaces:

interface FeatureFlags {
    val version: String
}

interface ApiBase {
    fun doApiThing()
}

If you have a class that implements both of those interfaces, like so:

class MyApiClassWithFeatures(override val version: String) : ApiBase, FeatureFlags {
    override fun doApiThing() {
    }
}

When you use that class somewhere, you don’t want to do myApiClassWithFeatures.version and myApiClassWithFeatures.doApiThing(), because you don’t know which function/property comes from which interface? So you instead want myApiClassWithFeatures.FF.version, and myApiClassWithFeatures.doApiThing()? Am I correctly understanding what you’re trying to achieve? You want to make it obvious in the code, every time you use a function/property, which interface the function/property comes from?

Sorry for the late response, did not get a notif

The actual case is as such:


// Psuedo

/**
* Defines which features are enabled since this version
**/
interface FeatureFlags {
    val version: String
  // this x100 times
   fun hasBlocking() = version > "0.1"
}

/**
* Mostly implements  a collection of interfaces
*/
interface ApiBase : FeatureFlags, ... {
  val version: String
}

/**
* Defines routes
*/
interface ApiV1 : ApiBase {
  // this x300
  fun getPosts()
}

/*
* Actual impl
*/
class ApiV1Service : ApiV1 {

   fun getPosts() = ...
}

// https://github.com/MV-GH/LemmyBackwardsCompatibleAPI

So I will have easily around 1000 methods on ApiV1Service, and it will be hard to distinguish between a feature flag, a method describing if a certain feature or a method which returns a response.
With “hard” meaning in a sense it won’t be immediately be clear if this method is a featureFlag or a something else.

So as consumer of this library:


val api = getApi("instance")

if(api.FF.hasBlocking()){
  // show blocking button
  action: { api.blockPerson() }
}

So for me it not necessary at all to know from code which function comes from which interface. I just want to make it very obvious if a function is a featureflag. For which namespacing it is the best imo.

Ok, now that I’ve thought about it some more, I can’t believe I didn’t think of this originally.

Just use an extension property to achieve your namespacing.

val FeatureFlags.FF: FeatureFlags
    get() = this

val service = ApiV1Service()

service.FF.hasBlocking()

service.getPosts()

Basically, just always use the FF extension property on any class that implements the FeatureFlags interface, and since it returns a FeatureFlags instance, the only methods available to you will be the ones on FeatureFlags,

I’m not sure if you’ll need to use generics for the extension property… I think it should work as-is, but if not, try something like this:

val <T : FeatureFlags> T.FF: FeatureFlags
    get() = this