android icon indicating copy to clipboard operation
android copied to clipboard

Enable generation of default (0-arguments) constructor when extending classes

Open petekanev opened this issue 9 years ago • 5 comments

If the developer extends a base class with no default constructor, they have no way of explicitly specifying that they want a default constructor code to be generated in Java. This is a certain limitation if the class needs to be declared in the AndroidManifest and is instantiated by the system (attempting to use IntentService is one such case - Android will attempt to create an instance of the implementation of IntentService, but will crash, as it tries to invoke a default constructor, which the developer cannot declare).

Given the following Java class:

package a.b;

public class Base {
    public Base(String a) { ... }       
}

and the following implementation in JavaScript:

a.b.Base.extend("a.b.ExtendedBase", {
    init: function() {
     _this("arbitrary string");      
     // -> will introduce additional logic in the sole ctor block
     // in Java, but will not create an additional constructor
    }  
});

will output

package a.b;

@com.tns.JavaScriptImplementation(javaScriptFile = "./ExtendedBase.js")
public class ExtendedBase extends a.b.Base implements com.tns.NativeScriptHashCodeProvider {
        /* No default constructor */

    public ExtendedBase (java.lang.String param_0){
        super(param_0);
                /* 
                    Additional logic to handle more arguments -> a result of the init function
                */                 
        com.tns.Runtime.initInstance(this);
    }

    public boolean equals__super(java.lang.Object other) {
        return super.equals(other);
    }

    public int hashCode__super() {
        return super.hashCode();
    }
}

Developers have no way to declare a default constructor for a.b.ExtendedBase.

Instead what I need is

package a.b;

@com.tns.JavaScriptImplementation(javaScriptFile = "./ExtendedBase.js")
public class ExtendedBase extends a.b.Base implements com.tns.NativeScriptHashCodeProvider {
        /* default constructor */
        public ExtendedBase () {
        this("Arbitrary String");
    }

    public ExtendedBase (java.lang.String param_0){
        super(param_0);
                /* 
                    Additional logic to handle more arguments -> a result of the init function
                */                 
        com.tns.Runtime.initInstance(this);
    }
}

Generating custom constructors based on the javascript code will require additional analysis, and may not be worth looking into at the moment.

petekanev avatar Aug 04 '16 14:08 petekanev

@Pip3r4o I think this is a quite important issue. Certain Google APIs (e.g. Geofences) require an implementation of the IntentService. I ended up creating jar libraries that provide the required implementations. A possibility to declare custom constructors in JS would be nice.

MrVonkey avatar Nov 25 '16 18:11 MrVonkey

@MrVonkey We'll take that into consideration, thanks for your feedback!

petekanev avatar Nov 25 '16 18:11 petekanev

@MrVonkey a workaround for the current issue could be solved with implementing the native class with the default constructor and accessing it through javascript. In other words, making a plugin.

Plamen5kov avatar Jan 13 '17 15:01 Plamen5kov

@MrVonkey the npm package nativescript-android-utils contains a custom implementation that has a 0-argument constructor, and should be sufficient to work around the generator limitations for the time being.

Thanks for using NativeScript!

petekanev avatar Jan 21 '17 21:01 petekanev

@Pip3r4o I am actually facing that issue right now. I can't find a way to extend that class even using a constrcutor like that:

class MergeTileDataSourceImpl extends com.carto.datasources.TileDataSource {
        static constructorCalled: boolean = false;
        constructor(min: number, max: number) {
            super(min, max);
            MergeTileDataSourceImpl.constructorCalled = true;
            // necessary when extending TypeScript constructors
            return global.__native(this);
        }
}

it does not work. The generated java class is like this:

@com.tns.JavaScriptImplementation(javaScriptFile = "./nativescript-carto/carto.js")
public class MergeTileDataSource extends com.carto.datasources.TileDataSource implements com.tns.NativeScriptHashCodeProvider {
	public MergeTileDataSource(long param_0, boolean param_1){
		super(param_0, param_1);
		com.tns.Runtime.initInstance(this);
	}

	protected MergeTileDataSource(){
		super();
		com.tns.Runtime.initInstance(this);
	}

	protected MergeTileDataSource(int param_0, int param_1){
		super(param_0, param_1);
		com.tns.Runtime.initInstance(this);
	}
}

so the public constructor will crash because a boolean will be sent to the native class (which also uses JNI)

Any solution?

Actually managed to fix it. The issue is with the generated constructors in my class. As you can see they are protected when the super class actually have them public.

Can make so that it keep the correct "public" attribute?

farfromrefug avatar May 19 '18 15:05 farfromrefug