@matiks/rn-app-state 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FLOW.md +239 -0
- package/NitroAppState.podspec +31 -0
- package/README.md +72 -0
- package/android/CMakeLists.txt +29 -0
- package/android/build.gradle +140 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/appstate/HybridAppState.kt +188 -0
- package/android/src/main/java/com/margelo/nitro/appstate/NitroAppStatePackage.kt +18 -0
- package/ios/HybridAppState.swift +224 -0
- package/nitro.json +24 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroAppState+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroAppState+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroAppStateOnLoad.cpp +48 -0
- package/nitrogen/generated/android/NitroAppStateOnLoad.hpp +25 -0
- package/nitrogen/generated/android/c++/JAppStateChangeEvent.hpp +57 -0
- package/nitrogen/generated/android/c++/JAppStateListener.hpp +67 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_AppStateChangeEvent.hpp +78 -0
- package/nitrogen/generated/android/c++/JHybridAppStateModuleSpec.cpp +84 -0
- package/nitrogen/generated/android/c++/JHybridAppStateModuleSpec.hpp +68 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/AppStateChangeEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/AppStateListener.kt +42 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/Func_void.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/Func_void_AppStateChangeEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/HybridAppStateModuleSpec.kt +72 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/appstate/NitroAppStateOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroAppState+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroAppState-Swift-Cxx-Bridge.cpp +49 -0
- package/nitrogen/generated/ios/NitroAppState-Swift-Cxx-Bridge.hpp +112 -0
- package/nitrogen/generated/ios/NitroAppState-Swift-Cxx-Umbrella.hpp +51 -0
- package/nitrogen/generated/ios/NitroAppStateAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NitroAppStateAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridAppStateModuleSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridAppStateModuleSpecSwift.hpp +106 -0
- package/nitrogen/generated/ios/swift/AppStateChangeEvent.swift +29 -0
- package/nitrogen/generated/ios/swift/AppStateListener.swift +37 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_AppStateChangeEvent.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridAppStateModuleSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridAppStateModuleSpec_cxx.swift +172 -0
- package/nitrogen/generated/shared/c++/AppStateChangeEvent.hpp +83 -0
- package/nitrogen/generated/shared/c++/AppStateListener.hpp +83 -0
- package/nitrogen/generated/shared/c++/HybridAppStateModuleSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridAppStateModuleSpec.hpp +70 -0
- package/package.json +54 -0
- package/src/index.ts +2 -0
- package/src/specs/AppState.nitro.ts +57 -0
- package/src/useNitroAppState/index.ts +2 -0
- package/src/useNitroAppState/types.ts +21 -0
- package/src/useNitroAppState/useNitroAppState.native.ts +142 -0
- package/src/useNitroAppState/useNitroAppState.web.ts +56 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
package com.margelo.nitro.appstate
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.SharedPreferences
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import android.os.Looper
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import androidx.lifecycle.DefaultLifecycleObserver
|
|
9
|
+
import androidx.lifecycle.LifecycleOwner
|
|
10
|
+
import androidx.lifecycle.ProcessLifecycleOwner
|
|
11
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
12
|
+
import androidx.annotation.Keep
|
|
13
|
+
import com.margelo.nitro.NitroModules
|
|
14
|
+
import java.util.UUID
|
|
15
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Thread-safe native app state module for Android.
|
|
19
|
+
* Uses ProcessLifecycleOwner to observe app lifecycle and notifies JS listeners.
|
|
20
|
+
*
|
|
21
|
+
* States:
|
|
22
|
+
* - "active": App is in the foreground (resumed)
|
|
23
|
+
* - "background": App is in the background (stopped)
|
|
24
|
+
* - "killed": Only reported via `wasKilledLastSession` (not a live state)
|
|
25
|
+
*
|
|
26
|
+
* Thread safety:
|
|
27
|
+
* - Listeners stored in ConcurrentHashMap for thread-safe access.
|
|
28
|
+
* - State updates happen on the main thread (lifecycle callbacks are main-thread).
|
|
29
|
+
* - getCurrentState() is safe to call from any thread (reads a volatile field).
|
|
30
|
+
*/
|
|
31
|
+
@DoNotStrip
|
|
32
|
+
@Keep
|
|
33
|
+
class HybridAppState : HybridAppStateModuleSpec() {
|
|
34
|
+
|
|
35
|
+
companion object {
|
|
36
|
+
private const val TAG = "HybridAppState"
|
|
37
|
+
private const val PREFS_NAME = "com.matiks.appstate"
|
|
38
|
+
private const val KEY_DID_EXIT_CLEANLY = "didExitCleanly"
|
|
39
|
+
private const val KEY_HAS_LAUNCHED_BEFORE = "hasLaunchedBefore"
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get SharedPreferences using NitroModules.applicationContext.
|
|
43
|
+
* Falls back gracefully if context is not yet available.
|
|
44
|
+
*/
|
|
45
|
+
private fun getPrefs(): SharedPreferences? {
|
|
46
|
+
val ctx = NitroModules.applicationContext
|
|
47
|
+
if (ctx == null) {
|
|
48
|
+
Log.w(TAG, "NitroModules.applicationContext is null — SharedPreferences unavailable")
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
return ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// MARK: - State
|
|
56
|
+
|
|
57
|
+
/** Current app state. Volatile for cross-thread visibility. */
|
|
58
|
+
@Volatile
|
|
59
|
+
private var currentState: String = "active"
|
|
60
|
+
|
|
61
|
+
/** Whether the previous session was killed. */
|
|
62
|
+
override val wasKilledLastSession: Boolean
|
|
63
|
+
|
|
64
|
+
/** Registered JS listeners. ConcurrentHashMap for thread safety. */
|
|
65
|
+
private val listeners = ConcurrentHashMap<String, (event: AppStateChangeEvent) -> Unit>()
|
|
66
|
+
|
|
67
|
+
/** Main thread handler for dispatching lifecycle observer registration. */
|
|
68
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
69
|
+
|
|
70
|
+
/** Lifecycle observer instance (stored to allow removal on dispose). */
|
|
71
|
+
private val lifecycleObserver: DefaultLifecycleObserver
|
|
72
|
+
|
|
73
|
+
// MARK: - Init
|
|
74
|
+
|
|
75
|
+
init {
|
|
76
|
+
val prefs = getPrefs()
|
|
77
|
+
|
|
78
|
+
// Determine if the app was killed last session.
|
|
79
|
+
val hasLaunchedBefore = prefs?.getBoolean(KEY_HAS_LAUNCHED_BEFORE, false) ?: false
|
|
80
|
+
val didExitCleanly = prefs?.getBoolean(KEY_DID_EXIT_CLEANLY, false) ?: false
|
|
81
|
+
|
|
82
|
+
// First launch ever: no previous session, so not "killed".
|
|
83
|
+
// Subsequent launches: if the flag is false, the app was killed.
|
|
84
|
+
wasKilledLastSession = hasLaunchedBefore && !didExitCleanly
|
|
85
|
+
|
|
86
|
+
// Reset the flag. It will be set to true when we exit cleanly.
|
|
87
|
+
prefs?.edit()
|
|
88
|
+
?.putBoolean(KEY_HAS_LAUNCHED_BEFORE, true)
|
|
89
|
+
?.putBoolean(KEY_DID_EXIT_CLEANLY, false)
|
|
90
|
+
?.apply()
|
|
91
|
+
|
|
92
|
+
Log.d(TAG, "Initialized. wasKilledLastSession=$wasKilledLastSession")
|
|
93
|
+
|
|
94
|
+
// Create lifecycle observer
|
|
95
|
+
lifecycleObserver = object : DefaultLifecycleObserver {
|
|
96
|
+
override fun onStart(owner: LifecycleOwner) {
|
|
97
|
+
// App came to foreground
|
|
98
|
+
updateState("active")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override fun onStop(owner: LifecycleOwner) {
|
|
102
|
+
// App went to background - mark clean exit
|
|
103
|
+
markCleanExit()
|
|
104
|
+
updateState("background")
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Register lifecycle observer on the main thread.
|
|
109
|
+
// ProcessLifecycleOwner.get().lifecycle must be accessed on main thread.
|
|
110
|
+
val registerBlock = Runnable {
|
|
111
|
+
try {
|
|
112
|
+
ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver)
|
|
113
|
+
Log.d(TAG, "Lifecycle observer registered")
|
|
114
|
+
} catch (e: Exception) {
|
|
115
|
+
Log.e(TAG, "Failed to register lifecycle observer", e)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
120
|
+
registerBlock.run()
|
|
121
|
+
} else {
|
|
122
|
+
mainHandler.post(registerBlock)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// MARK: - HybridAppStateModuleSpec Methods
|
|
127
|
+
|
|
128
|
+
override fun getCurrentState(): String {
|
|
129
|
+
return currentState
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override fun addListener(onStateChange: (event: AppStateChangeEvent) -> Unit): AppStateListener {
|
|
133
|
+
val id = UUID.randomUUID().toString()
|
|
134
|
+
listeners[id] = onStateChange
|
|
135
|
+
|
|
136
|
+
val removeCallback: () -> Unit = {
|
|
137
|
+
listeners.remove(id)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return AppStateListener(removeCallback)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
override fun getStateOnNotification(): String {
|
|
144
|
+
// Return the current state at the moment a notification is received.
|
|
145
|
+
// ProcessLifecycleOwner accurately tracks foreground/background,
|
|
146
|
+
// so currentState reflects the real state even during notification handling.
|
|
147
|
+
return currentState
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// MARK: - Private Helpers
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Update the current state and notify all listeners.
|
|
154
|
+
* Called from lifecycle callbacks (always on main thread).
|
|
155
|
+
*/
|
|
156
|
+
private fun updateState(newState: String) {
|
|
157
|
+
if (newState == currentState) return
|
|
158
|
+
currentState = newState
|
|
159
|
+
|
|
160
|
+
Log.d(TAG, "State changed to: $newState")
|
|
161
|
+
|
|
162
|
+
val event = AppStateChangeEvent(state = newState)
|
|
163
|
+
|
|
164
|
+
// Snapshot and iterate. ConcurrentHashMap's values are safe to iterate
|
|
165
|
+
// even if a listener calls remove() during callback.
|
|
166
|
+
for ((_, listener) in listeners) {
|
|
167
|
+
try {
|
|
168
|
+
listener(event)
|
|
169
|
+
} catch (e: Exception) {
|
|
170
|
+
Log.e(TAG, "Error in app state listener callback", e)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Persist a flag indicating the app exited cleanly (not killed).
|
|
177
|
+
* Uses commit() (synchronous) to ensure it's written before process death.
|
|
178
|
+
*/
|
|
179
|
+
private fun markCleanExit() {
|
|
180
|
+
try {
|
|
181
|
+
getPrefs()?.edit()
|
|
182
|
+
?.putBoolean(KEY_DID_EXIT_CLEANLY, true)
|
|
183
|
+
?.commit()
|
|
184
|
+
} catch (e: Exception) {
|
|
185
|
+
Log.e(TAG, "Failed to mark clean exit", e)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package com.margelo.nitro.appstate
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.NativeModule
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
6
|
+
import com.facebook.react.BaseReactPackage
|
|
7
|
+
|
|
8
|
+
class NitroAppStatePackage : BaseReactPackage() {
|
|
9
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
|
|
10
|
+
|
|
11
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { HashMap() }
|
|
12
|
+
|
|
13
|
+
companion object {
|
|
14
|
+
init {
|
|
15
|
+
NitroAppStateOnLoad.initializeNative()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import NitroModules
|
|
3
|
+
|
|
4
|
+
/// Thread-safe native app state module for iOS.
|
|
5
|
+
/// Observes UIApplication lifecycle notifications and notifies JS listeners.
|
|
6
|
+
///
|
|
7
|
+
/// States:
|
|
8
|
+
/// - "active": App is in the foreground and interactive
|
|
9
|
+
/// - "background": App is in the background or inactive (iOS inactive is merged into background)
|
|
10
|
+
/// - "killed": Only reported via `wasKilledLastSession` (not a live state)
|
|
11
|
+
///
|
|
12
|
+
/// Thread safety:
|
|
13
|
+
/// - Nitro calls methods on arbitrary threads.
|
|
14
|
+
/// - UIApplication.shared.applicationState must be read on the main thread.
|
|
15
|
+
/// - Listener storage uses a serial queue for thread-safe access.
|
|
16
|
+
/// - Listener callbacks are dispatched on the main thread.
|
|
17
|
+
class HybridAppState: HybridAppStateModuleSpec {
|
|
18
|
+
|
|
19
|
+
// MARK: - Constants
|
|
20
|
+
|
|
21
|
+
private static let killedFlagKey = "com.matiks.appstate.didExitCleanly"
|
|
22
|
+
|
|
23
|
+
// MARK: - State
|
|
24
|
+
|
|
25
|
+
/// Serial queue protecting listener dictionary access.
|
|
26
|
+
private let listenerQueue = DispatchQueue(label: "com.matiks.appstate.listeners")
|
|
27
|
+
|
|
28
|
+
/// All registered JS listeners, keyed by UUID.
|
|
29
|
+
private var listeners: [UUID: (AppStateChangeEvent) -> Void] = [:]
|
|
30
|
+
|
|
31
|
+
/// The current state as tracked by this module. Only mutated on the main thread.
|
|
32
|
+
private var currentState: String = "active"
|
|
33
|
+
|
|
34
|
+
/// Whether the previous session was killed (force-quit / OOM / crash).
|
|
35
|
+
private(set) var wasKilledLastSession: Bool = false
|
|
36
|
+
|
|
37
|
+
// MARK: - Init
|
|
38
|
+
|
|
39
|
+
override init() {
|
|
40
|
+
// Compute the killed flag before super.init(), then assign after.
|
|
41
|
+
// The C++ wrapper is created lazily (not during init), so the
|
|
42
|
+
// property will have the correct value by the time JS accesses it.
|
|
43
|
+
let defaults = UserDefaults.standard
|
|
44
|
+
let hasLaunchedBefore = defaults.object(forKey: HybridAppState.killedFlagKey) != nil
|
|
45
|
+
let didExitCleanly = defaults.bool(forKey: HybridAppState.killedFlagKey)
|
|
46
|
+
|
|
47
|
+
// First launch ever: no previous session, so not "killed".
|
|
48
|
+
// Subsequent launches: if the flag is false, the app was killed.
|
|
49
|
+
let killed = hasLaunchedBefore && !didExitCleanly
|
|
50
|
+
|
|
51
|
+
super.init()
|
|
52
|
+
|
|
53
|
+
self.wasKilledLastSession = killed
|
|
54
|
+
|
|
55
|
+
// Reset the flag to false. It will be set to true when we exit cleanly
|
|
56
|
+
// (entering background or willTerminate).
|
|
57
|
+
defaults.set(false, forKey: HybridAppState.killedFlagKey)
|
|
58
|
+
defaults.synchronize()
|
|
59
|
+
|
|
60
|
+
// Determine initial state from UIApplication (must be on main thread).
|
|
61
|
+
if Thread.isMainThread {
|
|
62
|
+
self.currentState = self.readApplicationState()
|
|
63
|
+
} else {
|
|
64
|
+
DispatchQueue.main.sync {
|
|
65
|
+
self.currentState = self.readApplicationState()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Register for lifecycle notifications on the main thread.
|
|
70
|
+
let registerBlock = { [weak self] in
|
|
71
|
+
guard let self = self else { return }
|
|
72
|
+
let center = NotificationCenter.default
|
|
73
|
+
center.addObserver(
|
|
74
|
+
self,
|
|
75
|
+
selector: #selector(self.handleDidBecomeActive),
|
|
76
|
+
name: UIApplication.didBecomeActiveNotification,
|
|
77
|
+
object: nil
|
|
78
|
+
)
|
|
79
|
+
center.addObserver(
|
|
80
|
+
self,
|
|
81
|
+
selector: #selector(self.handleDidEnterBackground),
|
|
82
|
+
name: UIApplication.didEnterBackgroundNotification,
|
|
83
|
+
object: nil
|
|
84
|
+
)
|
|
85
|
+
center.addObserver(
|
|
86
|
+
self,
|
|
87
|
+
selector: #selector(self.handleWillResignActive),
|
|
88
|
+
name: UIApplication.willResignActiveNotification,
|
|
89
|
+
object: nil
|
|
90
|
+
)
|
|
91
|
+
center.addObserver(
|
|
92
|
+
self,
|
|
93
|
+
selector: #selector(self.handleWillTerminate),
|
|
94
|
+
name: UIApplication.willTerminateNotification,
|
|
95
|
+
object: nil
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if Thread.isMainThread {
|
|
100
|
+
registerBlock()
|
|
101
|
+
} else {
|
|
102
|
+
DispatchQueue.main.async(execute: registerBlock)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
deinit {
|
|
107
|
+
NotificationCenter.default.removeObserver(self)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// MARK: - HybridAppStateModuleSpec Methods
|
|
111
|
+
|
|
112
|
+
func getCurrentState() throws -> String {
|
|
113
|
+
if Thread.isMainThread {
|
|
114
|
+
return currentState
|
|
115
|
+
}
|
|
116
|
+
var state = ""
|
|
117
|
+
DispatchQueue.main.sync {
|
|
118
|
+
state = self.currentState
|
|
119
|
+
}
|
|
120
|
+
return state
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func addListener(onStateChange: @escaping (AppStateChangeEvent) -> Void) throws -> AppStateListener {
|
|
124
|
+
let id = UUID()
|
|
125
|
+
|
|
126
|
+
listenerQueue.sync {
|
|
127
|
+
listeners[id] = onStateChange
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let removeCallback: () -> Void = { [weak self] in
|
|
131
|
+
guard let self = self else { return }
|
|
132
|
+
self.listenerQueue.sync {
|
|
133
|
+
_ = self.listeners.removeValue(forKey: id)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return AppStateListener(remove: removeCallback)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func getStateOnNotification() throws -> String {
|
|
141
|
+
// Return the real UIApplication state at the moment of the call.
|
|
142
|
+
// This is useful when a push/silent notification wakes the app.
|
|
143
|
+
if Thread.isMainThread {
|
|
144
|
+
return readApplicationState()
|
|
145
|
+
}
|
|
146
|
+
var state = ""
|
|
147
|
+
DispatchQueue.main.sync {
|
|
148
|
+
state = self.readApplicationState()
|
|
149
|
+
}
|
|
150
|
+
return state
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// MARK: - Lifecycle Handlers (always called on main thread by NotificationCenter)
|
|
154
|
+
|
|
155
|
+
@objc private func handleDidBecomeActive() {
|
|
156
|
+
updateState("active")
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@objc private func handleDidEnterBackground() {
|
|
160
|
+
// Mark clean exit so next launch knows we weren't killed.
|
|
161
|
+
markCleanExit()
|
|
162
|
+
updateState("background")
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@objc private func handleWillResignActive() {
|
|
166
|
+
// iOS fires willResignActive before didEnterBackground,
|
|
167
|
+
// and also when opening notification center, control center, etc.
|
|
168
|
+
// We treat this as "background" since the user only wants active/background/killed.
|
|
169
|
+
updateState("background")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@objc private func handleWillTerminate() {
|
|
173
|
+
// App is about to be terminated by the system or user.
|
|
174
|
+
// This is a clean termination, so mark it.
|
|
175
|
+
markCleanExit()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// MARK: - Private Helpers
|
|
179
|
+
|
|
180
|
+
/// Update the current state and notify all listeners.
|
|
181
|
+
/// Must be called on the main thread.
|
|
182
|
+
private func updateState(_ newState: String) {
|
|
183
|
+
guard newState != currentState else { return }
|
|
184
|
+
currentState = newState
|
|
185
|
+
|
|
186
|
+
let event = AppStateChangeEvent(state: newState)
|
|
187
|
+
|
|
188
|
+
// Snapshot listeners under the lock, then call outside the lock
|
|
189
|
+
// to avoid deadlocks if a listener calls remove() during callback.
|
|
190
|
+
var currentListeners: [(AppStateChangeEvent) -> Void] = []
|
|
191
|
+
listenerQueue.sync {
|
|
192
|
+
currentListeners = Array(self.listeners.values)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for listener in currentListeners {
|
|
196
|
+
listener(event)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// Read UIApplication.shared.applicationState and map to our state strings.
|
|
201
|
+
/// MUST be called on the main thread.
|
|
202
|
+
private func readApplicationState() -> String {
|
|
203
|
+
let appState = UIApplication.shared.applicationState
|
|
204
|
+
switch appState {
|
|
205
|
+
case .active:
|
|
206
|
+
return "active"
|
|
207
|
+
case .background:
|
|
208
|
+
return "background"
|
|
209
|
+
case .inactive:
|
|
210
|
+
// iOS "inactive" is transitional (e.g., notification center open).
|
|
211
|
+
// Map to "background" since user only wants active/background/killed.
|
|
212
|
+
return "background"
|
|
213
|
+
@unknown default:
|
|
214
|
+
return "active"
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// Persist a flag indicating the app exited cleanly (not killed).
|
|
219
|
+
private func markCleanExit() {
|
|
220
|
+
let defaults = UserDefaults.standard
|
|
221
|
+
defaults.set(true, forKey: HybridAppState.killedFlagKey)
|
|
222
|
+
defaults.synchronize()
|
|
223
|
+
}
|
|
224
|
+
}
|
package/nitro.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://nitro.margelo.com/nitro.schema.json",
|
|
3
|
+
"cxxNamespace": [
|
|
4
|
+
"appstate"
|
|
5
|
+
],
|
|
6
|
+
"ios": {
|
|
7
|
+
"iosModuleName": "NitroAppState"
|
|
8
|
+
},
|
|
9
|
+
"android": {
|
|
10
|
+
"androidNamespace": [
|
|
11
|
+
"appstate"
|
|
12
|
+
],
|
|
13
|
+
"androidCxxLibName": "NitroAppState"
|
|
14
|
+
},
|
|
15
|
+
"autolinking": {
|
|
16
|
+
"AppStateModule": {
|
|
17
|
+
"swift": "HybridAppState",
|
|
18
|
+
"kotlin": "HybridAppState"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"ignorePaths": [
|
|
22
|
+
"**/node_modules"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
** linguist-generated=true
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#
|
|
2
|
+
# NitroAppState+autolinking.cmake
|
|
3
|
+
# This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
# https://github.com/mrousavy/nitro
|
|
5
|
+
# Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
# This is a CMake file that adds all files generated by Nitrogen
|
|
9
|
+
# to the current CMake project.
|
|
10
|
+
#
|
|
11
|
+
# To use it, add this to your CMakeLists.txt:
|
|
12
|
+
# ```cmake
|
|
13
|
+
# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroAppState+autolinking.cmake)
|
|
14
|
+
# ```
|
|
15
|
+
|
|
16
|
+
# Define a flag to check if we are building properly
|
|
17
|
+
add_definitions(-DBUILDING_NITROAPPSTATE_WITH_GENERATED_CMAKE_PROJECT)
|
|
18
|
+
|
|
19
|
+
# Enable Raw Props parsing in react-native (for Nitro Views)
|
|
20
|
+
add_definitions(-DRN_SERIALIZABLE_STATE)
|
|
21
|
+
|
|
22
|
+
# Add all headers that were generated by Nitrogen
|
|
23
|
+
include_directories(
|
|
24
|
+
"../nitrogen/generated/shared/c++"
|
|
25
|
+
"../nitrogen/generated/android/c++"
|
|
26
|
+
"../nitrogen/generated/android/"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Add all .cpp sources that were generated by Nitrogen
|
|
30
|
+
target_sources(
|
|
31
|
+
# CMake project name (Android C++ library name)
|
|
32
|
+
NitroAppState PRIVATE
|
|
33
|
+
# Autolinking Setup
|
|
34
|
+
../nitrogen/generated/android/NitroAppStateOnLoad.cpp
|
|
35
|
+
# Shared Nitrogen C++ sources
|
|
36
|
+
../nitrogen/generated/shared/c++/HybridAppStateModuleSpec.cpp
|
|
37
|
+
# Android-specific Nitrogen C++ sources
|
|
38
|
+
../nitrogen/generated/android/c++/JHybridAppStateModuleSpec.cpp
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake
|
|
42
|
+
# Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake
|
|
43
|
+
target_compile_definitions(
|
|
44
|
+
NitroAppState PRIVATE
|
|
45
|
+
-DFOLLY_NO_CONFIG=1
|
|
46
|
+
-DFOLLY_HAVE_CLOCK_GETTIME=1
|
|
47
|
+
-DFOLLY_USE_LIBCPP=1
|
|
48
|
+
-DFOLLY_CFG_NO_COROUTINES=1
|
|
49
|
+
-DFOLLY_MOBILE=1
|
|
50
|
+
-DFOLLY_HAVE_RECVMMSG=1
|
|
51
|
+
-DFOLLY_HAVE_PTHREAD=1
|
|
52
|
+
# Once we target android-23 above, we can comment
|
|
53
|
+
# the following line. NDK uses GNU style stderror_r() after API 23.
|
|
54
|
+
-DFOLLY_HAVE_XSI_STRERROR_R=1
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Add all libraries required by the generated specs
|
|
58
|
+
find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++
|
|
59
|
+
find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule)
|
|
60
|
+
find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library
|
|
61
|
+
|
|
62
|
+
# Link all libraries together
|
|
63
|
+
target_link_libraries(
|
|
64
|
+
NitroAppState
|
|
65
|
+
fbjni::fbjni # <-- Facebook C++ JNI helpers
|
|
66
|
+
ReactAndroid::jsi # <-- RN: JSI
|
|
67
|
+
react-native-nitro-modules::NitroModules # <-- NitroModules Core :)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Link react-native (different prefab between RN 0.75 and RN 0.76)
|
|
71
|
+
if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
|
|
72
|
+
target_link_libraries(
|
|
73
|
+
NitroAppState
|
|
74
|
+
ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab
|
|
75
|
+
)
|
|
76
|
+
else()
|
|
77
|
+
target_link_libraries(
|
|
78
|
+
NitroAppState
|
|
79
|
+
ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core
|
|
80
|
+
)
|
|
81
|
+
endif()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// NitroAppState+autolinking.gradle
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
/// This is a Gradle file that adds all files generated by Nitrogen
|
|
9
|
+
/// to the current Gradle project.
|
|
10
|
+
///
|
|
11
|
+
/// To use it, add this to your build.gradle:
|
|
12
|
+
/// ```gradle
|
|
13
|
+
/// apply from: '../nitrogen/generated/android/NitroAppState+autolinking.gradle'
|
|
14
|
+
/// ```
|
|
15
|
+
|
|
16
|
+
logger.warn("[NitroModules] 🔥 NitroAppState is boosted by nitro!")
|
|
17
|
+
|
|
18
|
+
android {
|
|
19
|
+
sourceSets {
|
|
20
|
+
main {
|
|
21
|
+
java.srcDirs += [
|
|
22
|
+
// Nitrogen files
|
|
23
|
+
"${project.projectDir}/../nitrogen/generated/android/kotlin"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// NitroAppStateOnLoad.cpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#ifndef BUILDING_NITROAPPSTATE_WITH_GENERATED_CMAKE_PROJECT
|
|
9
|
+
#error NitroAppStateOnLoad.cpp is not being built with the autogenerated CMakeLists.txt project. Is a different CMakeLists.txt building this?
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
#include "NitroAppStateOnLoad.hpp"
|
|
13
|
+
|
|
14
|
+
#include <jni.h>
|
|
15
|
+
#include <fbjni/fbjni.h>
|
|
16
|
+
#include <NitroModules/HybridObjectRegistry.hpp>
|
|
17
|
+
|
|
18
|
+
#include "JHybridAppStateModuleSpec.hpp"
|
|
19
|
+
#include "JFunc_void.hpp"
|
|
20
|
+
#include "JFunc_void_AppStateChangeEvent.hpp"
|
|
21
|
+
#include <NitroModules/DefaultConstructableObject.hpp>
|
|
22
|
+
|
|
23
|
+
namespace margelo::nitro::appstate {
|
|
24
|
+
|
|
25
|
+
int initialize(JavaVM* vm) {
|
|
26
|
+
using namespace margelo::nitro;
|
|
27
|
+
using namespace margelo::nitro::appstate;
|
|
28
|
+
using namespace facebook;
|
|
29
|
+
|
|
30
|
+
return facebook::jni::initialize(vm, [] {
|
|
31
|
+
// Register native JNI methods
|
|
32
|
+
margelo::nitro::appstate::JHybridAppStateModuleSpec::registerNatives();
|
|
33
|
+
margelo::nitro::appstate::JFunc_void_cxx::registerNatives();
|
|
34
|
+
margelo::nitro::appstate::JFunc_void_AppStateChangeEvent_cxx::registerNatives();
|
|
35
|
+
|
|
36
|
+
// Register Nitro Hybrid Objects
|
|
37
|
+
HybridObjectRegistry::registerHybridObjectConstructor(
|
|
38
|
+
"AppStateModule",
|
|
39
|
+
[]() -> std::shared_ptr<HybridObject> {
|
|
40
|
+
static DefaultConstructableObject<JHybridAppStateModuleSpec::javaobject> object("com/margelo/nitro/appstate/HybridAppState");
|
|
41
|
+
auto instance = object.create();
|
|
42
|
+
return instance->cthis()->shared();
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
} // namespace margelo::nitro::appstate
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// NitroAppStateOnLoad.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#include <jni.h>
|
|
9
|
+
#include <NitroModules/NitroDefines.hpp>
|
|
10
|
+
|
|
11
|
+
namespace margelo::nitro::appstate {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initializes the native (C++) part of NitroAppState, and autolinks all Hybrid Objects.
|
|
15
|
+
* Call this in your `JNI_OnLoad` function (probably inside `cpp-adapter.cpp`).
|
|
16
|
+
* Example:
|
|
17
|
+
* ```cpp (cpp-adapter.cpp)
|
|
18
|
+
* JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
|
|
19
|
+
* return margelo::nitro::appstate::initialize(vm);
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
int initialize(JavaVM* vm);
|
|
24
|
+
|
|
25
|
+
} // namespace margelo::nitro::appstate
|