Skip to content

[Proposal] Use custom classes required at compile time (e.g. Activity, Application, etc.) #283

@slavchev

Description

@slavchev

Overview

In Android development it is a standard practice to use inheritance to create custom activities, applications and other components. Currently NativeScript runtime for Android ({N}) has limited support for this scenario. This proposal aims to provide a solution for creating custom activities, applications and other components.

Note: for the sake of this proposal I use the common scenario to extend an activity class.

Current State

{N} provides built-in NativeScriptApplication and NativeScriptActivity classes. While these classes helped the adoption of {N} they also provide limitations. Currently it is not possible to inherit from a third party activity or other types which are used in AndroidManifest.xml file.

Technical Details

Here is how a Java class is extended in {N}

// app/myactivity.js
var MyActivity = android.app.Activity.extend({
   onCreate: function(bundle) {
      // implementation
   }
});

However MyActivity class is generated at runtime by {N} bridge. This mean you cannot use MyActivity in AndroidManifest.xml the way we currently use NativeScriptActivity for example.

<!-- AndroidManifest.xml -->
<activity
    android:name="com.tns.NativeScriptActivity"
    android:label="TestApp">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Proposal

The current extend implementation can accept two parameters as well. We can rewrite the previous example as follows

// app/myactivity.js
var MyActivity = android.app.Activity.extend("MyActivity", {
   onCreate: function(bundle) {
      // implementation
   }
});

We can leverage the latter syntax of extend function and pass the fully qualified type name of the new class as follows

// app/myactivity.js
var MyActivity = android.app.Activity.extend("com.example.MyActivity", {
   onCreate: function(bundle) {
      // implementation
   }
});

During build time we can parse the JavaScript and generate Java file com/example/MyActivity.java. This proposal will not make the existing code ambiguous because currently there is a validation rule that says the class name cannot contain . symbol. The generated MyActivity.java file will look as follows

// com/example/MyActivity.java
package com.example;

@com.tns.JavaScriptImplementation(javaScriptFile = "app/myactivity.js")
public class MyActivity extends android.app.Activity {
   public MyActivity() {
      com.tns.Platform.initInstance(this);
   }
   public void onCreate(android.os.Bundle param_0) {
      Object[] args = new Object[1];
      args[0] = param_0;
      com.tns.Platform.callJSMethod(this, "onCreate", void.class, args);
   }
}

This is how the currently runtime generated types look like. We can provide a tool that generates equivalent types at build time. The only difference is the use of com.tns.JavaScriptImplementation annotation. The need a way to find the JavaScript implementation for MyActivity when an instance is created. Using annotation is just one possible approach. I find it clear and, more importantly, I find it aligned with the following proposition how to extend Java class in TypeScript.

// myactivity.ts
@JavaProxy("com.tns.MyActivity")
class MyActivity extends android.app.Activity {
   onCreate(bundle: android.os.Bundle)  {
      // implementation
   }
}

Using ES6/TypeScript decorators is a simple way to indicate that we have to generate Java class at build time. The main benefit of using decorators is that the change is less obtrusive than other alternatives.

Impact

This proposal will require {N} modules to change the way an application is initialized and activities are created. I expect that this would be small-to-medium change. The immediate benefits are better encapsulation and control. Also, it may require {N} modules to come with prebuilt Activity and Application classes. Another approach would be adding these classes to the project template.

Summary

This proposal has some other benefits as well. Removing NativeScriptActivity and NativeScriptApplication from {N} will allows us to support lower Android API levels. Currently we have to compile against API level 17 and there is no technical justification for that. Also it will allow embedding {N} in other applications much easier. One primary scenario is NativeScript companion app.

Finally, you can find a prototype here

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions