Skip to main content

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 files
  • AppInfoModuleImpl class stub file
  • AppInfoTurboPackage class stub file

Let's start implementing!

AppInfoModuleImpl.kt

Let's start by creating a small pure Kotlin class that will directly retrieve all application informations:

android/src/main/java/com/appinfopackage/AppInfoModuleImpl.kt
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:

android/src/newarch/java/com/appinfopackage/AppInfoModule.kt
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
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/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.

android/src/main/java/com/appinfopackage/AppInfoTurboPackage.kt
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.

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!