Android implementation
Let's use Android Studio to write Android code. Launch Android Studio and open the project under <your-project-dir>/android
path.
When the project is opened, find app-info-package
inside project-tree.
The app-info-package
contains 3 packages with the same name com.appinfopackage
. After expanding them, you'll notice that these contain following things:
- code-generated Java spec files
AppInfoModule
class stub filesAppInfoModuleImpl
class stub fileAppInfoTurboPackage
class stub file
Let's start implementing!
- Kotlin
- Java
AppInfoModuleImpl.kt
Let's start by creating a small pure Kotlin class that will directly retrieve all application informations:
package com.appinfopackage
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import com.facebook.react.bridge.ReactApplicationContext
/**
* Native module's shared implementation
*/
class AppInfoModuleImpl(
private val reactContext: ReactApplicationContext
) {
fun getAppBuildNumber(): String {
var buildNumber = "unknown"
try {
buildNumber = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
getPackageInfo().longVersionCode.toString()
} else {
@Suppress("DEPRECATION")
getPackageInfo().versionCode.toString()
}
} catch (_: Exception) {}
return buildNumber
}
fun getAppBundleId() = reactContext.packageName as String
fun getAppVersion(): String {
var appVersion = "unknown"
try {
appVersion = getPackageInfo().versionName
} catch (_: Exception) {}
return appVersion
}
private fun getPackageInfo(): PackageInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
reactContext
.packageManager
.getPackageInfo(
reactContext.packageName,
PackageManager.PackageInfoFlags.of(0L)
)
} else {
@Suppress("DEPRECATION")
reactContext
.packageManager
.getPackageInfo(reactContext.packageName, 0)
}
}
companion object {
const val NAME = "AppInfoModule"
}
}
AppInfoModuleImpl
declares 3 methods, each of them returns a string value. It also declares static NAME
constant which will be used in RN's module wrapper.
To retrieve bundle id, we will use Context#getPackageName method. For other app informations we will be calling PackageManager#getPackageInfo (which can be obtained with Context#getPackageManager).
AppInfoModule.kt
Now, let's move to the module that will manage function calls from the JS world:
package com.appinfopackage
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 = AppInfoModule.NAME)
class AppInfoModule(
// Each native module class consumes react application context
reactContext: ReactApplicationContext
) : NativeAppInfoModuleSpec(reactContext) {
// Use shared module implementation and forward react application context
private val moduleImpl = AppInfoModuleImpl(reactContext)
// Return the name of the module - it should match the name provided in JS specification
override fun getName() = NAME
// Exported methods are overriden - based on the spec class
override fun getAppBuildNumber() = moduleImpl.getAppBuildNumber()
override fun getAppBundleId() = moduleImpl.getAppBundleId()
override fun getAppVersion() = moduleImpl.getAppVersion()
companion object {
const val NAME = AppInfoModuleImpl.NAME
}
}
Here we declare the AppInfoModule
class.
It extends codegenerated spec class and takes ReactApplicationContext
instance as constructor parameter.
Additionally, AppInfoModule
is annotated with ReactModule
decorator.
Static constant NAME
matches the value declared in JS specification.
To implement codegenerated spec class, we are using methods from AppInfoModuleImpl
class.
Old architecture module
android/src/oldarch/java/com/appinfopackage/AppInfoModule.kt
at other text editor and paste following content:package com.appinfopackage
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 = AppInfoModule.NAME)
class AppInfoModule(
// Each native module class consumes react application context
reactContext: ReactApplicationContext
) : ReactContextBaseJavaModule(reactContext) {
// Use shared module implementation and forward react application context
private val moduleImpl = AppInfoModuleImpl(reactContext)
// Return the name of the module - it should match the name provided in JS specification
override fun getName() = NAME
// Exported methods must be annotated with @ReactMethod decorator
@ReactMethod(isBlockingSynchronousMethod = true)
fun getAppBuildNumber() = moduleImpl.getAppBuildNumber()
@ReactMethod(isBlockingSynchronousMethod = true)
fun getAppBundleId() = moduleImpl.getAppBundleId()
@ReactMethod(isBlockingSynchronousMethod = true)
fun getAppVersion() = moduleImpl.getAppVersion()
companion object {
const val NAME = AppInfoModuleImpl.NAME
}
}
Let's finalize it by exporting the module in the TurboReactPackage
instance.
AppInfoTurboPackage.kt
The last thing we need to do is to export AppInfoModule
in the TurboReactPackage
instance. Let's go to AppInfoTurboPackage.kt
and add our new module.
package com.appinfopackage
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 AppInfoTurboPackage : TurboReactPackage() {
/**
* Initialize and export modules based on the name of the required module
*/
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return when (name) {
AppInfoModule.NAME -> AppInfoModule(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(
AppInfoModule::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 AppInfoTurboPackage
, 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.
AppInfoModuleImpl.java
Let's start by creating a small pure Java class that will directly retrieve all application informations:
package com.appinfopackage;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import com.facebook.react.bridge.ReactApplicationContext;
/**
* Native module's shared implementation
*/
public class AppInfoModuleImpl {
private final ReactApplicationContext reactContext;
public static final String NAME = "AppInfoModule";
public AppInfoModuleImpl(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}
public String getAppBuildNumber() {
String buildNumber = "unknown";
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
buildNumber = Long.toString(getPackageInfo().getLongVersionCode());
} else {
buildNumber = Long.toString(getPackageInfo().versionCode);
}
} catch (Exception ignored) {}
return buildNumber;
}
public String getAppBundleId() {
return reactContext.getPackageName();
}
public String getAppVersion() {
String appVersion = "unknown";
try {
appVersion = getPackageInfo().versionName;
} catch (Exception ignored) {}
return appVersion;
}
private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return reactContext
.getPackageManager()
.getPackageInfo(
reactContext.getPackageName(),
PackageManager.PackageInfoFlags.of(0L)
);
} else {
return reactContext
.getPackageManager()
.getPackageInfo(reactContext.getPackageName(), 0);
}
}
}
AppInfoModuleImpl
declares 3 methods, each of them returns a string value. It also declares static NAME
constant which will be used in RN's module wrapper.
To retrieve bundle id, we will use Context#getPackageName method. For other app informations we will be calling PackageManager#getPackageInfo (which can be obtained with Context#getPackageManager).
AppInfoModule.java
Now, let's move to the module that will manage function calls from the JS world:
package com.appinfopackage;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
/**
* 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 = AppInfoModule.NAME)
public class AppInfoModule extends NativeAppInfoModuleSpec {
public static final String NAME = AppInfoModuleImpl.NAME;
// Use shared module implementation and forward react application context
private final AppInfoModuleImpl moduleImpl;
public AppInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
this.moduleImpl = new AppInfoModuleImpl(reactContext);
}
// Return the name of the module - it should match the name provided in JS specification
@Override
public String getName() {
return NAME;
}
// Exported methods are overriden - based on the spec class
@Override
public String getAppBuildNumber() {
return moduleImpl.getAppBuildNumber();
}
@Override
public String getAppBundleId() {
return moduleImpl.getAppBundleId();
}
@Override
public String getAppVersion() {
return moduleImpl.getAppVersion();
}
}
Here we declare the AppInfoModule
class.
It extends codegenerated spec class and takes ReactApplicationContext
instance as constructor parameter.
Additionally, AppInfoModule
is annotated with ReactModule
decorator.
Static constant NAME
matches the value declared in JS specification.
To implement codegenerated spec class, we are using methods from AppInfoModuleImpl
class.
Old architecture module
android/src/oldarch/java/com/appinfopackage/AppInfoModule.java
at other text editor and paste following content:package com.appinfopackage;
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 Java class for old arch native module implementation
*
* Each native module extends ReactContextBaseJavaModule class
*
* Class should be annotated with @ReactModule decorator
*/
@ReactModule(name = AppInfoModule.NAME)
public class AppInfoModule extends ReactContextBaseJavaModule {
public static final String NAME = AppInfoModuleImpl.NAME;
// Use shared module implementation and forward react application context
private final AppInfoModuleImpl moduleImpl;
public AppInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
this.moduleImpl = new AppInfoModuleImpl(reactContext);
}
// Return the name of the module - it should match the name provided in JS specification
@Override
public String getName() {
return NAME;
}
// Exported methods must be annotated with @ReactMethod decorator
@ReactMethod(isBlockingSynchronousMethod = true)
public String getAppBuildNumber() {
return moduleImpl.getAppBuildNumber();
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String getAppBundleId() {
return moduleImpl.getAppBundleId();
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String getAppVersion() {
return moduleImpl.getAppVersion();
}
}
Let's finalize it by exporting the module in the TurboReactPackage
instance.
AppInfoTurboPackage.java
The last thing we need to do is to export AppInfoModule
in the TurboReactPackage
instance. Let's go to AppInfoTurboPackage.java
and add our new module.
package com.appinfopackage;
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 AppInfoTurboPackage 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(AppInfoModule.NAME)) {
return new AppInfoModule(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[] {
AppInfoModule.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 AppInfoTurboPackage
, 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 AppInfo module in action!