diff --git a/native/expo-modules/android-lifecycle/android/build.gradle b/native/expo-modules/android-lifecycle/android/build.gradle --- a/native/expo-modules/android-lifecycle/android/build.gradle +++ b/native/expo-modules/android-lifecycle/android/build.gradle @@ -88,4 +88,9 @@ dependencies { implementation project(':expo-modules-core') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" + + implementation "androidx.lifecycle:lifecycle-runtime:2.5.1" + implementation "androidx.lifecycle:lifecycle-process:2.5.1" + + implementation 'com.facebook.react:react-native:+' } diff --git a/native/expo-modules/android-lifecycle/android/src/main/java/app/comm/android/lifecycle/AndroidLifecycleModule.kt b/native/expo-modules/android-lifecycle/android/src/main/java/app/comm/android/lifecycle/AndroidLifecycleModule.kt --- a/native/expo-modules/android-lifecycle/android/src/main/java/app/comm/android/lifecycle/AndroidLifecycleModule.kt +++ b/native/expo-modules/android-lifecycle/android/src/main/java/app/comm/android/lifecycle/AndroidLifecycleModule.kt @@ -1,38 +1,74 @@ package app.comm.android.lifecycle +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent +import androidx.lifecycle.ProcessLifecycleOwner +import com.facebook.react.bridge.UiThreadUtil import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition +private const val moduleName = "AndroidLifecycle" +private const val eventName = "LIFECYCLE_CHANGE" +private const val statusKey = "status" + +private enum class LifecycleState(val jsName: String) { + ACTIVE("active"), + BACKGROUND("background"); + + companion object { + val constantsMap + get() = LifecycleState.values().associate { it.name to it.jsName } + } +} + class AndroidLifecycleModule : Module() { - // Each module class must implement the definition function. The definition consists of components - // that describes the module's functionality and behavior. - // See https://docs.expo.dev/modules/module-api for more details about available components. override fun definition() = ModuleDefinition { - // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument. - // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity. - // The module will be accessible from `requireNativeModule('AndroidLifecycle')` in JavaScript. - Name("AndroidLifecycle") - - // Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary. - Constants( - "PI" to Math.PI - ) - - // Defines event names that the module can send to JavaScript. - Events("onChange") - - // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread. - Function("hello") { - "Hello world! 👋" + Name(moduleName) + + Constants { + val currentState = + if (lifecycle.getCurrentState() == Lifecycle.State.RESUMED) + LifecycleState.ACTIVE + else + LifecycleState.BACKGROUND + return@Constants LifecycleState.constantsMap + + ("initialStatus" to currentState.jsName) + } + + Events(eventName) + + OnStartObserving { + UiThreadUtil.runOnUiThread { + lifecycle.addObserver(observer) + } + } + + OnStopObserving { + UiThreadUtil.runOnUiThread { + lifecycle.removeObserver(observer) + } + } + } + + private val lifecycle: Lifecycle + get() = ProcessLifecycleOwner.get().getLifecycle() + + private val observer = object : LifecycleObserver { + @OnLifecycleEvent(Lifecycle.Event.ON_START) + fun onStart() { + sendEvent( + eventName, + mapOf(statusKey to LifecycleState.ACTIVE.jsName) + ) } - // Defines a JavaScript function that always returns a Promise and whose native code - // is by default dispatched on the different thread than the JavaScript runtime runs on. - AsyncFunction("setValueAsync") { value: String -> - // Send an event to JavaScript. - sendEvent("onChange", mapOf( - "value" to value - )) + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun onStop() { + sendEvent( + eventName, + mapOf(statusKey to LifecycleState.BACKGROUND.jsName) + ) } } } diff --git a/native/lifecycle/lifecycle-module.js b/native/lifecycle/lifecycle-module.js --- a/native/lifecycle/lifecycle-module.js +++ b/native/lifecycle/lifecycle-module.js @@ -5,32 +5,42 @@ NativeModulesProxy, EventEmitter, } from 'expo-modules-core'; +import invariant from 'invariant'; +import { Platform } from 'react-native'; import type { EmitterSubscription } from '../types/react-native'; -const AndroidLifecycleModule: { - +PI: number, - +hello: () => string, - +setValueAsync: string => Promise, - ... -} = requireNativeModule('AndroidLifecycle'); - -export const PI = AndroidLifecycleModule.PI; +type Active = 'active'; +type Background = 'background'; +type LifecycleStatus = Active | Background; -export function hello(): string { - return AndroidLifecycleModule.hello(); -} - -export async function setValueAsync(value: string): Promise { - return await AndroidLifecycleModule.setValueAsync(value); +let AndroidLifecycleModule: ?{ + +ACTIVE: Active, + +BACKGROUND: Background, + +initialStatus: LifecycleStatus, + ... +}; +let emitter; +if (Platform.OS === 'android') { + AndroidLifecycleModule = requireNativeModule('AndroidLifecycle'); + emitter = new EventEmitter( + AndroidLifecycleModule ?? NativeModulesProxy.AndroidLifecycle, + ); } -const emitter = new EventEmitter( - AndroidLifecycleModule ?? NativeModulesProxy.AndroidLifecycle, -); +export const ACTIVE: ?Active = AndroidLifecycleModule?.ACTIVE; +export const BACKGROUND: ?Background = AndroidLifecycleModule?.BACKGROUND; +export const initialStatus: ?LifecycleStatus = + AndroidLifecycleModule?.initialStatus; -export function addChangeListener( - listener: ({ +value: string }) => mixed, +export function addAndroidLifecycleListener( + listener: (state: LifecycleStatus) => mixed, ): EmitterSubscription { - return emitter.addListener('onChange', listener); + invariant( + Platform.OS === 'android' && emitter, + 'Only Android should call addAndroidLifecycleListener', + ); + return emitter.addListener('LIFECYCLE_CHANGE', ({ status }) => + listener(status), + ); }