Overview
One of NativeScript's strongest capabilities is the access to Android (also referred to as 'Java/Kotlin' or 'native') APIs inside JavaScript/TypeScript. That's possible thanks to build-time generated Metadata chunks which hold the information about the public classes from the Android SDK, Android support libraries, and any other Android libraries which may be imported into your Android NativeScript project.
Note: 'Android classes' and 'Java/Kotlin classes' are used interchangeably throughout the article to refer to classes in the Java/Kotlin programming language.
Access Android Packages
The
Android packages
are available in the JavaScript/TypeScript global context and
are the entry point for accessing Android APIs. Think of them as
of TypeScript/C# namespaces, or the way to access sets of
classes. For example, the android.view
package
grants access to classes like android.view.View
-
the base of all view elements in Android.
In order to access a particular class in JavaScript/TypeScript
the full package name leading up to the class name needs to be
specified, or you may end up working with
undefined
variables.
- java.lang
- android
- android.view
- etc.
The above is accessed in JavaScript like:
const javaLangPkg = java.lang;
const androidPkg = android;
const androidViewPkg = android.view;
// access classes from inside the packages later on
const View = androidViewPkg.View;
// or
const View = android.view.View;
const Object = javaLangPkg.Object; // === java.lang.Object;
To find out the package name of an Android class, refer to the Android SDK Reference, or to the supplied API Reference of a plugin, when importing 3rd-party Android components into your project.
For example, if you need to work with the Google API for Google
Maps, after following the installation guide, you may need to
access packages from the plugin like
com.google.android.gms.maps
, which you can find a
reference for at
Google APIs for Android Reference
Note: To have access and Intellisense for the native APIs with NativeScript + TypeScript or NativeScript + Angular projects, you have to add a dev dependency to
@nativescript/types
. More details about accessing native APIs with TypeScript can be found here.Note: (Experimental) Alternatively, to get Intellisense for the native APIs based on the available Android Platform SDK and imported Android Support packages (added by default to your Android project), supply the
--androidTypings
flag with yourtns run | build android
command. The resultingandroid.d.ts
file can then be used to provide auto-completion.Note: You cannot use APIs that are not present in the metadata. By default, if
--compileSdk
argument isn't provided while building, metadata will be built against the latest Android Platform SDK installed on the workstation. See metadata limitations.
Access Android Classes
Classes (See OOP) are the schematics to producing building blocks (Objects) in Android, as such, they are used to represent almost everything you see, as well as what you don't see, in an Android application - the Android layouts are objects built from classes, the buttons and text views also have class representations. Classes in Java and Kotlin have unique identifiers denoted by the full package name (see above), followed by the actual class name (usually capitalized - see above - 'View')
Accessing classes in Android you would normally add an
import
statement at the beginning of the
Java/Kotlin file, to allow referring to the class only by its
name. If the developer decides, they may be as expressive as
possible by using the full class identifier too:
package my.awesome.application;
import android.view.View;
public class ... {
public static void staticMethod(context) {
View newView = new View(context);
// or
android.view.View newView2 = new android.view.View(context);
}
}
Accessing Android classes, in the JavaScript/TypeScript of a NativeScript application, is kept as close to the original Java syntax as the JavaScript language allows:
function arbitraryFunction(context) { // 'context' is a JavaScript wrapper (Proxy - see below) for the underlying android.content.Context Java instance
const View = android.view.View;
const newView = new View(context);
// or
const newView2 = new android.view.View(context);
// newView and newView2 will be JavaScript wrappers (Proxies - see below) for the created Java android.view.View objects
}
Proxies
The JavaScript objects that lie behind the Android APIs are called Proxies. There are two types of proxies:
Package Proxy
Provides access to the classes, interfaces, constants and
enumerations within each package. See java.lang
.
Class Proxy
Represents a thin wrapper over a class or an interface and
provides access to its methods and fields. From a JavaScript
perspective this type of proxy may be considered as a
constructor function. For example
android.view.View
is a class proxy.
The result of the constructor calls (new ...()
)
will create native android.view.View
instances on
the Android side and a special hollow Object on the JavaScript
side. This special object knows how to invoke methods and access
fields on the corresponding native instance. For example we may
retrieve the path value of the above created
File
using the corresponding
File
class API like:
Access Methods, Fields and Constants
Thanks to the 'proxying' system, Java/Kotlin methods and fields can be accessed through the JavaScript wrappers of the native instances. For example, you may retrieve the result of a method call to the Java instance:
const javaObj = new java.lang.Object();
const javaObjHashCode = javaObj.hashCode(); // result is `int` in Java, marshalled to a JavaScript number
console.log(javaObjHashCode); // prints out the hashCode number
Public and private members, as well as static fields of an instance, or Java/Kotlin classes can also be accessed. The android.view.View class will be used below:
const context = ...; // retrieve context
const newView = new android.view.View(context);
newView.clearFocus(); // public member call to 'public void clearFocus()' as declared in Android
let newViewScaleX = newView.SCALE_X; // public static field access to 'public static final SCALE_X' as declared in Android
const focusUpDirection = android.view.View.FOCUS_UP; // public static field access to `FOCUS_UP` - represents an integer as declared in the Android source
let foundView = newView.focusSearch(android.view.View.FOCUS_UP); // public member call to 'public View focusSearch(int direction)'
const randomViewId = android.view.View.generateViewId(); // static method call to 'public static int generateViewId()' - generates a random integer suitable for Android Views
Extend Classes and Interfaces
For a comprehensive guide on extending classes and implementing interfaces through JavaScript/TypeScript check out the dedicated article.
Full-fledged Example
Let's take a sample Android code, and transcribe it to JavaScript/TypeScript.
The following code (courtesy of startandroid.ru) creates an Android layout, and adds a couple Button and TextView elements:
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// creating LinearLayout
LinearLayout linLayout = new LinearLayout(this);
// specifying vertical orientation
linLayout.setOrientation(LinearLayout.VERTICAL);
// creating LayoutParams
LayoutParams linLayoutParam = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
// set LinearLayout as a root element of the screen
setContentView(linLayout, linLayoutParam);
LayoutParams lpView = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
TextView tv = new TextView(this);
tv.setText("TextView");
tv.setLayoutParams(lpView);
linLayout.addView(tv);
Button btn = new Button(this);
btn.setText("Button");
linLayout.addView(btn, lpView);
LinearLayout.LayoutParams leftMarginParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
leftMarginParams.leftMargin = 50;
Button btn1 = new Button(this);
btn1.setText("Button1");
linLayout.addView(btn1, leftMarginParams);
LinearLayout.LayoutParams rightGravityParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
rightGravityParams.gravity = Gravity.RIGHT;
Button btn2 = new Button(this);
btn2.setText("Button2");
linLayout.addView(btn2, rightGravityParams);
}
}
class MainKotlinActivity: Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// creating LinearLayout
val linLayout = LinearLayout(this)
// specifying vertical orientation
linLayout.orientation = LinearLayout.VERTICAL
// creating LayoutParams
val linLayoutParam = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
// set LinearLayout as a root element of the screen
setContentView(linLayout, linLayoutParam)
val lpView = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
val tv = TextView(this)
tv.text = "TextView"
tv.layoutParams = lpView
linLayout.addView(tv)
val btn = Button(this)
btn.text = "Button"
linLayout.addView(btn, lpView)
val leftMarginParams = LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
leftMarginParams.leftMargin = 50
val btn1 = Button(this)
btn1.text = "Button1"
linLayout.addView(btn1, leftMarginParams)
val rightGravityParams = LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
rightGravityParams.gravity = Gravity.RIGHT
val btn2 = Button(this)
btn2.text = "Button2"
linLayout.addView(btn2, rightGravityParams)
}
}
const MainActivity = android.app.Activity.extend("my.application.name.MainActivity", {
onCreate: function (savedInstanceState) {
super.onCreate(savedInstance);
// creating LinearLayout
let linLayout = new android.widget.LinearLayout(this);
// specifying vertical orientation
linLayout.setOrientation(android.widget.LinearLayout.VERTICAL);
// creating LayoutParams - accessing static class LayoutParams of LinearLayout
let linLayoutParam = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.MATCH_PARENT);
// set LinearLayout as a root element of the screen
this.setContentView(linLayout, linLayoutParam);
let lpView = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
let tv = new android.widget.TextView(this);
tv.setText("TextView");
tv.setLayoutParams(lpView);
linLayout.addView(tv);
let btn = new android.widget.Button(this);
btn.setText("Button");
linLayout.addView(btn, lpView);
let leftMarginParams = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
leftMarginParams.leftMargin = 50;
let btn1 = new android.widget.Button(this);
btn1.setText("Button1");
linLayout.addView(btn1, leftMarginParams);
let rightGravityParams = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
rightGravityParams.gravity = android.view.Gravity.RIGHT;
let btn2 = new android.widget.Button(this);
btn2.setText("Button2");
linLayout.addView(btn2, rightGravityParams);
}
});
@JavaProxy("my.application.name.MainActivity");
class MainActivity extends android.app.Activity {
constructor() {
super();
return global.__native(this);
}
onCreate(savedInstanceState) {
super.onCreate(savedInstance);
// creating LinearLayout
let linLayout = new android.widget.LinearLayout(this);
// specifying vertical orientation
linLayout.setOrientation(android.widget.LinearLayout.VERTICAL);
// creating LayoutParams - accessing static class LayoutParams of LinearLayout
let linLayoutParam = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.MATCH_PARENT);
// set LinearLayout as a root element of the screen
this.setContentView(linLayout, linLayoutParam);
let lpView = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
let tv = new android.widget.TextView(this);
tv.setText("TextView");
tv.setLayoutParams(lpView);
linLayout.addView(tv);
let btn = new android.widget.Button(this);
btn.setText("Button");
linLayout.addView(btn, lpView);
let leftMarginParams = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
leftMarginParams.leftMargin = 50;
let btn1 = new android.widget.Button(this);
btn1.setText("Button1");
linLayout.addView(btn1, leftMarginParams);
let rightGravityParams = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
rightGravityParams.gravity = android.view.Gravity.RIGHT;
let btn2 = new android.widget.Button(this);
btn2.setText("Button2");
linLayout.addView(btn2, rightGravityParams);
}
};
The NativeScript code can further be shortened, and it starts to look a lot like Java:
const LinearLayout = android.widget.LinearLayout;
const LayoutParams = android.widget.LinearLayout.LayoutParams;
const TextView = android.widget.TextView;
const Button = android.widget.Button;
const Gravity = android.view.Gravity;
const MainActivity = android.app.Activity.extend("my.application.name.MainActivity", {
onCreate: function (savedInstanceState) {
super.onCreate(savedInstance);
// creating LinearLayout
let linLayout = new LinearLayout(this);
// specifying vertical orientation
linLayout.setOrientation(LinearLayout.VERTICAL);
// creating LayoutParams
let linLayoutParam = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
// set LinearLayout as a root element of the screen
setContentView(linLayout, linLayoutParam);
let lpView = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
let tv = new TextView(this);
tv.setText("TextView");
tv.setLayoutParams(lpView);
linLayout.addView(tv);
let btn = new Button(this);
btn.setText("Button");
linLayout.addView(btn, lpView);
let leftMarginParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
leftMarginParams.leftMargin = 50;
let btn1 = new Button(this);
btn1.setText("Button1");
linLayout.addView(btn1, leftMarginParams);
let rightGravityParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
rightGravityParams.gravity = Gravity.RIGHT;
let btn2 = new Button(this);
btn2.setText("Button2");
linLayout.addView(btn2, rightGravityParams);
}
});
const LinearLayout = android.widget.LinearLayout;
const LayoutParams = android.widget.LinearLayout.LayoutParams;
const TextView = android.widget.TextView;
const Button = android.widget.Button;
const Gravity = android.view.Gravity;
@JavaProxy("my.application.name.MainActivity");
class MainActivity extends android.app.Activity {
constructor() {
super();
return global.__native(this);
}
onCreate: function (savedInstanceState) {
super.onCreate(savedInstance);
// creating LinearLayout
let linLayout = new LinearLayout(this);
// specifying vertical orientation
linLayout.setOrientation(LinearLayout.VERTICAL);
// creating LayoutParams
let linLayoutParam = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
// set LinearLayout as a root element of the screen
setContentView(linLayout, linLayoutParam);
let lpView = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
let tv = new TextView(this);
tv.setText("TextView");
tv.setLayoutParams(lpView);
linLayout.addView(tv);
let btn = new Button(this);
btn.setText("Button");
linLayout.addView(btn, lpView);
let leftMarginParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
leftMarginParams.leftMargin = 50;
let btn1 = new Button(this);
btn1.setText("Button1");
linLayout.addView(btn1, leftMarginParams);
let rightGravityParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
rightGravityParams.gravity = Gravity.RIGHT;
let btn2 = new Button(this);
btn2.setText("Button2");
linLayout.addView(btn2, rightGravityParams);
}
});