Platform decorators
Use @iOS(...) and @Android(...) on an export declare class in a .nitro.ts spec to link the native classes for each platform:
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'
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:
| Decorator | Languages |
|---|---|
@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:
- Swift
- Kotlin
import NitroModules
class HybridMath: HybridMathSpec {
...
}
Ensure HybridMath.swift is being built by your project/SPM package.
package com.margelo.nitro.math
class HybridMath: HybridMathSpec() {
...
}
If HybridMath lives in a different Java package pass package: [...] to @Android(...).
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:
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:
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:
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:
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:
- Swift
- Kotlin
import UIKit
public protocol NativeImage {
var uiImage: UIImage { get }
}
package com.margelo.nitro.image
public interface NativeImage {
public val bitmap: android.graphics.Bitmap
}
Then declare your interface/protocol conformance via implementedInterfaces/protocolConformances:
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 ...
}
}