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 filesScreenOrientationModuleImpl
class stub fileScreenOrientationTurboPackage
class stub file
Let's start implementing!
- Kotlin
- Java
ScreenOrientationModuleImpl.kt
Let's start by creating a small pure Kotlin class that will be responsible for registering orientation listener:
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:
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
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.
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.
ScreenOrientationModuleImpl.java
Let's start by creating a small pure Java class that will be responsible for registering orientation listener:
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;
import java.util.Objects;
/**
* Native module's shared implementation
*/
public class ScreenOrientationModuleImpl {
private final ReactApplicationContext reactContext;
public static final String NAME = "ScreenOrientationModule";
public static final String EVENT_NAME = "onScreenOrientationModuleChange";
public static final String EVENT_KEY = "value";
public ScreenOrientationModuleImpl(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}
/**
* Example usage:
*
* ```java
* WritableMap payload = Arguments.createMap();
* payload.putDouble(EVENT_KEY, 42);
* sendEvent(EVENT_NAME, payload);
* ```
*/
private void sendEvent(String eventName, WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
private OrientationEventListener mOrientationDidChangeListener = null;
private String mLastOrientation = "unknown";
public void onInitialize() {
mOrientationDidChangeListener = new OrientationEventListener(reactContext, SensorManager.SENSOR_DELAY_NORMAL) {
@Override
public void onOrientationChanged(int orientation) {
String screenOrientation;
if (orientation > 315 || orientation < 45 || (orientation > 135 && orientation < 225)) {
screenOrientation = "portrait";
} else {
screenOrientation = "landscape";
}
if (Objects.equals(mLastOrientation, screenOrientation)) {
return;
}
mLastOrientation = screenOrientation;
WritableMap payload = Arguments.createMap();
payload.putString("orientation", screenOrientation);
sendEvent(EVENT_NAME, payload);
}
};
mOrientationDidChangeListener.enable();
}
public void onInvalidate() {
if (mOrientationDidChangeListener != null) {
mOrientationDidChangeListener.disable();
}
mOrientationDidChangeListener = null;
}
}
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.java
Now, let's move to the module class:
package com.screenorientationpackage;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import java.util.HashMap;
import java.util.Map;
/**
* Declare Java 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)
public class ScreenOrientationModule extends NativeScreenOrientationModuleSpec {
public static final String PORTRAIT = "PORTRAIT";
public static final String LANDSCAPE = "LANDSCAPE";
public static final String NAME = ScreenOrientationModuleImpl.NAME;
// Use shared module implementation and forward react application context
private final ScreenOrientationModuleImpl moduleImpl;
public ScreenOrientationModule(ReactApplicationContext reactContext) {
super(reactContext);
this.moduleImpl = new ScreenOrientationModuleImpl(reactContext);
}
// Return the name of the module - it should match the name provided in JS specification
@Override
public String getName() {
return ScreenOrientationModuleImpl.NAME;
}
@Override
public void initialize() {
super.initialize();
moduleImpl.onInitialize();
}
@Override
public void invalidate() {
moduleImpl.onInvalidate();
super.invalidate();
}
@Override
protected Map<String, Object> getTypedExportedConstants() {
final Map<String, Object> map = new HashMap<>();
map.put(PORTRAIT, PORTRAIT);
map.put(LANDSCAPE, LANDSCAPE);
return map;
}
@Override
public void addListener(String eventName) {}
@Override
public void removeListeners(double count) {}
}
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
android/src/oldarch/java/com/screenorientationpackage/ScreenOrientationModule.java
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;
import java.util.HashMap;
import java.util.Map;
/**
* Declare Java class for old arch native module implementation
*
* Each native module extends ReactContextBaseJavaModule class
*
* Class should be annotated with @ReactModule decorator
*/
@ReactModule(name = ScreenOrientationModule.NAME)
public class ScreenOrientationModule extends ReactContextBaseJavaModule {
public static final String PORTRAIT = "PORTRAIT";
public static final String LANDSCAPE = "LANDSCAPE";
public static final String NAME = ScreenOrientationModuleImpl.NAME;
// Use shared module implementation and forward react application context
private final ScreenOrientationModuleImpl moduleImpl;
public ScreenOrientationModule(ReactApplicationContext reactContext) {
super(reactContext);
this.moduleImpl = new ScreenOrientationModuleImpl(reactContext);
}
// Return the name of the module - it should match the name provided in JS specification
@Override
public String getName() {
return ScreenOrientationModuleImpl.NAME;
}
@Override
public void initialize() {
super.initialize();
moduleImpl.onInitialize();
}
@Override
public void invalidate() {
moduleImpl.onInvalidate();
super.invalidate();
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> map = new HashMap<>();
map.put(PORTRAIT, PORTRAIT);
map.put(LANDSCAPE, LANDSCAPE);
return map;
}
@ReactMethod
public void addListener(String eventName) {}
@ReactMethod
public void removeListeners(double count) {}
}
Let's finalize it by exporting the module in the TurboReactPackage
instance.
ScreenOrientationTurboPackage.java
The last thing we need to do is to export ScreenOrientationModule
in the TurboReactPackage
instance. Let's go to ScreenOrientationTurboPackage.java
and add our new module.
package com.screenorientationpackage;
import androidx.annotation.Nullable;
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;
import java.util.HashMap;
import java.util.Map;
public class ScreenOrientationTurboPackage extends TurboReactPackage {
/**
* Initialize and export modules based on the name of the required module
*/
@Override
@Nullable
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
if (name.equals(ScreenOrientationModule.NAME)) {
return new ScreenOrientationModule(reactContext);
}
return null;
}
/**
* Declare info about exported modules
*/
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
/**
* Here declare the array of exported modules
*/
Class<? extends NativeModule>[] moduleList = new Class[] {
ScreenOrientationModule.class
};
final Map<String, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
/**
* And here just iterate on that array and produce the info provider instance
*/
for (Class<? extends NativeModule> moduleClass : moduleList) {
ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class);
reactModuleInfoMap.put(
reactModule.name(),
new ReactModuleInfo(
reactModule.name(),
moduleClass.getName(),
true,
reactModule.needsEagerInit(),
reactModule.hasConstants(),
reactModule.isCxxModule(),
TurboModule.class.isAssignableFrom(moduleClass)
)
);
}
return new ReactModuleInfoProvider() {
@Override
public Map<String, ReactModuleInfo> getReactModuleInfos() {
return 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!