@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,142 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useCallback } from 'react'
|
|
2
|
+
import { NitroModules } from 'react-native-nitro-modules'
|
|
3
|
+
import type {
|
|
4
|
+
AppStateModule,
|
|
5
|
+
AppStateChangeEvent,
|
|
6
|
+
} from '../specs/AppState.nitro'
|
|
7
|
+
import type { AppStateType, NitroAppStateResult } from './types'
|
|
8
|
+
|
|
9
|
+
// Lazily create the HybridObject singleton.
|
|
10
|
+
// This is safe because on native, NitroModules.createHybridObject is synchronous.
|
|
11
|
+
let _module: AppStateModule | null = null
|
|
12
|
+
|
|
13
|
+
function getModule(): AppStateModule {
|
|
14
|
+
if (_module == null) {
|
|
15
|
+
_module = NitroModules.createHybridObject<AppStateModule>('AppStateModule')
|
|
16
|
+
}
|
|
17
|
+
return _module
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A reactive React hook that provides the current app lifecycle state
|
|
22
|
+
* using native code via Nitro Modules (JSI).
|
|
23
|
+
*
|
|
24
|
+
* States: 'active' | 'background' | 'killed'
|
|
25
|
+
*
|
|
26
|
+
* - 'active' and 'background' are live states that update reactively.
|
|
27
|
+
* - 'killed' is only the initial state on cold start if the previous
|
|
28
|
+
* session was force-killed (it transitions to 'active' immediately).
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* const { appState, wasKilledLastSession } = useNitroAppState()
|
|
33
|
+
*
|
|
34
|
+
* useEffect(() => {
|
|
35
|
+
* if (appState === 'active') {
|
|
36
|
+
* // Refresh data, resume animations, etc.
|
|
37
|
+
* }
|
|
38
|
+
* }, [appState])
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function useNitroAppState(): NitroAppStateResult {
|
|
42
|
+
const moduleRef = useRef<AppStateModule | null>(null)
|
|
43
|
+
|
|
44
|
+
// Initialize module lazily
|
|
45
|
+
if (moduleRef.current == null) {
|
|
46
|
+
moduleRef.current = getModule()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const mod = moduleRef.current
|
|
50
|
+
|
|
51
|
+
// Determine initial state
|
|
52
|
+
const getInitialState = (): AppStateType => {
|
|
53
|
+
try {
|
|
54
|
+
if (mod.wasKilledLastSession) {
|
|
55
|
+
return 'killed'
|
|
56
|
+
}
|
|
57
|
+
return mod.getCurrentState() as AppStateType
|
|
58
|
+
} catch {
|
|
59
|
+
return 'active'
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const [appState, setAppState] = useState<AppStateType>(getInitialState)
|
|
64
|
+
const [wasKilledLastSession] = useState<boolean>(() => {
|
|
65
|
+
try {
|
|
66
|
+
return mod.wasKilledLastSession
|
|
67
|
+
} catch {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Ref to track the latest appState for use in stable callbacks.
|
|
73
|
+
const appStateRef = useRef<AppStateType>(appState)
|
|
74
|
+
appStateRef.current = appState
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
let listener: { remove: () => void } | null = null
|
|
78
|
+
let isMounted = true
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
listener = mod.addListener((event: AppStateChangeEvent) => {
|
|
82
|
+
if (isMounted) {
|
|
83
|
+
const newState = event.state as AppStateType
|
|
84
|
+
setAppState(newState)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.warn('[useNitroAppState] Failed to add listener:', e)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// If initial state was 'killed', transition to the real native state
|
|
92
|
+
// after mount. We use setTimeout with a mounted guard to avoid
|
|
93
|
+
// updating state on an unmounted component.
|
|
94
|
+
if (appStateRef.current === 'killed') {
|
|
95
|
+
const timer = setTimeout(() => {
|
|
96
|
+
if (isMounted) {
|
|
97
|
+
try {
|
|
98
|
+
const currentNativeState = mod.getCurrentState() as AppStateType
|
|
99
|
+
setAppState(currentNativeState)
|
|
100
|
+
} catch {
|
|
101
|
+
setAppState('active')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}, 0)
|
|
105
|
+
|
|
106
|
+
return () => {
|
|
107
|
+
isMounted = false
|
|
108
|
+
clearTimeout(timer)
|
|
109
|
+
try {
|
|
110
|
+
listener?.remove()
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore cleanup errors
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return () => {
|
|
118
|
+
isMounted = false
|
|
119
|
+
try {
|
|
120
|
+
listener?.remove()
|
|
121
|
+
} catch {
|
|
122
|
+
// Ignore cleanup errors
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, [mod])
|
|
126
|
+
|
|
127
|
+
// Stable callback that doesn't change identity on every state transition.
|
|
128
|
+
// Uses the module's synchronous native call instead of stale JS state.
|
|
129
|
+
const getStateOnNotification = useCallback((): AppStateType => {
|
|
130
|
+
try {
|
|
131
|
+
return mod.getStateOnNotification() as AppStateType
|
|
132
|
+
} catch {
|
|
133
|
+
return appStateRef.current
|
|
134
|
+
}
|
|
135
|
+
}, [mod])
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
appState,
|
|
139
|
+
wasKilledLastSession,
|
|
140
|
+
getStateOnNotification,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { AppState } from 'react-native';
|
|
3
|
+
import type { AppStateType, NitroAppStateResult } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Web fallback for useNitroAppState.
|
|
7
|
+
*
|
|
8
|
+
* On web, NitroModules.createHybridObject() is not available.
|
|
9
|
+
* This fallback uses the standard React Native AppState API
|
|
10
|
+
* (which works on web via react-native-web's Page Visibility API).
|
|
11
|
+
*
|
|
12
|
+
* - 'killed' is never reported on web (no concept of app termination).
|
|
13
|
+
* - wasKilledLastSession is always false on web.
|
|
14
|
+
*/
|
|
15
|
+
export function useNitroAppState(): NitroAppStateResult {
|
|
16
|
+
const [appState, setAppState] = useState<AppStateType>(() => {
|
|
17
|
+
try {
|
|
18
|
+
const current = AppState.currentState;
|
|
19
|
+
if (current === 'active') return 'active';
|
|
20
|
+
return 'background';
|
|
21
|
+
} catch {
|
|
22
|
+
return 'active';
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const subscription = AppState.addEventListener('change', (nextAppState) => {
|
|
28
|
+
if (nextAppState === 'active') {
|
|
29
|
+
setAppState('active');
|
|
30
|
+
} else {
|
|
31
|
+
// 'inactive', 'background', 'unknown', 'extension' all map to 'background'
|
|
32
|
+
setAppState('background');
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return () => {
|
|
37
|
+
subscription.remove();
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const getStateOnNotification = useCallback((): AppStateType => {
|
|
42
|
+
try {
|
|
43
|
+
const current = AppState.currentState;
|
|
44
|
+
if (current === 'active') return 'active';
|
|
45
|
+
return 'background';
|
|
46
|
+
} catch {
|
|
47
|
+
return appState;
|
|
48
|
+
}
|
|
49
|
+
}, [appState]);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
appState,
|
|
53
|
+
wasKilledLastSession: false,
|
|
54
|
+
getStateOnNotification,
|
|
55
|
+
};
|
|
56
|
+
}
|