Skip to main content

Android implementation

Let's use Android Studio for writing Android code. Launch Android Studio and open the project under <your-project-dir>/android path. When the project is opened, find screen-orientation-package inside project-tree

The screen-orientation-package contains 3 packages with the same name com.screenorientationpackage. After expanding them, you'll notice that these contain following things:

  • code-generated Java spec files
  • ScreenOrientationModule class stub files
  • ScreenOrientationModuleImpl class stub file
  • ScreenOrientationTurboPackage class stub file

Let's start implementing!

ScreenOrientationModuleImpl.kt

Let's start by creating a small pure Kotlin class that will be responsible for registering orientation listener:

android/src/main/java/com/screenorientationpackage/ScreenOrientationModuleImpl.kt
package com.screenorientationpackage

import android.hardware.SensorManager
import android.view.OrientationEventListener
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule

/**
* Native module's shared implementation
*/
class ScreenOrientationModuleImpl(
private val reactContext: ReactApplicationContext
) {
/**
* Example usage:
*
* ```kotlin
* sendEvent(EVENT_NAME, Arguments.createMap().apply {
* putDouble(EVENT_KEY, 42)
* });
* ```
*/
private fun sendEvent(eventName: String, params: WritableMap?) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}

private var mOrientationDidChangeListener: OrientationEventListener? = null
private var mLastOrientation = "unknown"

fun onInitialize() {
mOrientationDidChangeListener = object : OrientationEventListener(reactContext, SensorManager.SENSOR_DELAY_NORMAL) {
override fun onOrientationChanged(orientation: Int) {
val screenOrientation = if (orientation > 315 || orientation < 45 || (orientation in 135..225)) {
"portrait"
} else {
"landscape"
}

if (mLastOrientation == screenOrientation) {
return
}
mLastOrientation = screenOrientation

sendEvent(EVENT_NAME, Arguments.createMap().apply {
putString("orientation", screenOrientation)
})
}
}
mOrientationDidChangeListener?.enable()
}

fun onInvalidate() {
mOrientationDidChangeListener?.disable()
mOrientationDidChangeListener = null
}

companion object {
const val EVENT_NAME = "onScreenOrientationModuleChange"
const val EVENT_KEY = "value"
const val NAME = "ScreenOrientationModule"
}
}

ScreenOrientationModuleImpl class declares two public methods responsible for registering/unregistering orientation listener. The orientation listener has a logic inside onOrientationChanged callback that checks if there's new orientation value to be emitted and, if yes, emits it to the JS world. To listen to orientation changes, we're using OrientationEventListener abstract class. To emit events, we are using RCTDeviceEventEmitter class, to use it in a convienient way, we wrap it in sendEvent helper method. The payload of the event is created thanks to Arguments.createMap utility helper.

ScreenOrientationModule.kt

Now, let's move to the module class:

android/src/newarch/java/com/screenorientationpackage/ScreenOrientationModule.kt
package com.screenorientationpackage

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule

/**
* Declare Kotlin class for new arch native module implementation
*
* Each turbo module extends codegenerated spec class
*
* Class should be annotated with @ReactModule decorator
*/
@ReactModule(name = ScreenOrientationModule.NAME)
class ScreenOrientationModule(
// Each native module class consumes react application context
reactContext: ReactApplicationContext
) : NativeScreenOrientationModuleSpec(reactContext) {
// Use shared module implementation and forward react application context
private val moduleImpl = ScreenOrientationModuleImpl(reactContext)

// Return the name of the module - it should match the name provided in JS specification
override fun getName() = ScreenOrientationModuleImpl.NAME

override fun initialize() {
super.initialize()
moduleImpl.onInitialize()
}

override fun invalidate() {
moduleImpl.onInvalidate()
super.invalidate()
}

override fun getTypedExportedConstants(): MutableMap<String, Any> {
return mutableMapOf(
PORTRAIT to PORTRAIT,
LANDSCAPE to LANDSCAPE
)
}

override fun addListener(eventName: String?) = Unit

override fun removeListeners(count: Double) = Unit

companion object {
const val PORTRAIT = "PORTRAIT"
const val LANDSCAPE = "LANDSCAPE"
const val NAME = ScreenOrientationModuleImpl.NAME
}
}

Here we declare the ScreenOrientationModule class. It extends codegenerated spec class and takes ReactApplicationContext instance as constructor parameter. Additionally, ScreenOrientationModule is annotated with ReactModule decorator. Static constant NAME matches the value declared in JS specification.

To start/stop listening to orientation change events, we call ScreenOrientationModuleImpl methods inside overriden initialize & invalidate lifecycle methods.

Additionally, we have to add empty implementation for addListener & removeListeners methods (to satisfy codegenerated spec base class).

To export constants, we have to override getTypedExportedConstants method which returns map of key-value pairs.

Old architecture module
The implementation of old architecture module won't be visible in Android Studio when you have new architecture enabled. To handle that, you can open android/src/oldarch/java/com/screenorientationpackage/ScreenOrientationModule.kt at other text editor and paste following content:

package com.screenorientationpackage

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.module.annotations.ReactModule

/**
* Declare Kotlin class for old arch native module implementation
*
* Each native module extends ReactContextBaseJavaModule class
*
* Class should be annotated with @ReactModule decorator
*/
@ReactModule(name = ScreenOrientationModule.NAME)
class ScreenOrientationModule(
// Each native module class consumes react application context
reactContext: ReactApplicationContext
) : ReactContextBaseJavaModule(reactContext) {
// Use shared module implementation and forward react application context
private val moduleImpl = ScreenOrientationModuleImpl(reactContext)

// Return the name of the module - it should match the name provided in JS specification
override fun getName() = ScreenOrientationModuleImpl.NAME

override fun initialize() {
super.initialize()
moduleImpl.onInitialize()
}

override fun invalidate() {
moduleImpl.onInvalidate()
super.invalidate()
}

override fun getConstants(): MutableMap<String, Any> {
return mutableMapOf(
PORTRAIT to PORTRAIT,
LANDSCAPE to LANDSCAPE
)
}

@ReactMethod
fun addListener(eventName: String?) = Unit

@ReactMethod
fun removeListeners(count: Double) = Unit

companion object {
const val PORTRAIT = "PORTRAIT"
const val LANDSCAPE = "LANDSCAPE"
const val NAME = ScreenOrientationModuleImpl.NAME
}
}

Let's finalize it by exporting the module in the TurboReactPackage instance.

ScreenOrientationTurboPackage.kt

The last thing we need to do is to export ScreenOrientationModule in the TurboReactPackage instance. Let's go to ScreenOrientationTurboPackage.kt and add our new module.

android/src/main/java/com/screenorientationpackage/ScreenOrientationTurboPackage.kt
package com.screenorientationpackage

import com.facebook.react.TurboReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.turbomodule.core.interfaces.TurboModule

class ScreenOrientationTurboPackage : TurboReactPackage() {
/**
* Initialize and export modules based on the name of the required module
*/
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return when (name) {
ScreenOrientationModule.NAME -> ScreenOrientationModule(reactContext)
else -> null
}
}

/**
* Declare info about exported modules
*/
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
/**
* Here declare the array of exported modules
*/
val moduleList: Array<Class<out NativeModule?>> = arrayOf(
ScreenOrientationModule::class.java
)
val reactModuleInfoMap: MutableMap<String, ReactModuleInfo> = HashMap()
/**
* And here just iterate on that array and produce the info provider instance
*/
for (moduleClass in moduleList) {
val reactModule = moduleClass.getAnnotation(ReactModule::class.java) ?: continue
reactModuleInfoMap[reactModule.name] =
ReactModuleInfo(
reactModule.name,
moduleClass.name,
true,
reactModule.needsEagerInit,
reactModule.hasConstants,
reactModule.isCxxModule,
TurboModule::class.java.isAssignableFrom(moduleClass)
)
}
return ReactModuleInfoProvider { reactModuleInfoMap }
}
}

To export the module, as the first step, we need to return it from getModule method inside ScreenOrientationTurboPackage, if it's requested (the method takes name as a parameter and makes decision which module should be served).

The second step is to implement getReactModuleInfoProvider method, where the module is injected to the info provider instance.

You can check training repo for Kotlin implementation here and Java implementation here.

That's Android part, now let's wrap things up and try to use screen orientation module in action!