@apexnative/navigation 1.0.0-alpha.3 → 1.0.0-alpha.5

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.
Files changed (239) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/android/CLAUDE.md +26 -9
  3. package/android/CORE.md +32 -17
  4. package/android/RECIPES.md +9 -3
  5. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +25 -9
  6. package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
  7. package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
  8. package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
  9. package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
  10. package/android/src/main/java/dev/apexnative/navigation/options/AnimationsOptions.kt +2 -2
  11. package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
  12. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
  13. package/android/src/main/java/dev/apexnative/navigation/options/HardwareBackButtonOptions.kt +9 -1
  14. package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
  15. package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
  16. package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +6 -6
  18. package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +29 -36
  19. package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
  20. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +33 -22
  21. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
  22. package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
  23. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +39 -20
  24. package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -4
  25. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +6 -6
  26. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +10 -5
  27. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +47 -46
  28. package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
  29. package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
  30. package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -4
  31. package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
  32. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
  33. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
  34. package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
  35. package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
  36. package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
  37. package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
  38. package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
  39. package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +3 -2
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +25 -17
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +6 -2
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +2 -2
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +5 -5
  49. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackCheck.kt +50 -0
  50. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
  51. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -8
  52. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +8 -5
  53. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +8 -8
  54. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
  55. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +1 -1
  56. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
  57. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +61 -15
  58. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
  59. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +25 -12
  60. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +2 -1
  61. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +85 -29
  62. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +2 -2
  63. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
  64. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt +3 -5
  65. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +5 -8
  66. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
  67. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
  68. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +14 -11
  69. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +56 -21
  70. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
  71. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt +0 -5
  72. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
  73. package/cli/doctor/checks/app.js +61 -13
  74. package/cli/doctor/checks/splash.js +4 -22
  75. package/cli/migrate/native.js +18 -0
  76. package/cli/setup/source.js +2 -1
  77. package/cli/setup/steps/mainApplication.js +134 -19
  78. package/cli/splash/index.js +3 -1
  79. package/docs-md/architecture/transitions.md +6 -7
  80. package/docs-md/containers/modals.md +2 -2
  81. package/docs-md/guides/cli.md +1 -2
  82. package/docs-md/help/glossary.md +1 -1
  83. package/docs-md/help/troubleshooting.md +22 -5
  84. package/docs-md/index.json +4 -0
  85. package/docs-md/migration/from-react-native-navigation.md +8 -2
  86. package/docs-md/screen-ui/back-and-gestures.md +24 -2
  87. package/docs-md/start/installation.md +4 -2
  88. package/ios/CLAUDE.md +17 -5
  89. package/ios/CORE.md +18 -13
  90. package/ios/Contract/ANNCommandAnswer.h +9 -2
  91. package/ios/Contract/ANNCommandAnswer.mm +28 -30
  92. package/ios/Contract/ANNCommandContract.h +15 -0
  93. package/ios/Contract/ANNCommandContract.mm +78 -0
  94. package/ios/Core/ANNAppearanceDispatcher.h +5 -4
  95. package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
  96. package/ios/Core/ANNErrorHandler.h +1 -1
  97. package/ios/Core/ANNErrorHandler.mm +3 -1
  98. package/ios/Core/ANNEventEmitter.h +1 -1
  99. package/ios/Core/ANNEventEmitter.mm +2 -2
  100. package/ios/Core/ANNJavaScriptCalls.h +34 -0
  101. package/ios/Core/ANNJavaScriptCalls.mm +61 -0
  102. package/ios/Core/ANNNavigationState.h +7 -5
  103. package/ios/Core/ANNNavigationState.mm +8 -7
  104. package/ios/Core/ANNNodeHelper.h +32 -12
  105. package/ios/Core/ANNNodeHelper.mm +100 -3
  106. package/ios/Core/ANNRenderWait.h +15 -1
  107. package/ios/Core/ANNRenderWait.mm +119 -17
  108. package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
  109. package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
  110. package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
  111. package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
  112. package/ios/Layouts/Root/ANNSplashViewController.h +5 -3
  113. package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
  114. package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
  115. package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
  116. package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
  117. package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
  118. package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
  119. package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
  120. package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
  121. package/ios/Layouts/Stack/ANNPreviewPush.h +5 -5
  122. package/ios/Layouts/Stack/ANNPreviewPush.mm +85 -22
  123. package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
  124. package/ios/Layouts/Stack/ANNStackController.mm +26 -3
  125. package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
  126. package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
  127. package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
  128. package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
  129. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
  130. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
  131. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
  132. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
  133. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
  134. package/ios/Options/ANNOptionsCache.h +7 -6
  135. package/ios/Options/ANNSearchBarOptions.h +4 -3
  136. package/ios/Options/ANNUserChoice.h +57 -0
  137. package/ios/Options/ANNUserChoice.mm +44 -0
  138. package/ios/Options/Params/ANNImageSource.h +61 -0
  139. package/ios/Options/Params/ANNImageSource.mm +101 -0
  140. package/ios/Options/Params/ANNOptionImages.h +37 -0
  141. package/ios/Options/Params/ANNOptionImages.mm +284 -0
  142. package/ios/Options/Params/Image.h +11 -2
  143. package/ios/Options/Params/Image.mm +15 -2
  144. package/ios/Options/Params/ImageParser.h +5 -5
  145. package/ios/Options/Params/ImageParser.mm +28 -46
  146. package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
  147. package/ios/RECIPES.md +11 -5
  148. package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
  149. package/ios/React/Slots/ANNSlotContainers.h +14 -6
  150. package/ios/React/Slots/ANNSlotContainers.mm +75 -2
  151. package/ios/TopBar/ANNBarButtons.h +9 -5
  152. package/ios/TopBar/ANNBarButtons.mm +211 -63
  153. package/ios/TopBar/ANNIconButtonImage.h +1 -0
  154. package/ios/TopBar/ANNIconButtonImage.mm +34 -1
  155. package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
  156. package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
  157. package/ios/TopBar/ANNSearchPresenter.mm +170 -19
  158. package/ios/TopBar/ANNTopBarPresenter.h +5 -0
  159. package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
  160. package/ios/TurboModules/ANNTurboModule.mm +39 -21
  161. package/lib/module/ARCHITECTURE.md +18 -18
  162. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  163. package/lib/module/api/commands/commands.js +54 -6
  164. package/lib/module/api/commands/commands.js.map +1 -1
  165. package/lib/module/api/navigation.js.map +1 -1
  166. package/lib/module/engine/commands.js +9 -7
  167. package/lib/module/engine/commands.js.map +1 -1
  168. package/lib/module/engine/layout.js +13 -9
  169. package/lib/module/engine/layout.js.map +1 -1
  170. package/lib/module/engine/options.js +44 -11
  171. package/lib/module/engine/options.js.map +1 -1
  172. package/lib/module/engine/places.js +52 -7
  173. package/lib/module/engine/places.js.map +1 -1
  174. package/lib/module/engine/props.js +30 -1
  175. package/lib/module/engine/props.js.map +1 -1
  176. package/lib/module/engine/rememberedOptions.js +1 -1
  177. package/lib/module/engine/rememberedOptions.js.map +1 -1
  178. package/lib/module/engine/screens.js +22 -12
  179. package/lib/module/engine/screens.js.map +1 -1
  180. package/lib/module/interfaces/contract/commands.json +101 -0
  181. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
  182. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  183. package/lib/typescript/api/commands/commands.d.ts.map +1 -1
  184. package/lib/typescript/api/commands/presentation.d.ts +4 -3
  185. package/lib/typescript/api/commands/presentation.d.ts.map +1 -1
  186. package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
  187. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  188. package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
  189. package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
  190. package/lib/typescript/api/navigation.d.ts +4 -1
  191. package/lib/typescript/api/navigation.d.ts.map +1 -1
  192. package/lib/typescript/engine/commands.d.ts.map +1 -1
  193. package/lib/typescript/engine/options.d.ts +5 -2
  194. package/lib/typescript/engine/options.d.ts.map +1 -1
  195. package/lib/typescript/engine/places.d.ts +7 -2
  196. package/lib/typescript/engine/places.d.ts.map +1 -1
  197. package/lib/typescript/engine/props.d.ts +8 -1
  198. package/lib/typescript/engine/props.d.ts.map +1 -1
  199. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  200. package/lib/typescript/engine/screens.d.ts.map +1 -1
  201. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  202. package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
  203. package/lib/typescript/interfaces/Events.d.ts +19 -10
  204. package/lib/typescript/interfaces/Events.d.ts.map +1 -1
  205. package/lib/typescript/interfaces/Options.d.ts +1 -1
  206. package/lib/typescript/interfaces/Options.d.ts.map +1 -1
  207. package/lib/typescript/interfaces/options/animations.d.ts +23 -4
  208. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  209. package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
  210. package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
  211. package/lib/typescript/interfaces/options/stack.d.ts +9 -0
  212. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  213. package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
  214. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  215. package/package.json +1 -1
  216. package/src/ARCHITECTURE.md +18 -18
  217. package/src/adapters/NativeANNTurboModule.ts +5 -2
  218. package/src/api/commands/commands.ts +48 -7
  219. package/src/api/commands/presentation.ts +4 -3
  220. package/src/api/events/navigationEvents.ts +1 -1
  221. package/src/api/hooks/useNavigation.ts +1 -1
  222. package/src/api/navigation.ts +4 -1
  223. package/src/engine/commands.ts +19 -7
  224. package/src/engine/layout.ts +16 -9
  225. package/src/engine/options.ts +39 -11
  226. package/src/engine/places.ts +65 -7
  227. package/src/engine/props.ts +30 -1
  228. package/src/engine/rememberedOptions.ts +4 -1
  229. package/src/engine/screens.tsx +23 -13
  230. package/src/interfaces/ComponentEvents.ts +2 -2
  231. package/src/interfaces/Events.ts +19 -10
  232. package/src/interfaces/Options.ts +1 -0
  233. package/src/interfaces/contract/commands.json +101 -0
  234. package/src/interfaces/options/animations.ts +24 -4
  235. package/src/interfaces/options/buttons.ts +13 -5
  236. package/src/interfaces/options/stack.ts +9 -0
  237. package/src/interfaces/options/topBar.ts +22 -9
  238. package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationStateReporter.kt +0 -62
  239. package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
@@ -11,8 +11,10 @@ const {
11
11
  reactNativeVersion,
12
12
  usesExpo,
13
13
  } = require('../../app');
14
+ const { mainActivity } = require('../../links/native');
14
15
  const { findProject } = require('../../project');
15
16
  const { codeOf } = require('../../setup/source');
17
+ const { loadsReactNativeItself } = require('../../setup/steps/mainApplication');
16
18
  const { planCodegen } = require('../../codegen');
17
19
  const { readConfig } = require('../../config');
18
20
  const { planProject } = require('../../setup');
@@ -27,6 +29,16 @@ const { splashChecks } = require('./splash');
27
29
  const SETUP = { label: 'npx apex-navigation setup', run: /** @type {const} */ ('setup') };
28
30
  const GENERATED = ['ApexNavigation.generated.swift', 'ApexNavigationGenerated.kt'];
29
31
  const CODEGEN = { label: 'npx apex-navigation codegen', run: /** @type {const} */ ('codegen') };
32
+ // The changes the main activity must handle itself; the template's value has them all.
33
+ const CONFIG_CHANGES = [
34
+ 'orientation',
35
+ 'screenSize',
36
+ 'smallestScreenSize',
37
+ 'screenLayout',
38
+ 'uiMode',
39
+ ];
40
+ const TEMPLATE_CONFIG_CHANGES =
41
+ 'keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode';
30
42
 
31
43
  /**
32
44
  * React Native, the navigation, and the files setup sets up.
@@ -122,28 +134,64 @@ function appChecks({ root, run }) {
122
134
  });
123
135
  }
124
136
 
137
+ const configChanges = configChangesCheck(project.manifest);
138
+ if (configChanges) checks.push(configChanges);
139
+
125
140
  checks.push(...splashChecks(root, project), ...iconChecks(root, project));
126
141
 
127
- // A second load of React Native crashes the app on start: the most common slip of a setup by hand.
142
+ // NavigationApplication leaves the load of React Native to the template's call: without it the app closes on start,
143
+ // and so it does with a second load of the app's own next to it.
128
144
  const application = project.mainApplication && fs.readFileSync(project.mainApplication, 'utf8');
129
- if (
130
- application &&
131
- /:\s*NavigationApplication\(\)/.test(application) &&
132
- /\bloadReactNative\s*\(/.test(codeOf(application))
133
- ) {
134
- checks.push({
135
- group: 'app',
136
- title: 'React Native loads twice',
137
- status: 'error',
138
- detail: 'MainApplication.kt calls loadReactNative(this), NavigationApplication loads it too',
139
- fix: { label: 'Remove loadReactNative(this) from MainApplication.kt' },
140
- });
145
+ if (application && /:\s*NavigationApplication\(/.test(application)) {
146
+ const loads = /\bloadReactNative\s*\(/.test(codeOf(application));
147
+ if (!loads) {
148
+ checks.push({
149
+ group: 'app',
150
+ title: 'React Native not loaded',
151
+ status: 'error',
152
+ detail: "MainApplication.kt doesn't call loadReactNative(this) in onCreate",
153
+ fix: SETUP,
154
+ });
155
+ } else if (loadsReactNativeItself(application)) {
156
+ checks.push({
157
+ group: 'app',
158
+ title: 'React Native loads twice',
159
+ status: 'error',
160
+ detail:
161
+ 'MainApplication.kt loads React Native itself (SoLoader.init or the New Architecture entry point) next to loadReactNative(this)',
162
+ fix: SETUP,
163
+ });
164
+ }
141
165
  }
142
166
  const generated = generatedCheck(root, run);
143
167
  if (generated) checks.push(generated);
144
168
  return checks;
145
169
  }
146
170
 
171
+ /**
172
+ * Whether the main activity handles the changes of its window itself, as the React Native template has it. Without
173
+ * one of them Android recreates the activity on a rotation, a resize, a fold or a light/dark switch: every screen goes
174
+ * with it and the navigation starts again from its root. Nothing without a manifest or a main activity.
175
+ * @param {string | undefined} manifest
176
+ * @returns {Check | undefined}
177
+ */
178
+ function configChangesCheck(manifest) {
179
+ if (!manifest || !fs.existsSync(manifest)) return undefined;
180
+ const activity = mainActivity(fs.readFileSync(manifest, 'utf8'));
181
+ if (!activity) return undefined;
182
+ const value = /android:configChanges\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(activity.text);
183
+ const declared = (value?.[1] ?? value?.[2] ?? '').split('|').map((change) => change.trim());
184
+ const missing = CONFIG_CHANGES.filter((change) => !declared.includes(change));
185
+ if (missing.length === 0) return undefined;
186
+ return {
187
+ group: 'app',
188
+ title: 'Config changes',
189
+ status: 'warn',
190
+ detail: `the main activity doesn't handle ${missing.join(', ')}: Android recreates it, and every screen with it`,
191
+ fix: { label: `Set android:configChanges="${TEMPLATE_CONFIG_CHANGES}" on the main activity` },
192
+ };
193
+ }
194
+
147
195
  /**
148
196
  * One line for a few files: set up, or which need setup or a change by hand.
149
197
  * @param {string} title
@@ -17,34 +17,16 @@ const CIRCLE = 192;
17
17
  const LOGO_HEIGHT = /<dimen name="apex_navigation_splash_logo_height">([\d.]+)dp<\/dimen>/;
18
18
 
19
19
  /**
20
- * The native splash: made from its settings in package.json on both platforms, its logo whole on Android 12+; without
21
- * settings, a launch screen the navigation can't keep on screen until the first screen draws.
20
+ * The native splash: made from its settings in package.json on both platforms, its logo whole on Android 12+. Without
21
+ * settings there is nothing to check: the navigation keeps a copy of a storyboard and of `UILaunchScreen` alike.
22
22
  * @param {string} root
23
23
  * @param {Project} project
24
24
  * @returns {Check[]}
25
25
  */
26
26
  function splashChecks(root, project) {
27
- const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
28
27
  const saved = readConfig(root).splash;
29
- if (!saved) {
30
- // UILaunchScreen alone: iOS shows it, but the navigation keeps only a storyboard while the first screen renders.
31
- if (
32
- plist?.includes('<key>UILaunchScreen</key>') &&
33
- !plist.includes('<key>UILaunchStoryboardName</key>')
34
- ) {
35
- return [
36
- {
37
- group: 'app',
38
- title: 'Launch screen',
39
- status: 'warn',
40
- detail:
41
- 'UILaunchScreen in Info.plist: an empty window shows between it and the first screen',
42
- fix: SPLASH,
43
- },
44
- ];
45
- }
46
- return [];
47
- }
28
+ if (!saved) return [];
29
+ const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
48
30
  const resolved = resolveSettings(root, {}, saved);
49
31
  if ('errors' in resolved) {
50
32
  return [
@@ -3,6 +3,7 @@
3
3
 
4
4
  const fs = require('node:fs');
5
5
  const path = require('node:path');
6
+ const { withReactNativeLoaded } = require('../setup/steps/mainApplication');
6
7
 
7
8
  /** @typedef {import('../changes').FileChange} FileChange */
8
9
  /** @typedef {import('./report').ReportItem} ReportItem */
@@ -158,6 +159,23 @@ function planNative(project) {
158
159
  const before = fs.readFileSync(file, 'utf8');
159
160
  let next = before;
160
161
  for (const [pattern, replacement] of rewrite.rules) next = next.replace(pattern, replacement);
162
+ // react-native-navigation loaded React Native itself; the navigation's application leaves it to the template's
163
+ // loadReactNative(this), which such an app doesn't have.
164
+ if (file === project.mainApplication && /:\s*NavigationApplication\(/.test(next)) {
165
+ const loaded = withReactNativeLoaded(next);
166
+ if (loaded !== undefined) next = loaded;
167
+ else {
168
+ report.push({
169
+ file: path.relative(root, file),
170
+ line: 1,
171
+ step: 'native',
172
+ before: 'onCreate',
173
+ after: 'super.onCreate() then loadReactNative(this), and no load of its own',
174
+ what: 'React Native loads through loadReactNative(this) in onCreate: add it by hand',
175
+ docs: `${DOCS}#native`,
176
+ });
177
+ }
178
+ }
161
179
  if (next !== before) {
162
180
  changes.push({
163
181
  name: path.basename(file),
@@ -35,7 +35,8 @@ function removeUnusedImports(text, names) {
35
35
  for (const name of names) {
36
36
  const line = new RegExp(`^import\\s+${escape(name)}[ \\t]*\\r?\\n`, 'm');
37
37
  const simple = name.slice(name.lastIndexOf('.') + 1);
38
- const code = result.replace(/^import\s.*$/gm, '');
38
+ // A name in a comment is no use of the import.
39
+ const code = codeOf(result).replace(/^import\s.*$/gm, '');
39
40
  if (line.test(result) && !new RegExp(`\\b${escape(simple)}\\b`).test(code))
40
41
  result = result.replace(line, '');
41
42
  }
@@ -5,24 +5,51 @@ const { addImport, codeOf, removeUnusedImports } = require('../source');
5
5
 
6
6
  /** @typedef {import('./types').StepResult} StepResult */
7
7
 
8
- const HOW_TO = `Extend NavigationApplication. It loads React Native itself, so loadReactNative(this) goes:
9
- calling it again crashes the app on start.
8
+ const HOW_TO = `Extend NavigationApplication and keep the template's loadReactNative(this) in onCreate: it loads
9
+ React Native as the app's Gradle files say, and the navigation makes its React host after it.
10
10
 
11
+ import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
11
12
  import dev.apexnative.navigation.NavigationApplication
12
13
 
13
14
  class MainApplication : NavigationApplication() {
14
15
  override val reactHost: ReactHost by lazy { ... } // as it is
16
+
17
+ override fun onCreate() {
18
+ super.onCreate()
19
+ loadReactNative(this)
20
+ }
15
21
  }`;
16
22
 
23
+ const LOAD_IMPORT = 'import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative';
24
+
17
25
  // Imports of the template that the change leaves unused.
18
- const TEMPLATE_IMPORTS = [
19
- 'android.app.Application',
20
- 'com.facebook.react.ReactApplication',
21
- 'com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative',
26
+ const TEMPLATE_IMPORTS = ['android.app.Application', 'com.facebook.react.ReactApplication'];
27
+
28
+ // A load of React Native of the app's own, next to loadReactNative(this) a second one that crashes the app at start:
29
+ // the New Architecture's entry point (react-native-navigation's apps call it in onCreate) and SoLoader.
30
+ // A bare `load()` is the entry point's only where the file imports it. A form these don't take out (`load` with
31
+ // arguments, an `if` without braces) is left to the app's author.
32
+ const OWN_LOAD = [
33
+ /^[ \t]*if\s*\(\s*BuildConfig\.IS_NEW_ARCHITECTURE_ENABLED\s*\)\s*\{(?:\s*\/\/[^\n]*)*\s*DefaultNewArchitectureEntryPoint\.load\(\)\s*\}[ \t]*\r?\n/gm,
34
+ /^[ \t]*DefaultNewArchitectureEntryPoint\.load\(\)[ \t]*\r?\n/gm,
35
+ /^[ \t]*SoLoader\.init\([^\n]*\)[ \t]*\r?\n/gm,
36
+ ];
37
+ const BARE_LOAD = [
38
+ /^[ \t]*if\s*\(\s*BuildConfig\.IS_NEW_ARCHITECTURE_ENABLED\s*\)\s*\{(?:\s*\/\/[^\n]*)*\s*load\(\)\s*\}[ \t]*\r?\n/gm,
39
+ /^[ \t]*load\(\)[ \t]*\r?\n/gm,
40
+ ];
41
+ const BARE_LOAD_IMPORT =
42
+ /^import\s+com\.facebook\.react\.defaults\.DefaultNewArchitectureEntryPoint\.load\b/m;
43
+ const OWN_LOAD_IMPORTS = [
44
+ 'com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load',
45
+ 'com.facebook.react.defaults.DefaultNewArchitectureEntryPoint',
46
+ 'com.facebook.soloader.SoLoader',
47
+ 'com.facebook.react.soloader.OpenSourceMergedSoMapping',
22
48
  ];
23
49
 
24
50
  /**
25
- * MainApplication.kt: the application becomes a NavigationApplication.
51
+ * MainApplication.kt: the application becomes a NavigationApplication, and loads React Native in its onCreate as the
52
+ * template does.
26
53
  * @param {string} text
27
54
  * @returns {StepResult}
28
55
  */
@@ -32,21 +59,20 @@ function planMainApplication(text) {
32
59
  note: 'changed by hand, see below',
33
60
  howTo: HOW_TO,
34
61
  });
35
- if (/:\s*NavigationApplication\(\)/.test(text)) {
36
- return /\bloadReactNative\s*\(/.test(codeOf(text))
62
+ if (/:\s*NavigationApplication\(/.test(text)) {
63
+ if (loadsReactNative(text) && !loadsReactNativeItself(text)) {
64
+ return { status: 'done', note: 'already set up' };
65
+ }
66
+ const next = withReactNativeLoaded(text);
67
+ return next === undefined
37
68
  ? manual
38
- : { status: 'done', note: 'already set up' };
69
+ : { status: 'change', next, note: 'loadReactNative(this) in onCreate' };
39
70
  }
40
71
  const header = /class\s+(\w+)\s*:\s*Application\(\)\s*,\s*ReactApplication\b/;
41
72
  if (!header.test(text)) return manual;
42
- let next = text.replace(header, 'class $1 : NavigationApplication()');
43
- // The template's onCreate only loads React Native, so it goes; another one keeps the rest of its code.
44
- next = next.replace(
45
- /\n\n?[ \t]*override\s+fun\s+onCreate\(\)\s*\{\s*super\.onCreate\(\)\s*loadReactNative\(this\)\s*\}/,
46
- ''
47
- );
48
- next = next.replace(/^[ \t]*loadReactNative\(this\)[ \t]*\r?\n/m, '');
49
- if (/\bloadReactNative\s*\(/.test(codeOf(next))) return manual;
73
+ // The template's onCreate stays: its loadReactNative(this) loads React Native for the navigation too.
74
+ let next = withReactNativeLoaded(text.replace(header, 'class $1 : NavigationApplication()'));
75
+ if (next === undefined) return manual;
50
76
  // Added first: with the template's imports gone the file would have none to sort it among.
51
77
  next = addImport(next, 'import dev.apexnative.navigation.NavigationApplication', {
52
78
  sorted: true,
@@ -55,4 +81,93 @@ function planMainApplication(text) {
55
81
  return { status: 'change', next, note: 'NavigationApplication' };
56
82
  }
57
83
 
58
- module.exports = { planMainApplication };
84
+ /**
85
+ * Whether the code of the file, not a comment, calls loadReactNative.
86
+ * @param {string} text
87
+ */
88
+ function loadsReactNative(text) {
89
+ return /\bloadReactNative\s*\(/.test(codeOf(text));
90
+ }
91
+
92
+ /**
93
+ * Whether the code of the file loads React Native on its own as well: the New Architecture's entry point or SoLoader.
94
+ * @param {string} text
95
+ */
96
+ function loadsReactNativeItself(text) {
97
+ const code = codeOf(text);
98
+ return (
99
+ /\bSoLoader\.init\s*\(/.test(code) ||
100
+ /\bDefaultNewArchitectureEntryPoint\.load\s*\(/.test(code) ||
101
+ (BARE_LOAD_IMPORT.test(text) && /(?<![\w.])load\s*\(/.test(code))
102
+ );
103
+ }
104
+
105
+ /**
106
+ * The application with `loadReactNative(this)` in its onCreate, after `super.onCreate()`: as it is when it has the
107
+ * call, with an onCreate of the template's when it has none. Undefined for an onCreate that doesn't call
108
+ * `super.onCreate()`, whose order only its author knows.
109
+ * @param {string} text
110
+ * @returns {string | undefined}
111
+ */
112
+ function withReactNativeLoaded(text) {
113
+ let without = text;
114
+ const patterns = BARE_LOAD_IMPORT.test(text) ? [...OWN_LOAD, ...BARE_LOAD] : OWN_LOAD;
115
+ for (const pattern of patterns) without = without.replace(pattern, '');
116
+ without = removeUnusedImports(without, OWN_LOAD_IMPORTS);
117
+ if (loadsReactNativeItself(without)) return undefined;
118
+ if (loadsReactNative(without)) return without;
119
+ return withLoadCall(without);
120
+ }
121
+
122
+ /**
123
+ * @param {string} text an application without a load of React Native
124
+ * @returns {string | undefined}
125
+ */
126
+ function withLoadCall(text) {
127
+ let next;
128
+ const superCall = /^([ \t]*)super\.onCreate\(\)[ \t]*\r?\n/m;
129
+ if (/\boverride\s+fun\s+onCreate\s*\(/.test(text)) {
130
+ if (!superCall.test(text)) return undefined;
131
+ next = text.replace(superCall, (line, indent) => `${line}${indent}loadReactNative(this)\n`);
132
+ } else {
133
+ const close = classEnd(text);
134
+ if (close === undefined) return undefined;
135
+ const indent = /^([ \t]+)override\b/m.exec(text)?.[1] ?? ' ';
136
+ const body = text.slice(0, close).replace(/\s*$/, '');
137
+ const onCreate = [
138
+ '',
139
+ `${indent}override fun onCreate() {`,
140
+ `${indent}${indent}super.onCreate()`,
141
+ `${indent}${indent}loadReactNative(this)`,
142
+ `${indent}}`,
143
+ ].join('\n');
144
+ next = `${body}\n${onCreate}\n${text.slice(close)}`;
145
+ }
146
+ return addImport(next, LOAD_IMPORT, { sorted: true });
147
+ }
148
+
149
+ /**
150
+ * The index of the closing brace of the file's application class: its braces counted from the class's own, past any
151
+ * declaration or comment after it. Undefined when the braces don't close.
152
+ * @param {string} text
153
+ * @returns {number | undefined}
154
+ */
155
+ function classEnd(text) {
156
+ // The comments and the string and character literals blanked out, so that an index in it is one in the text and a
157
+ // brace in a literal doesn't count.
158
+ const blank = (/** @type {string} */ part) => part.replace(/[^\n]/g, ' ');
159
+ const code = text
160
+ .replace(/\/\*[\s\S]*?\*\//g, blank)
161
+ .replace(/\/\/.*$/gm, blank)
162
+ .replace(/"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'/g, blank);
163
+ const header = /\bclass\s+\w+\s*:[^{]*\{/.exec(code);
164
+ if (!header) return undefined;
165
+ let depth = 0;
166
+ for (let index = header.index + header[0].length - 1; index < code.length; index++) {
167
+ if (code[index] === '{') depth++;
168
+ else if (code[index] === '}' && --depth === 0) return index;
169
+ }
170
+ return undefined;
171
+ }
172
+
173
+ module.exports = { planMainApplication, withReactNativeLoaded, loadsReactNativeItself };
@@ -88,7 +88,9 @@ async function splash({
88
88
  }
89
89
  const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
90
90
  if (plist?.includes('<key>UILaunchScreen</key>')) {
91
- command.warn('Info.plist has UILaunchScreen too: remove it, so that iOS shows the storyboard.');
91
+ command.warn(
92
+ 'Info.plist has UILaunchScreen too: iOS shows it instead of the storyboard; remove it, so that iOS shows the storyboard.'
93
+ );
92
94
  }
93
95
  return command.apply(
94
96
  [
@@ -146,7 +146,7 @@ navigation.push('Details', {
146
146
 
147
147
  Details rises 300 points as it fades in. Each property animates from a value to a value over a time, with a delay and an interpolation if it needs them; the [API](#api) lists every one.
148
148
 
149
- The same goes for `pop`, `showModal`, `dismissModal`, `setStackRoot` and `setRoot`. The swipe back is the exception: (iOS) it always follows the finger with the system's slide, so a custom `pop` plays for the back button and `pop()` only.
149
+ The same goes for `pop`, `showModal`, `dismissModal`, `setStackRoot` and `setRoot`. The swipe back is the exception: (iOS) it always follows the finger with the system's slide, so a custom `pop` plays for the back button and `pop()` only. (Android) A pop with an animation of its own shows no preview under the swipe: the screen pops when the swipe ends.
150
150
 
151
151
  ### The top bar
152
152
 
@@ -168,11 +168,10 @@ A screen can come in before its React content has drawn, or wait for it. Coming
168
168
 
169
169
  | | By default |
170
170
  | --- | --- |
171
- | (iOS) push | Doesn't wait for the content: the screen slides in with its bar, and the content draws as it comes. It waits only for components in the bar. |
172
- | (Android) push | Doesn't wait. With `appProvider` a React screen is rendered before the push starts, so it comes in drawn from the first frame. A screen with a React root of its own comes in at once, and its content draws as it comes; so does a native screen. The components of the bar draw as the screen comes in. |
171
+ | Push | Waits for the screen's first mount, half a second at most, so it comes in drawn, with what it set as it rendered (a title from `useScreenOptions`) already in its bar. A native screen comes in at once. (iOS) The components of the bar wait too, each to its own limit from the start of the wait. (Android) The components of the bar draw as the screen comes in; with `appProvider` the screen is rendered before the push starts ([below](#wait-one-tree)). |
173
172
  | (Android) modal | Waits for the content and the components of the bar, half a second at most, so the screen comes in drawn. |
174
173
 
175
- `waitForRender` of an animation changes it. `true` waits as long as it takes; `false` comes in at once (a push on Android with `appProvider` [still renders the screen first](#wait-one-tree)):
174
+ `waitForRender` of an animation changes it. `true` waits as long as it takes; `false` comes in at once, the content drawing as it comes (a push on Android with `appProvider` [still renders the screen first](#wait-one-tree)):
176
175
 
177
176
  ```ts
178
177
  navigation.push('Details', {
@@ -180,15 +179,15 @@ navigation.push('Details', {
180
179
  });
181
180
  ```
182
181
 
183
- A screen that draws nothing at first doesn't hold its transition back: it comes in empty, with its bar.
182
+ A screen that draws nothing at first holds its transition back half a second at most, then comes in with its bar.
184
183
 
185
184
  ### A push on Android, in one tree
186
185
 
187
- (Android) With `appProvider` a push doesn't wait for the screen, and the screen still comes in drawn: nothing of it mounts during the transition, which would drop frames. It goes in three steps:
186
+ (Android) With `appProvider` the screen comes in drawn without a wait of its own: nothing of it mounts during the transition, which would drop frames. It goes in three steps:
188
187
 
189
188
  1. The native side lays the screen out in its stack, out of sight, and gives React its size and its safe area.
190
189
  2. React renders the screen at that size with its content, at once.
191
- 3. The push starts in the frame in which that content appears; if it doesn't appear, after half a second.
190
+ 3. The push starts right after that content appears, once what the screen set as it rendered (its `useScreenOptions`) is applied; if it doesn't appear, after half a second.
192
191
 
193
192
  So the push starts later by the time React takes to render the screen, as a push in a native app starts once its screen is built. `waitForRender: false` goes the same way here; `true` waits for the content as long as it takes, and for the components of the bar, half a second at most.
194
193
 
@@ -52,8 +52,8 @@ Compose shows with a close button at the leading edge of its top bar. The settin
52
52
 
53
53
  | Setting | What it does |
54
54
  | --- | --- |
55
- | `closeButton` | A close button on the first screen: the system one on iOS, an icon on Android. Needs the stack. |
56
- | `onDismiss` | Called once the modal is gone, whatever closed it: a swipe, a command, a new root. |
55
+ | `closeButton` | A close button on the first screen: the system one on iOS, an icon on Android. Needs the stack. A new first screen from `setStack` or `replace` in the modal's stack gets it too. |
56
+ | `onDismiss` | Called once the modal is gone, whatever closed it: a swipe, a command, a new root. `setStack` and `replace` in the modal's stack replace its first screen and call it too. |
57
57
  | `stack` | `false`: the screen alone, without a stack and its top bar. |
58
58
  | `style`, `sheet`, `popover`, `contentSize` | How it looks: see [How a modal looks](#styles). |
59
59
  | `transition`, `options` | The animation, and the screen's options for this show. |
@@ -311,7 +311,7 @@ Each file and why: [What the command writes](./splash-screen#files).
311
311
  - **The settings** go into `package.json`, under `apexNavigation.splash`. An option given replaces its saved value; to drop the dark logo or color, remove `darkLogo` or `darkBackground` there and run the command again.
312
312
  - **A logo whose corners leave the circle** of Android 12's splash icon, 192 dp, gets a warning: Android cuts them. Make `--logo-width` smaller, or give the logo room around it.
313
313
  - **A file it can't change** is listed as `manual`, with the change to make: an app without `Images.xcassets` next to `Info.plist`, a manifest whose `MainActivity` and `<application>` have no theme to go over.
314
- - **`Info.plist` with `UILaunchScreen`** as well gets a warning: remove that dictionary, so that iOS shows the storyboard.
314
+ - **`Info.plist` with `UILaunchScreen`** as well gets a warning: iOS shows that dictionary instead of the storyboard, so remove it.
315
315
  - **Its errors** in the JSON: `splash-settings` for a setting it can't take, `splash-image` for a logo it can't read or that doesn't fit (too tall at its width, a dark logo of other proportions), and `sharp-missing`, as for `icons`.
316
316
 
317
317
  `doctor` checks the splash in the App group:
@@ -320,7 +320,6 @@ Each file and why: [What the command writes](./splash-screen#files).
320
320
  | --- | --- |
321
321
  | Splash | The files of iOS or Android aren't made from the settings in `package.json`: changed by hand, made on one platform only, or by an older version of the command. Or the settings themselves are wrong, such as a logo that isn't there. |
322
322
  | Splash logo | The logo's corners leave Android 12's circle. |
323
- | Launch screen | Without settings: `Info.plist` has `UILaunchScreen` and no storyboard. iOS shows it at launch, but the navigation can only keep a storyboard on screen, so an empty window shows between it and the first screen. |
324
323
 
325
324
  Each warning names its fix, such as `npx apex-navigation splash`; `--fix` leaves these to you. What the user sees, and a splash of your own after the native one: [Splash screen](./splash-screen).
326
325
 
@@ -370,7 +370,7 @@ The system bar at the top of the screen, with the time and the battery. Each scr
370
370
 
371
371
  ### Swipe back
372
372
 
373
- (iOS) The swipe from the left edge, and on iOS 26 from the content, that goes back following the finger. `popGesture` narrows it or turns it off. More: [The swipe back](../screen-ui/back-and-gestures#swipe).
373
+ (iOS) The swipe from the left edge, and on iOS 26 from the content, that goes back following the finger. `popGesture` narrows it or turns it off. (Android) 14+ The swipe of the system back shows the pop under the finger, and the user can stop halfway. More: [The swipe back](../screen-ui/back-and-gestures#swipe).
374
374
 
375
375
  ### System back
376
376
 
@@ -73,13 +73,30 @@ Options set in `static options` on a component no longer apply.
73
73
 
74
74
  **Fix:** move them into `defineScreen({ component, options })`; the object carries over as it is. More: [Options next to the screen](../migration/from-react-native-navigation#options).
75
75
 
76
+ ### The Android app crashes at start: React Native isn't loaded
77
+
78
+ (Android) The app closes at once with `NavigationApplication needs React Native loaded first`, and `doctor` reports `MainApplication.kt doesn't call loadReactNative(this) in onCreate`.
79
+
80
+ **Why:** `NavigationApplication` leaves the load of React Native to the template's `loadReactNative(this)`, which loads it as the app's Gradle files say (the New Architecture, edge-to-edge). Without the call nothing loads it.
81
+
82
+ **Fix:** keep the template's `onCreate`, or run `npx apex-navigation setup`, which adds it:
83
+
84
+ ```kotlin
85
+ override fun onCreate() {
86
+ super.onCreate()
87
+ loadReactNative(this)
88
+ }
89
+ ```
90
+
91
+ More: [Android files](../start/installation#android-files).
92
+
76
93
  ### The Android app crashes at start: React Native loads twice
77
94
 
78
- (Android) The app closes at once, and `doctor` reports `MainApplication.kt calls loadReactNative(this), NavigationApplication loads it too`.
95
+ (Android) The app closes at once, and `doctor` reports `MainApplication.kt loads React Native itself … next to loadReactNative(this)`.
79
96
 
80
- **Why:** `NavigationApplication` loads React Native itself, and a second load crashes the app.
97
+ **Why:** the application loads React Native on its own as well, with `SoLoader.init` or the New Architecture's entry point (`load()`, as apps from react-native-navigation have it), and a second load crashes the app.
81
98
 
82
- **Fix:** remove the `loadReactNative(this)` call from `MainApplication.kt`. More: [Android files](../start/installation#android-files).
99
+ **Fix:** keep only `loadReactNative(this)` in `onCreate`; `npx apex-navigation setup` removes the other load. More: [Android files](../start/installation#android-files).
83
100
 
84
101
  ### doctor warns about the JDK
85
102
 
@@ -215,9 +232,9 @@ A signed-in user sees Login for a moment, then the app.
215
232
 
216
233
  (iOS) Between the launch screen and the first screen, the window is empty for a moment.
217
234
 
218
- **Why:** the launch screen is made with the `UILaunchScreen` dictionary of `Info.plist`, not a storyboard. The navigation keeps a copy of a storyboard on screen until the first root, and can't make one of the dictionary. `doctor` warns about it under Launch screen.
235
+ **Why:** the app has no launch screen the navigation can copy: `Info.plist` names no storyboard and has no `UILaunchScreen` dictionary (or `UILaunchScreens` default), and no legacy launch images.
219
236
 
220
- **Fix:** run `npx apex-navigation splash`, or name a storyboard in `UILaunchStoryboardName` and remove `UILaunchScreen`. More: [Make the native splash](../guides/splash-screen#command).
237
+ **Fix:** run `npx apex-navigation splash`, or name a storyboard in `UILaunchStoryboardName`. More: [Make the native splash](../guides/splash-screen#command).
221
238
 
222
239
  ### Android brings the app back without its pushed screens
223
240
 
@@ -3952,6 +3952,10 @@
3952
3952
  "heading": "A screen ignores its static options",
3953
3953
  "anchor": "static-options"
3954
3954
  },
3955
+ {
3956
+ "heading": "The Android app crashes at start: React Native isn't loaded",
3957
+ "anchor": "not-loaded"
3958
+ },
3955
3959
  {
3956
3960
  "heading": "The Android app crashes at start: React Native loads twice",
3957
3961
  "anchor": "loads-twice"
@@ -140,7 +140,7 @@ Each piece of `react-native-navigation`, and what takes its place. The [steps](#
140
140
  | `AppRegistry` left out of `index.js` | `AppRegistry.registerComponent(name, Navigation)` of the package. |
141
141
  | `RNNAppDelegate`, `import ReactNativeNavigation` | `ANNAppDelegate`, `import ApexNavigation`. |
142
142
  | `com.reactnativenavigation.*` | `dev.apexnative.navigation.*`. |
143
- | `NavigationReactNativeHost` in `MainApplication` | The template's `reactHost`; `NavigationApplication` loads React Native itself. |
143
+ | `NavigationReactNativeHost` in `MainApplication` | The template's `reactHost`, and its `loadReactNative(this)` in `onCreate`. |
144
144
  | `RNNKotlinVersion` in `android/build.gradle` | Nothing: the library reads `kotlinVersion`. |
145
145
 
146
146
  ## The move, step by step
@@ -212,11 +212,12 @@ import dev.apexnative.navigation.NavigationActivity
212
212
  class MainActivity : NavigationActivity()
213
213
  ```
214
214
 
215
- The application drops `NavigationReactNativeHost` and keeps the `reactHost` of the React Native template. `NavigationApplication` loads React Native in its `onCreate`, so the app doesn't call `loadReactNative` again.
215
+ The application drops `NavigationReactNativeHost` and keeps the `reactHost` of the React Native template. react-native-navigation loaded React Native itself; the navigation leaves it to the template's `loadReactNative(this)` in `onCreate`, which `migrate` adds.
216
216
 
217
217
  ```kotlin
218
218
  import com.facebook.react.PackageList
219
219
  import com.facebook.react.ReactHost
220
+ import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
220
221
  import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
221
222
  import dev.apexnative.navigation.NavigationApplication
222
223
 
@@ -227,6 +228,11 @@ class MainApplication : NavigationApplication() {
227
228
  packageList = PackageList(this).packages,
228
229
  )
229
230
  }
231
+
232
+ override fun onCreate() {
233
+ super.onCreate()
234
+ loadReactNative(this)
235
+ }
230
236
  }
231
237
  ```
232
238
 
@@ -17,7 +17,7 @@ On iOS the back button of Details reads "Inbox". This page answers:
17
17
 
18
18
  1. [The ways back](#ways) on each platform.
19
19
  2. [The back button](#back-button): [its title](#title) and [its look](#look).
20
- 3. [The swipe back](#swipe) of iOS, and [the system back](#system-back) of Android.
20
+ 3. [The swipe back](#swipe) of both platforms, and [the system back](#system-back) of Android.
21
21
  4. [How to keep the user on a screen](#prevent) with unsaved changes.
22
22
 
23
23
  ## The ways back
@@ -85,7 +85,7 @@ export const MapScreen = defineScreen({
85
85
  | `'edge'` | From the edge only: a swipe on the content stays with the content. |
86
86
  | `false` | Off. The back button still works. |
87
87
 
88
- (Android) The system back swipes from either edge; the app doesn't change it.
88
+ (Android) The swipe is the system's back, from either edge, and `popGesture` doesn't apply to it. On Android 14+ it shows the pop before the user lets go: both screens shrink, the screen under comes in behind a dim, and the screen on top follows the finger. The user can stop halfway and stay, as on iOS. On Android 13 and older the screen pops when the swipe ends. When the preview shows and when it doesn't: [the preview of a pop](#pop-preview).
89
89
 
90
90
  ## The system back of Android
91
91
 
@@ -106,6 +106,17 @@ export const MapScreen = defineScreen({
106
106
 
107
107
  The preview shows when back pops the screen with the default transition. It doesn't when back does something else first, or when the app takes it: a menu or a search that closes, a screen that holds back (`hardwareBackButton.popStackOnPress: false`, a [`BackHandler` listener](#back-handler), `usePreventLeave`), or a pop with an animation of its own (`animations.pop`). On Android 13 and older the screen pops when the swipe ends.
108
108
 
109
+ `animations.pop.preview: false` turns the preview off for a screen: the gesture shows nothing, and the screen pops when the user lets go, as on Android 13. It is read from the screen that goes, so the app's `options` turn it off everywhere:
110
+
111
+ ```ts
112
+ defineScreen({
113
+ component: Checkout,
114
+ options: { animations: { pop: { preview: false } } },
115
+ });
116
+ ```
117
+
118
+ iOS ignores it: the swipe back there is UIKit's.
119
+
109
120
  ### Back to the home screen
110
121
 
111
122
  (Android) On the first screen of the app, with nothing left to go back to, the navigation leaves the back to the system. The system then plays its predictive back animation: as the user swipes, the app shrinks and the home screen shows behind it, and the user can still change their mind halfway. The system decides this as the swipe starts, so the navigation takes the back only while it has something to do with it: a screen to pop, a modal, a menu or a search to close, a tab to go to (`backBehavior`), or a [`BackHandler` listener](#back-handler) of the app.
@@ -122,6 +133,17 @@ Without it, back on the first screen still leaves the app, at once. See [What se
122
133
 
123
134
  With the animation on, the system leaves the app itself: an `invokeDefaultOnBackPressed` of the app's `MainActivity` (such as `moveTaskToBack(true)`) isn't called on the first screen. Without it, and before Android 13, the back goes through the activity as before.
124
135
 
136
+ `hardwareBackButton.systemAnimation: false` keeps the back in the app on the first screen too, so the system plays no animation there: the gesture shows nothing, and on release the press goes to the [`BackHandler` listeners](#back-handler), then to the activity's own back, `invokeDefaultOnBackPressed` included. It is read from the screen back acts on; for the whole app, set it in the app's `options`:
137
+
138
+ ```ts
139
+ createNavigation({
140
+ // …
141
+ options: { hardwareBackButton: { systemAnimation: false } },
142
+ });
143
+ ```
144
+
145
+ iOS ignores it.
146
+
125
147
  ### Handling back yourself: BackHandler
126
148
 
127
149
  (Android) React Native's `BackHandler` works on every screen, as in any React Native app. A listener runs before the navigation's own back: returning `true` takes the press, `false` passes it on to the next listener, then to the navigation.