πŸš€ 8.9 Released! β†’ ⚑️ New Node-API Engine Preview, πŸ“² ns widget ios, πŸ’… Tailwind v4 and more...
Read Announcement

Extending Kotlin and Java classes ​

Subclassing Kotlin and Java classes ​

The following examples demonstrate how to subclass Kotlin/Java classes in NativeScript:

kotlin
classMyButton(context: Context) : android.widget.Button(context) {
overridefunsetEnabled(enabled: Boolean) {
super.setEnabled(enabled)
 }
}

val btn =MyButton(context)
java
publicclassMyButtonextends android.widget.Button {
publicMyButton(Context context) {
super(context);
 }

 @Override
publicvoidsetEnabled(booleanenabled) {
super.setEnabled(enabled);
 }
}

MyButton btn =newMyButton(context);

Doing the same in NativeScript:

ts
let constructorCalled =false

@NativeClass
classMyButtonextendsandroid.widget.Button {
constructor() {
super()
 constructorCalled =true

// necessary when extending TypeScript constructors
return global.__native(this)
 }

setEnabled(enabled:boolean):void {
this.super.setEnabled(enabled)
 }
}

constbutton=newMyButton(context)
js
let constructorCalled =false
constMyButton= android.widget.Button.extend({
// constructor
init: function () {
 constructorCalled =true
 },

setEnabled: function (enabled) {
this.super.setEnabled(enabled)
 },
})

constbutton=newMyButton(context)

Note

In the above example, the setEnabled function's use of this keyword points to the JavaScript object that proxies the extended native instance. The this.super usage provides access to the base class method implementation.

Creating an anonymous Java class which extends from the base Java java.lang.Object class ​

ts
@NativeClass
classMyClassextendsjava.lang.Object {
constructor() {
super()
// necessary when extending TypeScript constructors
return global.__native(this)
 }

toString():string {
// override Object's toString
 }
}

constmyClassInstance=newMyClass()
js
constMyClass= java.lang.Object({
init() {
// constructor
 },

toString() {
// override Object's toString
 },
})

constmyClassInstance=newMyClass()

Creating named Java classes ​

To create a named Java class which extends from the java.lang.Object class:

ts
@NativeClass
@JavaProxy('org.nativescript.example.MyClass')
classMyClassextendsjava.lang.Object {
// constructor
constructor() {
super()
// necessary when extending TypeScript constructors
return global.__native(this)
 }

toString():string {
// override Object's toString
 }
}

constmyClassInstance=newMyClass()

// note: this may result in a TypeScript error because the namespace is not typed
// this can be safely ignored with // @ts-ignore
// or by type-casting to `any` for example.
constmyClassInstance2=new org.nativescript.example.MyClass()
constmyClassInstance3=new (org asany).nativescript.example.MyClass()
js
constMyClass= java.lang.Object('org.nativescript.example.MyClass', {
init() {
// constructor
 },

toString() {
// override Object's toString
 },
})

constmyClassInstance=newMyClass()
constmyClassInstance2=new org.nativescript.example.MyClass()

TIP

One important thing to note when dealing with extending classes and implementing interfaces in NativeScript is that, unlike in Javaβ€”where you can extend an Abstract class with a new java.arbitrary.abstract.Class() { }, in NativeScript the class needs to be extended as per the previous examples - using the extend function on the java.arbitrary.abstract.Class, or using the extends class syntax in TypeScript.

Custom Android Application and Activity ​

NativeScript provides a way to create custom android.app.Application and android.app.Activity implementations.

Extending Android Application ​

Create a new TypeScript file in the root of your project folder - name it application.android.ts or application.android.js if you are using plain JS.

Note

Note the *.android suffix - we want this file packaged for Android only.

A minimal example of the custom Application class:

ts
// the `JavaProxy` decorator specifies the package and the name for the native *.JAVA file generated.
@NativeClass()
@JavaProxy('org.nativescript.example.Application')
classApplicationextendsandroid.app.Application {
publiconCreate():void {
super.onCreate()

// At this point modules have already been initialized
// Enter custom initialization code here
 }

publicattachBaseContext(baseContext:android.content.Context) {
super.attachBaseContext(baseContext)

// This code enables MultiDex support for the application (if needed)
// androidx.multidex.MultiDex.install(this);
 }
}
js
constsuperProto= android.app.Application.prototype

// the first parameter of the `extend` call defines the package and the name for the native *.JAVA file generated.
android.app.Application.extend('org.nativescript.example.Application', {
onCreate: function () {
 superProto.onCreate.call(this)

// At this point modules have already been initialized
// Enter custom initialization code here
 },
attachBaseContext: function (base) {
 superProto.attachBaseContext.call(this, base)
// This code enables MultiDex support for the application (if needed compile androidx.multidex:multidex)
// androidx.multidex.MultiDex.install(this);
 },
})

To use the custom Application class, modify the application entry in the AndroidManifest.xml in App_Resources/Android/:

xml
<application
android:name="org.nativescript.example.Application"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">

Lastly, in order to correctly include the custom Application class, the extended Android application should be added to the webpack.config.js file.

js
constwebpack=require('@nativescript/webpack')

module.exports= (env) => {
 webpack.init(env)

 webpack.chainWebpack((config) => {
if (webpack.Utils.platform.getPlatformName() ==='android') {
// make sure the path to the application.android.(js|ts)
// is relative to the webpack.config.js
 config.entry('application').add('./application.android')
 }
 })

return webpack.resolveConfig()
}

The source code of application.android.ts is bundled separately as application.js file which is loaded from the native Application.java class on launch.

The bundle.js and vendor.js files are not loaded early enough in the application launch. That's why the logic in application.android.ts is needed to be bundled separately in order to be loaded as early as needed in the application lifecycle.

Note

This approach will not work if application.android.ts requires external modules.

Extending Android Activity ​

@nativescript/core ships with a default androidx.appcompat.app.AppCompatActivity implementation, that bootstraps the NativeScript application, without forcing users to declare their custom Activity in every project. In some cases you may need to implement a custom Android Activity.

Create a new ./src/activity.android.ts or ./src/activity.android.js when using plain JS.

Note

Note the .android suffix - we only want this file on Android.

A basic Activity can be implemented as follows:

ts
import {
 Frame,
 Application,
 setActivityCallbacks,
 AndroidActivityCallbacks,
} from'@nativescript/core'

@NativeClass()
@JavaProxy('org.nativescript.example.CustomActivity')
classCustomActivityextendsandroidx.appcompat.app.AppCompatActivity {
publicisNativeScriptActivity

private_callbacks:AndroidActivityCallbacks

publiconCreate(savedInstanceState:android.os.Bundle):void {
 Application.android.init(this.getApplication())
// Set the isNativeScriptActivity in onCreate (as done in the original NativeScript activity code)
// The JS constructor might not be called because the activity is created from Android.
this.isNativeScriptActivity =true
if (!this._callbacks) {
setActivityCallbacks(this)
 }

this._callbacks.onCreate(
this,
 savedInstanceState,
this.getIntent(),
super.onCreate,
 )
 }

publiconNewIntent(intent:android.content.Intent):void {
this._callbacks.onNewIntent(
this,
 intent,
super.setIntent,
super.onNewIntent,
 )
 }

publiconSaveInstanceState(outState:android.os.Bundle):void {
this._callbacks.onSaveInstanceState(
this,
 outState,
super.onSaveInstanceState,
 )
 }

publiconStart():void {
this._callbacks.onStart(this, super.onStart)
 }

publiconStop():void {
this._callbacks.onStop(this, super.onStop)
 }

publiconDestroy():void {
this._callbacks.onDestroy(this, super.onDestroy)
 }

publiconPostResume():void {
this._callbacks.onPostResume(this, super.onPostResume)
 }

publiconBackPressed():void {
this._callbacks.onBackPressed(this, super.onBackPressed)
 }

publiconRequestPermissionsResult(
requestCode:number,
permissions:Array<string>,
grantResults:Array<number>,
 ):void {
this._callbacks.onRequestPermissionsResult(
this,
 requestCode,
 permissions,
 grantResults,
undefined/*TODO: Enable if needed*/,
 )
 }

publiconActivityResult(
requestCode:number,
resultCode:number,
data:android.content.Intent,
 ):void {
this._callbacks.onActivityResult(
this,
 requestCode,
 resultCode,
 data,
super.onActivityResult,
 )
 }
}
js
import { Frame, Application, setActivityCallbacks } from'@nativescript/core'

constsuperProto= androidx.appcompat.app.AppCompatActivity.prototype

androidx.appcompat.app.AppCompatActivity.extend(
'org.nativescript.example.CustomActivity',
 {
onCreate(savedInstanceState) {
// Used to make sure the App is inited in case onCreate is called before the rest of the framework
 Application.android.init(this.getApplication())

// Set the isNativeScriptActivity in onCreate (as done in the original NativeScript activity code)
// The JS constructor might not be called because the activity is created from Android.
this.isNativeScriptActivity =true
if (!this._callbacks) {
setActivityCallbacks(this)
 }
// Modules will take care of calling super.onCreate, do not call it here
this._callbacks.onCreate(
this,
 savedInstanceState,
this.getIntent(),
 superProto.onCreate,
 )

// Add custom initialization logic here
 },
onNewIntent(intent) {
this._callbacks.onNewIntent(
this,
 intent,
 superProto.setIntent,
 superProto.onNewIntent,
 )
 },
onSaveInstanceState(outState) {
this._callbacks.onSaveInstanceState(
this,
 outState,
 superProto.onSaveInstanceState,
 )
 },
onStart() {
this._callbacks.onStart(this, superProto.onStart)
 },
onStop() {
this._callbacks.onStop(this, superProto.onStop)
 },
onDestroy() {
this._callbacks.onDestroy(this, superProto.onDestroy)
 },
onPostResume() {
this._callbacks.onPostResume(this, superProto.onPostResume)
 },
onBackPressed() {
this._callbacks.onBackPressed(this, superProto.onBackPressed)
 },
onRequestPermissionsResult(requestCode, permissions, grantResults) {
this._callbacks.onRequestPermissionsResult(
this,
 requestCode,
 permissions,
 grantResults,
undefined,
 )
 },
onActivityResult(requestCode, resultCode, data) {
this._callbacks.onActivityResult(
this,
 requestCode,
 resultCode,
 data,
 superProto.onActivityResult,
 )
 },
/* Add any other events you need to capture */
 },
)

Note

The this._callbacks property is automatically assigned to your extended class by the frame.setActivityCallbacks method. It implements the AndroidActivityCallbacks interface and allows core to get notified for important Activity events. It is important to use these callbacks, as many parts of NativeScript rely on them!

Next, modify the activity in App_Resources/Android/src/main/AndroidManifest.xml

xml
<activity
android:name="org.nativescript.example.CustomActivity"
android:label="@string/title_activity_kimera"
android:configChanges="keyboardHidden|orientation|screenSize">

To include the new Activity in the build, make sure it's added to the webpack.config.js with the following:

js
constwebpack=require('@nativescript/webpack')

module.exports= (env) => {
 webpack.init(env)
 env.appComponents = (env.appComponents || []).concat([
'./src/activity.android',
 ])

return webpack.resolveConfig()
}

Implementing Kotlin and Java interfaces ​

The next example shows how to implement an interface in Kotlin/Java with NativeScript. The main difference between inheriting classes and implementing interfaces in NativeScript is the use of the extend keyword. Implement an interface is done by passing the implementation object to the interface constructor function. The syntax is identical to Java Anonymous Classes.

kotlin
button.setOnClickListener {
// Perform action on click
}
java
button.setOnClickListener(new View.OnClickListener() {
publicvoidonClick(View v) {
// Perform action on click
 }
});

The same can be implemented in NativeScript:

ts
button.setOnClickListener(
new android.view.View.OnClickListener({
onClick() {
// Perform action on click
 },
 }),
)

Alternatively you can use the following pattern for a named interface implementation using TypeScript:

ts
@NativeClass()
@Interfaces([android.view.View.OnClickListener])
classClickListener
extendsjava.lang.Object
implementsandroid.view.View.OnClickListener
{
constructor() {
super()

// necessary when extending TypeScript constructors
return global.__native(this)
 }

onClick(view:android.view.View):void {
// Perform action on click
 }
}

nativeView.setOnClickListener(newClickListener())

Or using JavaScript:

js
constClickListener= java.lang.Object.extend({
// the interfaces to use
 interfaces: [android.view.View.OnClickListener],
onClick() {
// Perform action on click
 },
})

nativeView.setOnClickListener(newClickListener())
Next
iOS

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /