react-native-pointr 9.9.2 → 9.9.3
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/CHANGELOG.md +6 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/pointr/PTRMapWidgetManager.kt +57 -0
- package/android/src/main/java/com/pointr/PointrModule.kt +1 -0
- package/ios/PTRMapWidgetContainerView.swift +14 -0
- package/ios/PTRNativeLibrary.swift +28 -6
- package/package.json +1 -1
- package/react-native-pointr.podspec +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [9.9.3] - 2026-07-22
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Mobile SDK 9.9.3 integration.
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
## [9.9.2] - 2026-07-09
|
|
8
14
|
|
|
9
15
|
### Changed
|
package/android/build.gradle
CHANGED
|
@@ -93,7 +93,7 @@ dependencies {
|
|
|
93
93
|
//noinspection GradleDynamicVersion
|
|
94
94
|
implementation "com.facebook.react:react-android:0.82.1"
|
|
95
95
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
96
|
-
implementation("com.pointrlabs:pointr:9.9.
|
|
96
|
+
implementation("com.pointrlabs:pointr:9.9.3")
|
|
97
97
|
implementation ("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
|
|
98
98
|
implementation 'com.google.android.material:material:1.12.0'
|
|
99
99
|
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
|
@@ -245,6 +245,18 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
245
245
|
Choreographer.getInstance().postFrameCallback(frameCallback)
|
|
246
246
|
Log.v(name, "Adding itself as listener to map widget")
|
|
247
247
|
ptrMapWidgetFragment?.addListener(this)
|
|
248
|
+
// The SDK adds the fragment via an ASYNCHRONOUS commit. Force it to complete
|
|
249
|
+
// now, while the container view is still attached. Otherwise, if the widget is
|
|
250
|
+
// closed before that add lands (e.g. closed mid-load), the queued transaction
|
|
251
|
+
// runs afterwards against the detached container and crashes with
|
|
252
|
+
// "No view found for id 0x…". Running it here removes that pending-add window.
|
|
253
|
+
try {
|
|
254
|
+
(reactContext.currentActivity as? FragmentActivity)
|
|
255
|
+
?.supportFragmentManager
|
|
256
|
+
?.executePendingTransactions()
|
|
257
|
+
} catch (e: Exception) {
|
|
258
|
+
Log.e(name, "executePendingTransactions after fragment create failed: ${e.message}")
|
|
259
|
+
}
|
|
248
260
|
return ptrMapWidgetFragment!!
|
|
249
261
|
}
|
|
250
262
|
|
|
@@ -263,6 +275,51 @@ class PTRMapWidgetManager(private val reactContext: ReactApplicationContext) :
|
|
|
263
275
|
return frameLayout
|
|
264
276
|
}
|
|
265
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Called by React Native when the <PTRMapWidget /> view is unmounted.
|
|
280
|
+
*
|
|
281
|
+
* The map widget is a Fragment attached to the host Activity's FragmentManager,
|
|
282
|
+
* so removing the React view alone does NOT tear it down — its dialogs/popups
|
|
283
|
+
* (welcome, onboarding, init-error, info page, …) live at the window level and
|
|
284
|
+
* would otherwise remain visible after the widget is closed. Removing the
|
|
285
|
+
* Fragment runs its onDestroyView(), which dismisses every dialog it owns, and
|
|
286
|
+
* cancelling the frame callback stops the per-frame command loop from leaking.
|
|
287
|
+
*/
|
|
288
|
+
override fun onDropViewInstance(view: FrameLayout) {
|
|
289
|
+
super.onDropViewInstance(view)
|
|
290
|
+
|
|
291
|
+
// Stop the per-frame loop that keeps re-measuring / re-triggering commands.
|
|
292
|
+
Choreographer.getInstance().removeFrameCallback(frameCallback)
|
|
293
|
+
|
|
294
|
+
val fragment = ptrMapWidgetFragment
|
|
295
|
+
ptrMapWidgetFragment = null
|
|
296
|
+
// Reset so a subsequent open builds a fresh widget and re-runs its command.
|
|
297
|
+
commandDidExecute = false
|
|
298
|
+
commandTriggered = false
|
|
299
|
+
|
|
300
|
+
if (fragment != null) {
|
|
301
|
+
fragment.removeListener(this)
|
|
302
|
+
val fragmentManager =
|
|
303
|
+
(reactContext.currentActivity as? FragmentActivity)?.supportFragmentManager
|
|
304
|
+
if (fragmentManager != null && !fragmentManager.isDestroyed && fragment.isAdded) {
|
|
305
|
+
// Remove the fragment SYNCHRONOUSLY (commitNow) while the container view
|
|
306
|
+
// still exists. An async commit()/commitAllowingStateLoss() would run a
|
|
307
|
+
// beat later — after React Native has already detached this FrameLayout —
|
|
308
|
+
// and the FragmentManager would then try to (re)create the fragment's view
|
|
309
|
+
// into a container that is gone, crashing with "No view found for id".
|
|
310
|
+
// Wrapped defensively so a lifecycle race can degrade to a no-op, never a
|
|
311
|
+
// crash, on the app's main thread.
|
|
312
|
+
try {
|
|
313
|
+
fragmentManager.beginTransaction()
|
|
314
|
+
.remove(fragment)
|
|
315
|
+
.commitNowAllowingStateLoss()
|
|
316
|
+
} catch (e: Exception) {
|
|
317
|
+
Log.e(name, "Failed to remove map widget fragment on view drop: ${e.message}")
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
266
323
|
/**
|
|
267
324
|
* Handle "create" command (called from JS) and call createFragment method
|
|
268
325
|
*/
|
|
@@ -339,6 +339,20 @@ import PointrKit
|
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
// React Native is unmounting this view when it is moved to a nil window.
|
|
343
|
+
// Tear the map widget down here so its popups/sheets cannot surface after the
|
|
344
|
+
// widget is closed: removing ourselves as a listener breaks the widget→container
|
|
345
|
+
// retain cycle, letting the view controller (and its whole view hierarchy,
|
|
346
|
+
// including any popup) deallocate instead of lingering.
|
|
347
|
+
override func willMove(toWindow newWindow: UIWindow?) {
|
|
348
|
+
super.willMove(toWindow: newWindow)
|
|
349
|
+
guard newWindow == nil, let mapWidget = mapWidget else { return }
|
|
350
|
+
mapWidget.removeListener(self)
|
|
351
|
+
mapWidget.view.removeFromSuperview()
|
|
352
|
+
wayfindingEventsAdapter = nil
|
|
353
|
+
self.mapWidget = nil
|
|
354
|
+
}
|
|
355
|
+
|
|
342
356
|
private func applySDKConfigIfNeeded() {
|
|
343
357
|
guard let jsonString = sdkConfig,
|
|
344
358
|
let data = jsonString.data(using: .utf8),
|
|
@@ -224,10 +224,21 @@ class PTRNativeLibrary: RCTEventEmitter, PTREventManagerDelegate {
|
|
|
224
224
|
} else {
|
|
225
225
|
let dispatchGroup = DispatchGroup()
|
|
226
226
|
dispatchGroup.enter()
|
|
227
|
+
// Pointr.shared.start(with:completion:) does not fire its completion
|
|
228
|
+
// exactly once — it's invoked on every subsequent state change too
|
|
229
|
+
// (e.g. the transition triggered by a later stop() call also matches
|
|
230
|
+
// `state.rawValue <= 0`). Guard so dispatchGroup.leave() only ever
|
|
231
|
+
// runs once; otherwise a second leave() on an already-balanced group
|
|
232
|
+
// is a fatal "BUG IN CLIENT OF LIBDISPATCH" crash.
|
|
233
|
+
var hasLeftDispatchGroup = false
|
|
234
|
+
let dispatchGroupLock = NSLock()
|
|
227
235
|
Pointr.shared.start(with: PTRNativeLibrary.params) { state in
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
236
|
+
guard state == .running || state.rawValue <= 0 else { return }
|
|
237
|
+
dispatchGroupLock.lock()
|
|
238
|
+
defer { dispatchGroupLock.unlock() }
|
|
239
|
+
guard !hasLeftDispatchGroup else { return }
|
|
240
|
+
hasLeftDispatchGroup = true
|
|
241
|
+
dispatchGroup.leave()
|
|
231
242
|
}
|
|
232
243
|
dispatchGroup.wait()
|
|
233
244
|
if Pointr.shared.state == .running {
|
|
@@ -239,9 +250,20 @@ class PTRNativeLibrary: RCTEventEmitter, PTREventManagerDelegate {
|
|
|
239
250
|
}
|
|
240
251
|
|
|
241
252
|
@objc func stop() {
|
|
242
|
-
Pointr.shared.
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
guard Pointr.shared.state == .running else {
|
|
254
|
+
// Nothing to tear down — avoids double-stop crashes if called twice
|
|
255
|
+
// in a row or before the SDK finished starting.
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
// This module reports `requiresMainQueueSetup() -> false`, so React Native
|
|
259
|
+
// invokes this method on a background queue. Pointr.shared.stop() tears down
|
|
260
|
+
// location/Bluetooth/map resources that must be released on the main thread,
|
|
261
|
+
// so hop over explicitly to avoid a crash.
|
|
262
|
+
DispatchQueue.main.async {
|
|
263
|
+
Pointr.shared.positioningManager?.removeListener(self)
|
|
264
|
+
Pointr.shared.geofenceManager?.removeListener(self)
|
|
265
|
+
Pointr.shared.stop()
|
|
266
|
+
}
|
|
245
267
|
}
|
|
246
268
|
|
|
247
269
|
@objc func requestPermissions() {
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
|
|
16
16
|
|
|
17
17
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
18
18
|
|
|
19
|
-
s.dependency 'PointrKit', '9.9.
|
|
19
|
+
s.dependency 'PointrKit', '9.9.3'
|
|
20
20
|
|
|
21
21
|
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
|
|
22
22
|
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
|