Advanced Concepts
Android Marshalling
NativeScript seamlessly handles data type conversion between JavaScript and Java/Kotlin, utilizing type inference and dedicated wrappers, ensuring smooth integration and type safety in cross-platform development.
String Conversion
Converting JavaScript String to Java string types
JavaScript String maps to java.lang.String:
var context = ...;
var button = new android.widget.Button(context);
var text = "My Button";
button.setText(text);
button.setText(text)
- text
is converted to java.lang.String
Converting JavaScript String to Kotlin string types
JavaScript String maps to kotlin.String:
const kotlinClass = new com.example.KotlinClassWithStringProperty()
var text = 'My Button'
kotlinClass.setStringProperty(text) /
kotlinClass.setStringProperty(text)
- JavaScript text
is converted to kotlin.String
Converting Java string types to JavaScript String
Both java.lang.String and java.lang.Character types are projected as JavaScript String:
var file = new java.io.File('/path/to/file')
var path = file.getPath()
getPath()
- returns java.lang.String
, converted to JS String
Converting Kotlin string types to JavaScript String
Both kotlin.String and kotlin.Char types are projected as JavaScript String:
package com.example
class KotlinClassWithStringAndCharProperty {
val stringProperty: String = "string property"
val charProperty: Char = 'c'
}
var kotlinClass = new com.example.KotlinClassWithStringAndCharProperty()
var str1 = kotlinClass.getStringProperty() // returns kotlin.String, converted to JS String
var str2 = kotlinClass.getCharProperty() // returns kotlin.Char, converted to JS String
getStringProperty()
- returnskotlin.String
, converted toJS String
getCharProperty()
- returnskotlin.Char
, converted to JSString
Boolean conversion
JavaScript Boolean to Java boolean type
JavaScript Boolean maps to Java primitive boolean.
var context = ...;
var button = new android.widget.Button(context);
var enabled = false;
button.setEnabled(enabled);
button.setEnabled(enabled)
- JavaScript Boolean enabled
is converted to Java primitive boolean.
JavaScript Boolean to Kotlin boolean type
JavaScript Boolean maps to Kotlin class Boolean.
Converting from Java boolean to JavaScript boolean
Both the primitive boolean and reference java.lang.Boolean types are projected as JavaScript Boolean:
var context = ...
var button = new android.widget.Button(context);
var enabled = button.isEnabled();
isEnabled()
- returns primitive boolean
, converted to JS Boolean
Converting from Kotlin boolean to JavaScript boolean
Kotlin's boolean type kotlin.Boolean is mapped to JavaScript Boolean:
package com.example
class KotlinClassWithBooleanProperty {
val booleanProperty: Boolean = false
}
var kotlinClass = new com.example.KotlinClassWithBooleanProperty()
var enabled = kotlinClass.getBooleanProperty() // returns Kotlin Boolean, converted to JS Boolean
getBooleanProperty()
- returns Kotlin Boolean
, converted to JS Boolean
.
Numeric data types
Converting JavaScript Number to Java/Kotlin numeric types
Java and Kotlin have several primitive numeric types while JavaScript has the Number
type only. Additionally, unlike JavaScript, Java and Kotlin support Method Overloading, which makes the numeric conversion more complex.
Consider the following examples:
class MyObject extends java.lang.Object {
public void myMethod(byte value){
}
public void myMethod(short value){
}
public void myMethod(int value){
}
public void myMethod(long value){
}
public void myMethod(float value){
}
public void myMethod(double value){
}
}
class MyObject : Any() {
fun myMethod(value: Byte) {}
fun myMethod(value: Short) {}
fun myMethod(value: Int) {}
fun myMethod(value: Long) {}
fun myMethod(value: Float) {}
fun myMethod(value: Double) {}
}
The following logic applies when calling myMethod
on a myObject
instance from JavaScript:
var myObject = new MyObject()
Implicit conversion
- integer conversion:
When you call
myObject.myMethod(10)
the runtime implicitly converts the JavaScript 10(Number)
to Java/Kotlin Int
and then calls the myMethod(Int)
method.
Note
If there is no myMethod(Int) implementation, the Android runtime will try to choose the best possible overload with least conversion loss. If no such method is found an exception will be raised.
- floating-point conversion:
myObject.myMethod(10.5) // myMethod(Double) will be called.
The JavaScript 10.5
Number
gets converted to Java/Kotlin double
and then myMethod(ouble) gets called.
Note
In a scenario where no myMethod(double) implementation exists, the Runtime will attempt to select the most suitable possible overload, with the least amount of conversion loss. If no such method is found an exception thrown.
Explicit conversion
To explicitly convert from a JavaScript Number
to Java/Kotlin numeric data types, and call a specific method overload, NativeScript provides the following functions in the global scope:
byte(number)
→ (Java primitive byte
|Kotlin Byte
)
- The number value will be truncated and only its first byte of the whole part will be used.short(number)
→ Java primitive short
Converting Java numeric types to JavaScript Number
The following Java types are converted to the JavaScript Number:
- Primitive byte and reference java.lang.Byte :
var byte = new java.lang.Byte('1')
var jsByteValue = byte.byteValue() // returns primitive byte, converted to Number
- Primitive short and reference java.lang.Short :
var short = new java.lang.Short('1')
var jsShortValue = short.shortValue() // returns primitive short, converted to Number
- Primitive int and reference java.lang.Integer:
var int = new java.lang.Integer('1')
var jsIntValue = int.intValue() // returns primitive int, converted to Number
intValue()
- returns primitive int
, converted to Number
- Primitive float and reference java.lang.Float :
var float = new java.lang.Float('1.5')
var jsFloatValue = float.floatValue() // returns primitive float, converted to Number
floatValue()
returns a primitive float
, converted to Number
.
- Primitive double and reference java.lang.Double :
var double = new java.lang.Double('1.5')
var jsDoubleValue = double.doubleValue() // returns primitive double, converted to Number
doubleValue()
returns a primitive float
, converted to Number
.
- Long & Primitive long
java.lang.Long and its primitive equivalent are special types which are projected to JavaScript by applying the following rules:
- If the value is in the interval `(-2^53, 2^53)` then it is converted to [Number](http://www.w3schools.com/jsref/jsref_obj_number.asp)
- Else a special object with the following characteristics is created:
- Has Number.NaN set as a prototype
- Has value property set to the string representation of the Java long value
- Its `valueOf()` method returns NaN
- Its `toString()` method returns the string representation of the Java long value
public class TestClass {
public long getLongNumber54Bits(){
return 1 << 54;
}
public long getLongNumber53Bits(){
return 1 << 53;
}
}
var testClass = new TestClass()
var jsNumber = testClass.getLongNumber53Bits()
var specialObject = testClass.getLongNumber54Bits()
jsNumber
is a JavaScript Number and specialObject
is the special object discussed above.
Converting Kotlin numeric types to JavaScript Number
Similar to the conversion from Java numeric data types to JavaScript Number, the following Kotlin numeric data types are converted to JavaScript Number type:
- Kotlin's byte type kotlin.Byte:
package com.example
class KotlinClassWithByteProperty {
val byteProperty: Byte = 42
}
var kotlinClass = new com.example.KotlinClassWithByteProperty()
var jsByteValue = kotlinClass.getByteProperty() // returns Kotlin Byte, converted to Number
- Kotlin's short type kotlin.Short:
package com.example
class KotlinClassWithShortProperty {
val shortProperty: Short = 42
}
var kotlinClass = new com.example.KotlinClassWithShortProperty()
var jsShortValue = kotlinClass.getShortProperty()
getShortProperty()
- returns Kotlin Short
, converted to Number
- Kotlin's integer type kotlin.Int:
package com.example
class KotlinClassWithIntProperty {
val intProperty: Int = 42
}
var kotlinClass = new com.example.KotlinClassWithIntProperty()
var jsIntValue = kotlinClass.getIntProperty()
getIntProperty()
- returns Kotlin Int
, converted to Number
- Kotlin's float type kotlin.Float:
package com.example
class KotlinClassWithFloatProperty {
val floatProperty: Float = 42.0f
}
var kotlinClass = new com.example.KotlinClassWithFloatProperty()
getFloatProperty()
- returns Kotlin Float
, converted to Number
- Kotlin's double type kotlin.Double:
package com.example
class KotlinClassWithDoubleProperty {
val doubleProperty: Double = 42.0
}
var kotlinClass = new com.example.KotlinClassWithDoubleProperty()
var jsDoubleValue = kotlinClass.getDoubleProperty()
getDoubleProperty()
- returns Kotlin double
, converted to Number
Kotlin's long type kotlin.Long is a special type which is projected to JavaScript by applying the following rules:
- If the value is in the interval
(-2^53, 2^53)
then it is converted to Number - Else a special object with the following characteristics is created:
- Has Number.NaN set as a prototype
- Has value property set to the string representation of the Kotlin long value
- Its valueOf() method returns NaN
- Its toString() method returns the string representation of the Kotlin long value
- If the value is in the interval
package com.example
class KotlinClassWithLongProperties {
val longNumber54Bits: Long
get() = (1 shl 54).toLong()
val longNumber53Bits: Long
get() = (1 shl 53).toLong()
}
var kotlinClass = new com.example.KotlinClassWithLongProperties()
var jsNumber = kotlinClass.getLongNumber53Bits()
var specialObject = kotlinClass.getLongNumber54Bits()
jsNumber
is a JavaScript Number and specialObject
is the special object discussed above.
Converting Undefined & Null
JavaScript Undefined & Null maps to Java and Kotlin null literal (or null pointer).
var context = ...;
var button = new android.widget.Button(context);
button.setOnClickListener(undefined);
In the abve example, the Java call will be made using the null
keyword.
Array conversion
A JavaScript Array is implicitly converted to a Java Array or a Kotlin Array, using the above described rules for type conversion of the array's elements. For example:
var items = ['One', 'Two', 'Three']
var myObject = new MyObject()
myObject.myMethod(items)
class MyObject extends java.lang.Object {
public void myMethod(java.lang.String[] items){
}
}
class MyObject : Any() {
fun myMethod(items: Array<String>) {}
}
Converting from Java/Kotlin arrays to JavaScript array
Array in Java/Kotlin is a special java.lang.Object that have an implicit Class associated. A Java/Kotlin Array is projected to JavaScript as a special JavaScript proxy object with the following characteristics:
- Has length property
- Has registered indexed getter and setter callbacks, which:
- If the array contains elements of type convertible to a JavaScript type, then accessing the i-th element will return a converted type
- If the array contains elements of type non-convertible to JavaScript, then accessing the i-th element will return a proxy object over the Java/Kotlin type see Accessing APIs
var directory = new java.io.File('path/to/myDir')
var files = directory.listFiles() // files is a special object as described above
var singleFile = files[0] // the indexed getter callback is triggered and a proxy object over the java.io.File is returned
package com.example
class KotlinClassWithStringArrayProperty {
val stringArrayProperty: Array<String> = arrayOf("element1", "element2", "element3")
}
var kotlinClass = new com.example.KotlinClassWithStringArrayProperty()
var kotlinArray = kotlinClass.getStringArrayProperty() // kotlinArray is a special object as described above
var firstStringElement = kotlinArray[0] // the indexed getter callback is triggered and the kotlin.String is returned as a JS string
Note
A Java/Kotlin Array is intentionally not converted to a JavaScript Array for the sake of performance, especially when it comes to large arrays.
Array of Objects
In scenarios where the creation of Java/Kotlin arrays from JavaScript is mandatory. In the given scenario, we have extended the built-in JavaScript Array object by adding a custom method named create. By doing so, we have augmented the default functionality of the Array object with our own implementation to cater to specific requirements in built-in JavaScript Array
object. Here are some examples how to use Array.create
method:
// the following statement is equivalent to byte[] byteArr = new byte[10];
var byteArr = Array.create('byte', 10)
// the following statement is equivalent to String[] stringArr = new String[10];
var stringArr = Array.create(java.lang.String, 10)
Here is the full specification for Array.create
method
Array.create(elementClassName, length)
Array.create(javaClassCtorFunction, length)
The first signature accepts a string
type for an elementClassName
. This option becomes beneficial when the creation of a Java array of primitive types (e.g. char
, boolean
, byte
, short
, int
, long
, float
and double
) is required. This also applicable when the creation of Java jagged arrays is needed. For this scenario elementClassName
must be the standard JNI class notation. Here are some examples:
// equivalent to int[][] jaggedIntArray2 = new int[10][];
var jaggedIntArray2 = Array.create('[I', 10)
// equivalent to boolean[][][] jaggedBooleanArray3 = new boolean[10][][];
var jaggedBooleanArray3 = Array.create('[[Z', 10)
// equivalent to Object[][][][] jaggedObjectArray4 = new Object[10][][][];
var jaggedObjectArray4 = Array.create('[[[Ljava.lang.Object;', 10)
The second signature requires you to provide the javaClassCtorFunction
, which must be the JavaScript constructor function representing the desired Java type. Here are some examples:
// equivalent to String[] stringArr = new String[10];
var stringArr = Array.create(java.lang.String, 10)
// equivalent to Object[] objectArr = new Object[10];
var objectArr = Array.create(java.lang.Object, 10)
Array of Primitive Types
When dealing with arrays of primitive types, automatic marshalling is not supported. To pass them as arguments to a method, you'll need to use wrapper classes (e.g., Integer, Double) to convert the primitives into objects, allowing for automatic marshalling.
public static void myMethod(int[] someParam)
Then yoy need to invoke it as follows:
let arr = Array.create('int', 3)
arr[0] = 1
arr[1] = 2
arr[2] = 3
SomeObject.myMethod(arr) // assuming the method is accepting an array of primitive types
However there are some other helpful classes we can use to create a few other arrays of primitive types
const byteArray = java.nio.ByteBuffer.wrap([1]).array()
const shortArray = java.nio.ShortBuffer.wrap([1]).array()
const intArray = java.nio.IntBuffer.wrap([1]).array()
const longArray = java.nio.LongBuffer.wrap([1]).array()
const floatArray = java.nio.FloatBuffer.wrap([1]).array()
const doubleArray = java.nio.DoubleBuffer.wrap([1]).array()
Two-Dimensional Arrays of Primitive Types
The above scenario gets more tricky with two-dimensional arrays. Consider a Java method that accepts as an argument a two-dimensional array:
public static void myMethod(java.lang.Integer[][] someParam)
The marshalled JavaScript code will look like this:
let arr = Array.create('[Ljava.lang.Integer;', 2)
let elements = Array.create('java.lang.Integer', 3)
elements[0] = new java.lang.Integer(1)
elements[1] = new java.lang.Integer(2)
elements[2] = new java.lang.Integer(3)
arr[0] = elements
SomeObject.myMethod(arr) // assuming the method is accepting a two-dimensional array of primitive types
interface Printer {
fun print(content: String)
fun print(content: String, offset: Int)
}
interface Copier {
fun copy(content: String): String
}
interface Writer {
fun write(arr: Array<Any>)
fun writeLine(arr: Array<Any>)
}
Implementing the interfaces:
public class MyVersatileCopywriter implements Printer, Copier, Writer {
public void print(String content) { ... }
public void print(String content, int offset) { ... }
public String copy(String content) { ... }
public void write(Object[] arr) { ... }
public void writeLine(Object[] arr) { ... }
}
class MyVersatileCopywriter: Printer, Copier, Writer{
override fun print(content: String) { ... }
override fun print(content: String, offset: Int) { ... }
override fun copy(content: String): String { ... }
override fun write(arr: Array<Any>) { ... }
override fun writeLine(arr: Array<Any>) { ... }
}
The same result can be achieved in NativeScript by extending any valid object that inherits Java Object.
- In JavaScript - Declare an
interfaces
array in the implementation - Using Typescript syntax - apply a
decorator
to the extended class (note@Interfaces([...])
)
Using Javascript syntax - attach interfaces
array to implementation object of the extend call
let MyVersatileCopyWriter = java.lang.Object.extend({
interfaces: [com.a.b.Printer, com.a.b.Copier, com.a.b.Writer], /* the interfaces that will be inherited by the resulting class */
print: function() { ... }, /* implementing the 'print' methods from Printer */
copy: function() { ... }, /* implementing the 'copy' method from Copier */
write: function() { ... }, /* implementing the 'write' method from Writer */
writeLine: function() { ... }, /* implementing the 'writeLine' method from Writer */
toString: function() { ... } /* override `java.lang.Object's` `toString */
});
@Interfaces([com.a.b.Printer, com.a.b.Copier, com.a.b.Writer]) /* the interfaces that will be inherited by the resulting MyVersatileCopyWriter class */
class MyVersatileCopyWriter extends java.lang.Object {
constructor() {
super();
return global.__native(this);
}
print() { ... }
copy() { ... }
write() { ... }
writeLine() { ... }
}
WARNING
Implementing two interfaces with the same method signature will generate just 1 method. It is the implementor's responsibility to define how the method will behave for both interfaces
Implementing two interfaces with the same method name, parameter number, but different return type (
void a()
vsboolean a()
) will result in a compilation error.
Note
Java/Kotlin method overloads are handled by the developer by explicitly checking the arguments count of the invoked function
class MyVersatileCopyWriter extends ... {
constructor() {
super();
return global.__native(this);
}
...
print() {
let content = "";
let offset = 0;
if (arguments.length == 2) {
offset = arguments[1];
}
content = arguments[0];
// do stuff
}
...
}
Note
In OOP, when a class extends another class aka inheritance, the new class not only gains access to the interface methods it must implement, but also has the ability to override methods from the extended class. Moreover, it can introduce new methods specific to the new class's functionality. This enables the new class to extend and enhance the behaviour of its parent class while providing additional functionality of its own.
Java nested types in NativeScript
public class Outer {
public class Inner {
// inner and nested class
}
public static class Nested {
// nested but not inner class
}
}
//Instantiate nested types
Outer outer = new Outer();
Outer.Inner inner1 = outer.new Inner();
Outer.Inner inner2 = new Outer().new Inner();
Outer.Nested nested = new Outer.Nested()
var outer = new Outer()
var inner1 = new outer.Inner()
var inner2 = new new Outer().Inner()
var nested = new Outer.Nested()
Kotlin Types
All Kotlin types are projected to JavaScript using the Package and Class proxies as described in
Kotlin Companion objects
You can access Kotlin's companion objects via the Companion
field:
package com.example
class KotlinClassWithCompanion {
companion object {
fun getDataFromCompanion() = "some data"
}
}
var companion = com.example.KotlinClassWithCompanion.Companion
var data = companion.getDataFromCompanion()
Kotlin Object
To access the Kotlin's objects, use the INSTANCE
field:
package com.example
object KotlinObject {
fun getDataFromObject() = "some data"
}
var objectInstance = com.example.KotlinObject.INSTANCE
var data = objectInstance.getDataFromObject()
Accessing Kotlin properties
To access the Kotlin's properties, use their compiler-generated get/set methods. Non-boolean Kotlin properties could be used in NativeScript applications as JS fields as well.
package com.example
class KotlinClassWithStringProperty(var stringProperty: kotlin.String)
var kotlinClass = new com.example.KotlinClassWithStringProperty()
var propertyValue = kotlinClass.getStringPropert()
kotlinClass.setStringProperty('example')
propertyValue = kotlinClass.stringProperty
kotlinClass.stringProperty = 'second example'
Accessing Kotlin package-level functions
In order to use a Kotlin package-level function, the class where it's defined should be known. Let's take a look at an example:
package com.example
fun getRandomNumber() = 42
var randomNumber = com.example.FunctionsKt.getRandomNumber()
In the example above, the class FunctionsKt
is autogenerated by the Kotlin compiler and its name is based on the name of the file where the functions are defined. Kotlin supports annotating a file to have a user provided name and this simplifies using package-level functions:
var randomNumber = com.example.UtilityFunctions.getRandomNumber()
@file:JvmName("UtilityFunctions")
package com.example
fun getRandomNumber() = 42
Accessing Kotlin extension functions
In order to use an extension function, the class where it's defined is required to be known. Also, when invoking this function, the first parameter should be an instance of the type for which the function is defined. Let's take a look at an example:
package com.example
import java.util.ArrayList
fun ArrayList<String>.switchPlaces(firstElementIndex: Int, secondElementIndex: Int) {
val temp = this[firstElementIndex]
this[firstElementIndex] = this[secondElementIndex]
this[secondElementIndex] = temp
}
var arrayList = new java.util.ArrayList()
arrayList.add('firstElement')
arrayList.add('secondElement')
com.example.Extensions.switchPlaces(arrayList, 0, 1)
In the example above, the class ExtensionsKt
is autogenerated by the Kotlin compiler and its name is derived on the name of the file where the functions are defined. Kotlin supports annotating a file to have a user provided name, simplifying using package-level functions:
@file:JvmName("ExtensionFunctions")
package com.example
import java.util.ArrayList
fun ArrayList<String>.switchPlaces(firstElementIndex: Int, secondElementIndex: Int) {
val temp = this[firstElementIndex]
this[firstElementIndex] = this[secondElementIndex]
this[secondElementIndex] = temp
}
var arrayList = new java.util.ArrayList()
arrayList.add('firstElement')
arrayList.add('secondElement')
com.example.ExtensionFunctions.switchPlaces(arrayList, 0, 1)
- Previous
- iOS Runtime Types