josesamuel / remoter

Remoter - An alternative to Android AIDL for Android Remote IPC services using plain java interfaces
Apache License 2.0
84 stars 14 forks source link

Allow code generation for foreign interfaces. #10

Closed Flockavelli closed 2 years ago

Flockavelli commented 2 years ago

I have a multiplatform project (desktop and android) with a hierarchy of modules.

at the base is a plain Java library written in Kotlin. (:businesslogic) It contains an interface definition. (IBusinessLogic)

I then have an android library module that depends on this .jar, (:businesslogic:android) where I need to create a remoter host service and a client for this interface.

I can add com.josesamuel:remoter-annotations to the Java Library module (:businesslogic) and annotate the interface, but since it is a pure java library with no Android dependency (it produces a .jar, not an .aar) I cannot add com.josesamuel:remoter-builder and have a remoter stub or proxy generated in that module.

I would like to keep the interface defined in the Java module (:businesslogic) so as to have all the business logic separate from the android platform.

would it be possible to add some kind of mechanism to have the remoter-builder generate an implementation for the interface (IBusinessLogic) in the android module? (:businesslogic:android) while keeping the interface in the java module? Perhaps this could apply to any interface, not just ones that are marked with @Remoter?

perhaps something like the marker you have in your suspender library?

@GenerateRemoterFor(classesToWrap = [
    IBusinessLogic::class, 
    IThirdPartyLibraryInterface::class
])
interface XXX
Flockavelli commented 2 years ago

Any chance of this getting consideration soon @josesamuel ?

josesamuel commented 2 years ago

Try this --

Define your interface in the java library. It doesn't need @ Remoter annotation.

public interface IBusinessLogic {
      void helloWorld();
}

In your android module, define a wrapper interface that extends the other interface and annotate that with @ Remoter. This acts like the marker interface for remoter and generates all the functions that you defined in the base interface without needing to redefine it here.

@Remoter
public interface IAndroidInterface extends IBusinessLogic {
}
Flockavelli commented 2 years ago

How then do I bind to the remote interface?

In your kotlin example service sample:

package util.remoter.remoterclient

import android.app.Service
import android.content.Intent
import util.remoter.service.ISampleKotlinService_Stub

class SampleKotlinService : Service() {

    private val binder by lazy {
        ISampleKotlinService_Stub(KotlinServiceImpl())
    }

    override fun onBind(intent: Intent?) = binder
}

ISampleKotlinService_Stub's constructor requires an instance of the IKotlinService interface.

If I were to take your suggestion and make a marker interface that extends the java module interface, I would have to pass in a class that implements the marker interface, so it would have to be defined in an Android module along with the marker interface.

in other words, for your suggestion to work, your _Stub class would need to be able to accept the base (IBusinessLogic) interface/class, not just the (IAndroidInterface) marker interface.

josesamuel commented 2 years ago

In your android module, create a simple implementation class for IAndroidInterface that extends from your actual implementation defined in the java module. Pass that to the stub

Flockavelli commented 2 years ago

Then the java module class needs to be made open (since kotlin is closed by default) and I need to make this extension for every class I need and update constructors if the base class gets its constructor updated.

That is doable, but it is pretty tedious if you have to do it for a lot of classes, and this library is already trying to cut down on the tedious remoting code you would otherwise need to write.

Flockavelli commented 2 years ago

After trying some of your suggestions, another issue I run into with this marker interface is that it cannot be made private or internal since the stub's constructor runs into this 'public' function exposes its 'internal' parameter type IAndroidInterface

so anyone who depends on the android module will now have that IAndroidInterface interface show up in the API along with the base IBusinessLogic and wonder why its public or if they need to care whether it is a remoter interface or not. it leaks implementation details.

josesamuel commented 2 years ago

Ok, I see your point. Let me think of what is the best way to do this. Will get back on this

Flockavelli commented 2 years ago

Thanks so much. this is an excellent library and I appreciate the effort that you must have put in to writing it.

josesamuel commented 2 years ago

This is what i am thinking -- adding an optional parameter to the Remoter annotation so that you can specify whether the given interface is merely a delegate of another one and hence proxy and stub should be generated for the specified interface instead

So you would define the wrapper interface in your android module specifying a delegate to your java interface

@Remoter(delegateInterface = IBusinessLogic.class)
public interface IAndroidInterface extends IBusinessLogic {
}

will result in  - 

IBusinessLogic_Proxy and IBusinessLogic_Stub to get generated.

Generated IBusinessLogic_Proxy will extend IBusinessLogic
Generated IBusinessLogic_Stub will accept IBusinessLogic

You will still have to the wrapper IAndroidInterface defined in your android module where the annotation process dependency is defined.

sounds good?

Volatile-Memory commented 2 years ago

It looks ok, but why would you have to have the interface extend the other if you are specifying it as a parameter to the annotation? does that add anything?

if not, I would remove that requirement and just have it be anything that can take an anotation.

with that requirement, if you leave that interface public (or even internal) you may confuse consumers as to which interface they should use, and would generally polute your API for no reason.

Also, could the parameter be an array of interfaces to generate remoter proxy/stubs for?

that way you define these in one location on one marker interface

Flockavelli commented 2 years ago

perhaps something like the marker you have in your suspender library?

@GenerateRemoterFor(classesToWrap = [
    IBusinessLogic::class, 
    IThirdPartyLibraryInterface::class
])
interface XXX

Is there any reason it can't be like it is in your suspender library?

josesamuel commented 2 years ago

Agree, will do that way but use the same @ Remoter annotation itself. So you just would need one marker interface that specifies all other interfaces

josesamuel commented 2 years ago

Done, Try on version 2.0.4.

it might take some time for the version to be available at maven central.

Flockavelli commented 2 years ago

odd, I get classpath issues now:

Supertypes of the following classes cannot be resolved. 
Please make sure you have the required dependencies in the classpath:
    class com.example.remoter.IBusinessLogic_Proxy, unresolved supertypes: remoter.RemoterProxy
Adding -Xextended-compiler-checks argument might provide additional information.
josesamuel commented 2 years ago

Do you have remoter-annotations added as dependnecy to your module?

Flockavelli commented 2 years ago

Actually, yes. I had the pure base java/kotlin module depend on implementation "com.josesamuel:remoter-annotations:2.0.4"

as well as the android module that depends on it.

But, I did find that changing the pure java/kotlin dependency to api "com.josesamuel:remoter-annotations:2.0.4" makes it work fine.