Skip to main content

Platform decorators

Use @iOS(...) and @Android(...) on an export declare class in a .nitro.ts spec to link the native classes for each platform:

Math.nitro.ts
import { type HybridObject, iOS, Android } from 'nitro-core'

@iOS(...)
@Android(...)
export declare class Math extends HybridObject {
constructor()
static add(a: number, b: number): number
}
export * from '#nitro/Math'
tip

Hybrid Objects with constructors or static methods always require a concrete implementation, while Hybrid Objects without constructors or static methods can also be declared abstract via allowMultipleImplementations: true.

Native language​

Hybrid Objects can be implemented in different languages. C++ is supported on all platforms, and each platform supports other platform-specific languages:

DecoratorLanguages
@iOS'swift', 'c++'
@Android'kotlin', 'c++'

Concrete implementations​

If your Hybrid Object is known to have a concrete implementation, specify it:

import { type HybridObject, iOS, Android } from 'nitro-core'

@iOS({
language: 'swift',
implementation: { className: 'HybridMath' }
})
@Android({
language: 'kotlin',
implementation: { className: 'HybridMath' }
})
export declare class Math extends HybridObject {
...
}
export * from '#nitro/Math'

Now, ensure HybridMath is implemented:

HybridMath.swift
import NitroModules

class HybridMath: HybridMathSpec {
...
}
tip

Ensure HybridMath.swift is being built by your project/SPM package.

Allow inheritance​

Hybrid Objects can be extended to create inheritance chains. If you want your Hybrid Object to be extendable, set allowInheritance: true:

import { type HybridObject, iOS, Android } from 'nitro-core'

@iOS({
...
allowInheritance: true
})
@Android({
...
allowInheritance: true
})
export declare class Image extends HybridObject {
...
}
export * from '#nitro/Image'

@iOS(...)
@Android(...)
export declare class GPUImage extends Image {
...
}
export * from '#nitro/GPUImage'

Prevent inheritance​

To prevent inheritance, set allowInheritance: false - this is also a performance optimization and speeds up calls on your Hybrid Object:

import { type HybridObject, iOS, Android } from 'nitro-core'

@iOS({
...
allowInheritance: false
})
@Android({
...
allowInheritance: false
})
export declare class Math extends HybridObject {
...
}
export * from '#nitro/Math'

Multiple implementations​

If your Hybrid Object is not bound to one specific implementation, set allowMultipleImplementations: true. For example, an Image could be backed by a UIImage, a CGImage, or a GPU buffer:

Image.nitro.ts
import { type HybridObject, iOS } from 'nitro-core'

@iOS({
language: 'swift',
allowMultipleImplementations: true
})
export declare class Image extends HybridObject {
readonly width: number
readonly height: number
}
export * from '#nitro/Image'

Now multiple classes can implement the same spec:

HybridImage.swift
class HybridUIImage: HybridImageSpec {
let uiImage: UIImage
}
class HybridCGImage: HybridImageSpec {
let cgImage: CGImage
}
class HybridGPUImage: HybridImageSpec {
let gpuImage: MTLTexture
}

Image can no longer have a constructor or static methods, but you can still create other Hybrid Objects that can create Image instances - this is a factory pattern:

ImageFactory.nitro.ts
import { type HybridObject, iOS } from 'nitro-core'

@iOS({
language: 'swift',
implementation: { className: 'HybridImageFactory' }
})
export declare class ImageFactory extends HybridObject {
static createUIImage(): Image
static createCGImage(): Image
static createGPUImage(): Image
}
export * from '#nitro/ImageFactory'

Then, simply create your instances natively:

HybridImageFactory.swift
class HybridImageFactory: HybridImageFactorySpec {
static func createUIImage() -> any HybridImageSpec {
return HybridUIImage()
}
static func createCGImage() -> any HybridImageSpec {
return HybridCGImage()
}
static func createGPUImage() -> any HybridImageSpec {
return HybridGPUImage()
}
}

Additional native interface/protocol conformances​

Hybrid Objects are natively represented as interfaces in Kotlin and protocols in Swift. If you often pass Hybrid Objects around, you may end up writing such cast-patterns repeatedly:

class HybridImage: HybridImageSpec {
let uiImage: UIImage
...
}

class HybridImageCropper: HybridImageCropperSpec {
static func crop(image: any HybridImageSpec) throws -> any HybridImageSpec {
// If we want to access image.uiImage, we have to downcast from
// the HybridImageSpec protocol to the HybridImage class first:
guard let image = image as? HybridImage else {
throw RuntimeError("Image was not of type HybridImage!")
}
let uiImage = image.uiImage
return ...
}
}

To avoid these repeated downcasts, simply declare a native Kotlin interface/Swift protocol that describes your native type:

NativeImage.swift
import UIKit

public protocol NativeImage {
var uiImage: UIImage { get }
}

Then declare your interface/protocol conformance via implementedInterfaces/protocolConformances:

Image.nitro.ts
import { type HybridObject, iOS } from 'nitro-core'

@iOS({
language: 'swift',
allowMultipleImplementations: true,
protocolConformances: [{ protocol: 'NativeImage' }]
})
@Android({
language: 'kotlin',
allowMultipleImplementations: true,
implementedInterfaces: [{ interface: 'NativeImage', package: 'com.margelo.nitro.image' }]
})
export declare class Image extends HybridObject {
readonly width: number
}
export * from '#nitro/Image'

Now, any HybridImageSpec has to also conform to the NativeImage interface/protocol, allowing you to skip the downcast:

class HybridImage: HybridImageSpec {
let uiImage: UIImage
...
}

class HybridImageCropper: HybridImageCropperSpec {
static func crop(image: any HybridImageSpec) throws -> any HybridImageSpec {
// any HybridImageSpec now conforms to NativeImage:
let uiImage = image.uiImage
return ...
}
}