@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
package/FLOW.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# `@matiks/rn-app-state` — How it works
|
|
2
|
+
|
|
3
|
+
This document describes how the package exposes **app lifecycle state** (`active` | `background` | `killed`) to React Native via **Nitro Modules** (JSI), and how each platform derives that state.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Purpose in one paragraph
|
|
8
|
+
|
|
9
|
+
The library ships a React hook, `useNitroAppState`, that returns:
|
|
10
|
+
|
|
11
|
+
| Field | Meaning |
|
|
12
|
+
|--------|---------|
|
|
13
|
+
| `appState` | Reactive lifecycle: `'active'`, `'background'`, or briefly `'killed'` on cold start when inferring a force-killed previous session. |
|
|
14
|
+
| `wasKilledLastSession` | Boolean from native storage: whether the **previous** run ended without a “clean exit” (persisted flag). |
|
|
15
|
+
| `getStateOnNotification` | Synchronous snapshot of foreground vs background, intended for push/notification handlers. |
|
|
16
|
+
|
|
17
|
+
On **web**, there is no Nitro bridge; the hook falls back to React Native’s `AppState` API and never reports `killed`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. High-level architecture
|
|
22
|
+
|
|
23
|
+
Nitro connects JavaScript to **one native HybridObject** registered under the name `AppStateModule`. TypeScript describes the contract; **Nitrogen** generates C++/Swift/Kotlin glue; your implementations live in `ios/HybridAppState.swift` and `android/.../HybridAppState.kt`.
|
|
24
|
+
|
|
25
|
+
```mermaid
|
|
26
|
+
flowchart TB
|
|
27
|
+
subgraph JS["JavaScript / TypeScript"]
|
|
28
|
+
Hook["useNitroAppState"]
|
|
29
|
+
Spec["AppState.nitro.ts (interface)"]
|
|
30
|
+
Hook -->|"createHybridObject('AppStateModule')"| NitroJS["react-native-nitro-modules"]
|
|
31
|
+
Hook -.-> Spec
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
subgraph Nitro["Nitro runtime"]
|
|
35
|
+
JSI["JSI bridge"]
|
|
36
|
+
Cxx["Generated C++ HybridAppStateModuleSpec"]
|
|
37
|
+
NitroJS --> JSI --> Cxx
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
subgraph iOS["iOS"]
|
|
41
|
+
Swift["HybridAppState.swift"]
|
|
42
|
+
UIKit["UIKit: UIApplication + NotificationCenter"]
|
|
43
|
+
UD["UserDefaults (killed/clean flag)"]
|
|
44
|
+
Cxx <--> Swift
|
|
45
|
+
Swift --> UIKit
|
|
46
|
+
Swift --> UD
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
subgraph Android["Android"]
|
|
50
|
+
Kt["HybridAppState.kt"]
|
|
51
|
+
PLO["ProcessLifecycleOwner"]
|
|
52
|
+
Prefs["SharedPreferences"]
|
|
53
|
+
Cxx <--> Kt
|
|
54
|
+
Kt --> PLO
|
|
55
|
+
Kt --> Prefs
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Cxx --> Swift
|
|
59
|
+
Cxx --> Kt
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Registration:** `nitro.json` maps the HybridObject name to native classes:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
"autolinking": {
|
|
66
|
+
"AppStateModule": {
|
|
67
|
+
"swift": "HybridAppState",
|
|
68
|
+
"kotlin": "HybridAppState"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Generated code (e.g. `NitroAppStateAutolinking.swift`) constructs `HybridAppState()` when JS requests `AppStateModule`.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 3. Repository layout (mental model)
|
|
78
|
+
|
|
79
|
+
| Path | Role |
|
|
80
|
+
|------|------|
|
|
81
|
+
| `src/specs/AppState.nitro.ts` | Type-only Nitro spec: methods and event shapes consumed by TS and codegen. |
|
|
82
|
+
| `src/useNitroAppState/useNitroAppState.native.ts` | Hook for iOS/Android: Nitro module + listener + `killed` UX. |
|
|
83
|
+
| `src/useNitroAppState/useNitroAppState.web.ts` | Web: `AppState` from `react-native`; no `killed`. |
|
|
84
|
+
| `src/useNitroAppState/types.ts` | `AppStateType`, `NitroAppStateResult`. |
|
|
85
|
+
| `ios/HybridAppState.swift` | iOS implementation (UIKit + UserDefaults). |
|
|
86
|
+
| `android/.../HybridAppState.kt` | Android implementation (lifecycle + SharedPreferences). |
|
|
87
|
+
| `nitrogen/generated/**` | Generated bridges — **do not edit**; regenerate with `npm run specs` / `npx nitrogen`. |
|
|
88
|
+
| `nitro.json` | Nitro project config + autolinking map. |
|
|
89
|
+
|
|
90
|
+
Metro resolves **`.native.ts`** vs **`.web.ts`** when the bundler target is native vs web, so a single export path (`useNitroAppState`) picks the right implementation.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 4. TypeScript contract (`AppStateModule`)
|
|
95
|
+
|
|
96
|
+
The native module exposes:
|
|
97
|
+
|
|
98
|
+
- **`getCurrentState(): string`** — Current cached lifecycle string (`'active'` | `'background'`). Not `'killed'`; `killed` is JS-only for UX.
|
|
99
|
+
- **`wasKilledLastSession: boolean`** — Read once per process from persisted flags.
|
|
100
|
+
- **`addListener(cb)`** — Subscribe to `{ state: string }` events when native state changes.
|
|
101
|
+
- **`getStateOnNotification(): string`** — Snapshot at call time (see platform notes below).
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 5. Hook flow (native)
|
|
106
|
+
|
|
107
|
+
### 5.1 Obtaining the module
|
|
108
|
+
|
|
109
|
+
```mermaid
|
|
110
|
+
sequenceDiagram
|
|
111
|
+
participant React as React component
|
|
112
|
+
participant Hook as useNitroAppState
|
|
113
|
+
participant Nitro as NitroModules
|
|
114
|
+
participant Native as HybridAppState
|
|
115
|
+
|
|
116
|
+
React->>Hook: render
|
|
117
|
+
Hook->>Nitro: createHybridObject('AppStateModule')
|
|
118
|
+
Nitro->>Native: construct / wrap singleton
|
|
119
|
+
Hook->>Native: wasKilledLastSession, getCurrentState()
|
|
120
|
+
Hook->>Native: addListener(...)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The module is cached in a module-level `_module` and in a `useRef` so the same instance is reused.
|
|
124
|
+
|
|
125
|
+
### 5.2 Initial `appState` and the `'killed'` special case
|
|
126
|
+
|
|
127
|
+
Native **never** returns the string `'killed'` from `getCurrentState()`. The hook synthesizes `'killed'` **only** when:
|
|
128
|
+
|
|
129
|
+
1. `wasKilledLastSession === true`, and
|
|
130
|
+
2. It uses that as the **initial** `useState` value so the UI can show “resuming after kill” once.
|
|
131
|
+
|
|
132
|
+
Immediately after mount, if the initial state was `'killed'`, a `setTimeout(0)` runs `getCurrentState()` again and sets React state to the real native value (`'active'` or `'background'`). So `'killed'` is a **transient** first paint, not a streaming lifecycle state from the OS.
|
|
133
|
+
|
|
134
|
+
```mermaid
|
|
135
|
+
stateDiagram-v2
|
|
136
|
+
direction LR
|
|
137
|
+
[*] --> KilledUI: wasKilledLastSession && first paint
|
|
138
|
+
KilledUI --> ActiveOrBg: setTimeout → getCurrentState()
|
|
139
|
+
ActiveOrBg --> ActiveOrBg: addListener events
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 5.3 Ongoing updates
|
|
143
|
+
|
|
144
|
+
`addListener` receives `AppStateChangeEvent` with `state: 'active' | 'background'`. The effect unsubscribes on unmount via `listener.remove()`.
|
|
145
|
+
|
|
146
|
+
### 5.4 `getStateOnNotification`
|
|
147
|
+
|
|
148
|
+
Exposed as a **stable** `useCallback` (depends only on `mod`). It calls native `getStateOnNotification()` so push handlers get a fresh snapshot rather than a stale React closure.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 6. iOS (`HybridAppState.swift`)
|
|
153
|
+
|
|
154
|
+
### 6.1 Live state: UIKit
|
|
155
|
+
|
|
156
|
+
- **Initial:** `readApplicationState()` reads `UIApplication.shared.applicationState` on the **main thread** and maps `.active` → `"active"`, `.background` / `.inactive` → `"background"`.
|
|
157
|
+
- **Updates:** `NotificationCenter` observers call `updateState` on the main thread:
|
|
158
|
+
- `didBecomeActive` → `"active"`
|
|
159
|
+
- `willResignActive` → `"background"` (covers control center, incoming call overlay, etc.)
|
|
160
|
+
- `didEnterBackground` → `"background"` and `markCleanExit()`
|
|
161
|
+
- `willTerminate` → `markCleanExit()`
|
|
162
|
+
|
|
163
|
+
`currentState` is only mutated on the main thread. `getCurrentState()` uses `Thread.isMainThread` / `DispatchQueue.main.sync` so callers from arbitrary Nitro threads still read consistent main-thread state.
|
|
164
|
+
|
|
165
|
+
### 6.2 `wasKilledLastSession`: UserDefaults
|
|
166
|
+
|
|
167
|
+
- Key stores whether the last run **exited cleanly** (background or terminate handler set the flag).
|
|
168
|
+
- On launch: if the app had run before and the flag says **not** clean, `wasKilledLastSession` is `true`.
|
|
169
|
+
- Then the flag is reset to “not clean” until the next clean exit.
|
|
170
|
+
|
|
171
|
+
### 6.3 `getStateOnNotification` on iOS
|
|
172
|
+
|
|
173
|
+
Calls `readApplicationState()` on the main thread — i.e. **live `UIApplication` state** at invocation time, not only the cached `currentState`.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 7. Android (`HybridAppState.kt`)
|
|
178
|
+
|
|
179
|
+
### 7.1 Live state: `ProcessLifecycleOwner`
|
|
180
|
+
|
|
181
|
+
- Observer on `onStart` → `"active"`, `onStop` → `"background"` (and `markCleanExit()` on stop).
|
|
182
|
+
- `currentState` is `@Volatile` for safe reads from any thread; `getCurrentState()` returns it directly.
|
|
183
|
+
|
|
184
|
+
### 7.2 `wasKilledLastSession`: SharedPreferences
|
|
185
|
+
|
|
186
|
+
Same **clean-exit** idea as iOS: `hasLaunchedBefore` + `didExitCleanly` in app prefs.
|
|
187
|
+
|
|
188
|
+
### 7.3 `getStateOnNotification` on Android
|
|
189
|
+
|
|
190
|
+
Returns the module’s **`currentState`** (updated by process lifecycle), not a separate system API.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 8. Web (`useNitroAppState.web.ts`)
|
|
195
|
+
|
|
196
|
+
- Uses `AppState` from `react-native` (e.g. visibility-based behavior under `react-native-web`).
|
|
197
|
+
- Maps everything except `'active'` to `'background'`.
|
|
198
|
+
- `wasKilledLastSession` is always `false`; `'killed'` is never used.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 9. Codegen and native load
|
|
203
|
+
|
|
204
|
+
- **`nitro.json`** sets namespaces and links `AppStateModule` → `HybridAppState`.
|
|
205
|
+
- **`npm run specs`** runs Nitrogen (`npx nitrogen@0.33.9`) to refresh `nitrogen/generated/**` when the spec or config changes.
|
|
206
|
+
- Android loads the C++ library in generated `NitroAppStateOnLoad` (`System.loadLibrary("NitroAppState")`); iOS links via CocoaPods / generated autolinking.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 10. End-to-end: user backgrounds the app (native)
|
|
211
|
+
|
|
212
|
+
```mermaid
|
|
213
|
+
sequenceDiagram
|
|
214
|
+
participant OS as OS lifecycle
|
|
215
|
+
participant Native as HybridAppState
|
|
216
|
+
participant JSI as JSI / Nitro
|
|
217
|
+
participant JS as useNitroAppState
|
|
218
|
+
|
|
219
|
+
OS->>Native: e.g. willResignActive / onStop
|
|
220
|
+
Native->>Native: updateState("background")
|
|
221
|
+
Native->>JSI: listener({ state: "background" })
|
|
222
|
+
JSI->>JS: setAppState("background")
|
|
223
|
+
JS->>JS: React re-render
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 11. Summary table
|
|
229
|
+
|
|
230
|
+
| Layer | Responsibility |
|
|
231
|
+
|--------|----------------|
|
|
232
|
+
| `AppState.nitro.ts` | Contract for JS and codegen. |
|
|
233
|
+
| `useNitroAppState.native.ts` | Wire Nitro + React; synthesize brief `'killed'` UX; subscribe/unsubscribe. |
|
|
234
|
+
| `useNitroAppState.web.ts` | RN `AppState` only. |
|
|
235
|
+
| iOS `HybridAppState` | UIKit notifications + `UIApplication` state; UserDefaults for kill inference. |
|
|
236
|
+
| Android `HybridAppState` | `ProcessLifecycleOwner`; SharedPreferences for kill inference. |
|
|
237
|
+
| Nitrogen output | JSI/C++ bridges and autolinking factories — keep in sync with spec via Nitrogen. |
|
|
238
|
+
|
|
239
|
+
This should be enough to navigate the codebase, extend the spec safely, or debug lifecycle mismatches per platform.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "NitroAppState"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/matiks-com/rn-app-state.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = [
|
|
17
|
+
# Implementation (Swift)
|
|
18
|
+
"ios/**/*.{swift}",
|
|
19
|
+
# Autolinking/Registration (Objective-C++)
|
|
20
|
+
"ios/**/*.{m,mm}",
|
|
21
|
+
# Implementation (C++ objects)
|
|
22
|
+
"cpp/**/*.{hpp,cpp}",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
load 'nitrogen/generated/ios/NitroAppState+autolinking.rb'
|
|
26
|
+
add_nitrogen_files(s)
|
|
27
|
+
|
|
28
|
+
s.dependency 'React-jsi'
|
|
29
|
+
s.dependency 'React-callinvoker'
|
|
30
|
+
install_modules_dependencies(s)
|
|
31
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @matiks/rn-app-state
|
|
2
|
+
|
|
3
|
+
Nitro-powered native app state module for React Native. Provides a reactive hook that tracks app lifecycle state (`active`, `background`, `killed`) using native code via JSI -- zero bridge overhead.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Reactive hook** -- `useNitroAppState()` triggers re-renders on state changes
|
|
8
|
+
- **Native lifecycle observation** -- iOS (NotificationCenter) + Android (ProcessLifecycleOwner)
|
|
9
|
+
- **Kill detection** -- `wasKilledLastSession` flag detected on cold start via persisted clean-exit flag
|
|
10
|
+
- **Notification support** -- `getStateOnNotification()` returns the app state at the moment of a push/silent notification
|
|
11
|
+
- **Web fallback** -- Uses React Native's AppState API on web (no Nitro required)
|
|
12
|
+
- **Thread-safe** -- Both iOS and Android implementations handle concurrent access correctly
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
yarn add @matiks/rn-app-state
|
|
18
|
+
cd ios && pod install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useNitroAppState } from '@matiks/rn-app-state'
|
|
25
|
+
|
|
26
|
+
function MyComponent() {
|
|
27
|
+
const { appState, wasKilledLastSession, getStateOnNotification } = useNitroAppState()
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (appState === 'active') {
|
|
31
|
+
// Refresh data, resume animations, etc.
|
|
32
|
+
}
|
|
33
|
+
}, [appState])
|
|
34
|
+
|
|
35
|
+
return <Text>App State: {appState}</Text>
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
### `useNitroAppState()`
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
|
|
45
|
+
| Property | Type | Description |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `appState` | `'active' \| 'background' \| 'killed'` | Current app lifecycle state. Reactive. |
|
|
48
|
+
| `wasKilledLastSession` | `boolean` | Whether the app was force-killed in the previous session. |
|
|
49
|
+
| `getStateOnNotification` | `() => AppStateType` | Synchronous call to get native state at notification receipt time. |
|
|
50
|
+
|
|
51
|
+
### States
|
|
52
|
+
|
|
53
|
+
- **`active`** -- App is in the foreground and interactive
|
|
54
|
+
- **`background`** -- App is in the background (iOS `inactive` is merged into `background`)
|
|
55
|
+
- **`killed`** -- Previous session was force-terminated (reported briefly on cold start, then transitions to `active`)
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
- React Native 0.76+
|
|
60
|
+
- `react-native-nitro-modules` 0.33.x
|
|
61
|
+
- iOS 16.0+
|
|
62
|
+
- Android minSdk 23+
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Generate nitrogen bridge code
|
|
68
|
+
yarn specs
|
|
69
|
+
|
|
70
|
+
# Type check
|
|
71
|
+
yarn typecheck
|
|
72
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
project(NitroAppState)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set (PACKAGE_NAME NitroAppState)
|
|
5
|
+
set (CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set (CMAKE_CXX_STANDARD 20)
|
|
7
|
+
|
|
8
|
+
# Define C++ library and add all sources
|
|
9
|
+
add_library(${PACKAGE_NAME} SHARED
|
|
10
|
+
src/main/cpp/cpp-adapter.cpp
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# Add Nitrogen specs
|
|
14
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroAppState+autolinking.cmake)
|
|
15
|
+
|
|
16
|
+
# Set up local includes
|
|
17
|
+
include_directories(
|
|
18
|
+
"src/main/cpp"
|
|
19
|
+
"../cpp"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
find_library(LOG_LIB log)
|
|
23
|
+
|
|
24
|
+
# Link all libraries together
|
|
25
|
+
target_link_libraries(
|
|
26
|
+
${PACKAGE_NAME}
|
|
27
|
+
${LOG_LIB}
|
|
28
|
+
android
|
|
29
|
+
)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath "com.android.tools.build:gradle:8.13.2"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def reactNativeArchitectures() {
|
|
13
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
14
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def isNewArchitectureEnabled() {
|
|
18
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
apply plugin: "com.android.library"
|
|
22
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
23
|
+
apply from: '../nitrogen/generated/android/NitroAppState+autolinking.gradle'
|
|
24
|
+
apply from: "./fix-prefab.gradle"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def getExtOrDefault(name) {
|
|
28
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroAppState_" + name]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def getExtOrIntegerDefault(name) {
|
|
32
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroAppState_" + name]).toInteger()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
android {
|
|
36
|
+
namespace "com.margelo.nitro.appstate"
|
|
37
|
+
|
|
38
|
+
ndkVersion getExtOrDefault("ndkVersion")
|
|
39
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
40
|
+
|
|
41
|
+
defaultConfig {
|
|
42
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
43
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
44
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
45
|
+
|
|
46
|
+
externalNativeBuild {
|
|
47
|
+
cmake {
|
|
48
|
+
cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
|
|
49
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
50
|
+
abiFilters (*reactNativeArchitectures())
|
|
51
|
+
|
|
52
|
+
buildTypes {
|
|
53
|
+
debug {
|
|
54
|
+
cppFlags "-O1 -g"
|
|
55
|
+
}
|
|
56
|
+
release {
|
|
57
|
+
cppFlags "-O2"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
externalNativeBuild {
|
|
65
|
+
cmake {
|
|
66
|
+
path "CMakeLists.txt"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
packagingOptions {
|
|
71
|
+
excludes = [
|
|
72
|
+
"META-INF",
|
|
73
|
+
"META-INF/**",
|
|
74
|
+
"**/libc++_shared.so",
|
|
75
|
+
"**/libNitroModules.so",
|
|
76
|
+
"**/libfbjni.so",
|
|
77
|
+
"**/libjsi.so",
|
|
78
|
+
"**/libfolly_json.so",
|
|
79
|
+
"**/libfolly_runtime.so",
|
|
80
|
+
"**/libglog.so",
|
|
81
|
+
"**/libhermes.so",
|
|
82
|
+
"**/libhermes-executor-debug.so",
|
|
83
|
+
"**/libhermes_executor.so",
|
|
84
|
+
"**/libreactnative.so",
|
|
85
|
+
"**/libreactnativejni.so",
|
|
86
|
+
"**/libturbomodulejsijni.so",
|
|
87
|
+
"**/libreact_nativemodule_core.so",
|
|
88
|
+
"**/libjscexecutor.so"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
buildFeatures {
|
|
93
|
+
buildConfig true
|
|
94
|
+
prefab true
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
buildTypes {
|
|
98
|
+
release {
|
|
99
|
+
minifyEnabled false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
lintOptions {
|
|
104
|
+
disable "GradleCompatible"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
compileOptions {
|
|
108
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
109
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
sourceSets {
|
|
113
|
+
main {
|
|
114
|
+
if (isNewArchitectureEnabled()) {
|
|
115
|
+
java.srcDirs += [
|
|
116
|
+
// React Codegen files
|
|
117
|
+
"${project.buildDir}/generated/source/codegen/java"
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
repositories {
|
|
125
|
+
mavenCentral()
|
|
126
|
+
google()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
dependencies {
|
|
131
|
+
//noinspection GradleDynamicVersion
|
|
132
|
+
compileOnly "com.facebook.react:react-native:+"
|
|
133
|
+
|
|
134
|
+
// Add a dependency on NitroModules
|
|
135
|
+
compileOnly project(":react-native-nitro-modules")
|
|
136
|
+
|
|
137
|
+
// ProcessLifecycleOwner for app lifecycle observation
|
|
138
|
+
implementation "androidx.lifecycle:lifecycle-process:2.8.7"
|
|
139
|
+
implementation "androidx.lifecycle:lifecycle-common:2.8.7"
|
|
140
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
tasks.configureEach { task ->
|
|
2
|
+
// Make sure that we generate our prefab publication file only after having built the native library
|
|
3
|
+
// so that not a header publication file, but a full configuration publication will be generated, which
|
|
4
|
+
// will include the .so file
|
|
5
|
+
|
|
6
|
+
def prefabConfigurePattern = ~/^prefab(.+)ConfigurePackage$/
|
|
7
|
+
def matcher = task.name =~ prefabConfigurePattern
|
|
8
|
+
if (matcher.matches()) {
|
|
9
|
+
def variantName = matcher[0][1]
|
|
10
|
+
task.outputs.upToDateWhen { false }
|
|
11
|
+
task.dependsOn("externalNativeBuild${variantName}")
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
afterEvaluate {
|
|
16
|
+
def abis = reactNativeArchitectures()
|
|
17
|
+
rootProject.allprojects.each { proj ->
|
|
18
|
+
if (proj === rootProject) return
|
|
19
|
+
|
|
20
|
+
def dependsOnThisLib = proj.configurations.findAll { it.canBeResolved }.any { config ->
|
|
21
|
+
config.dependencies.any { dep ->
|
|
22
|
+
dep.group == project.group && dep.name == project.name
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!dependsOnThisLib && proj != project) return
|
|
26
|
+
|
|
27
|
+
if (!proj.plugins.hasPlugin('com.android.application') && !proj.plugins.hasPlugin('com.android.library')) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def variants = proj.android.hasProperty('applicationVariants') ? proj.android.applicationVariants : proj.android.libraryVariants
|
|
32
|
+
// Touch the prefab_config.json files to ensure that in ExternalNativeJsonGenerator.kt we will re-trigger the prefab CLI to
|
|
33
|
+
// generate a libnameConfig.cmake file that will contain our native library (.so).
|
|
34
|
+
// See this condition: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/ExternalNativeJsonGenerator.kt;l=207-219?q=createPrefabBuildSystemGlue
|
|
35
|
+
variants.all { variant ->
|
|
36
|
+
def variantName = variant.name
|
|
37
|
+
abis.each { abi ->
|
|
38
|
+
def searchDir = new File(proj.projectDir, ".cxx/${variantName}")
|
|
39
|
+
if (!searchDir.exists()) return
|
|
40
|
+
def matches = []
|
|
41
|
+
searchDir.eachDir { randomDir ->
|
|
42
|
+
def prefabFile = new File(randomDir, "${abi}/prefab_config.json")
|
|
43
|
+
if (prefabFile.exists()) matches << prefabFile
|
|
44
|
+
}
|
|
45
|
+
matches.each { prefabConfig ->
|
|
46
|
+
prefabConfig.setLastModified(System.currentTimeMillis())
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|