react-native-drum-picker 0.1.2 → 0.1.4

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 CHANGED
@@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  _No changes yet._
11
11
 
12
+ ## [0.1.4] - 2026-05-22
13
+
14
+ ### Fixed
15
+
16
+ - Fixed a crash during screen unmount/navigation by avoiding unsafe RecyclerView cleanup in `onDetachedFromWindow` (`adapter = null`, `clearOnScrollListeners`, SnapHelper detach during react-native-screens transitions).
17
+ - Prevented `onChange` events from dispatching after the view is detached.
18
+ - Fixed Android Kotlin compilation on React Native 0.81+ by dispatching Fabric events with `UIManagerType.FABRIC`.
19
+
20
+ ### Documentation
21
+
22
+ - Added compatibility and troubleshooting notes for React Native 0.81+, Expo SDK 54, Expo Go, and navigation crashes.
23
+
12
24
  ## [0.1.2] - 2026-05-22
13
25
 
14
26
  ### Fixed
@@ -51,7 +63,8 @@ First release on [npm](https://www.npmjs.com/package/react-native-drum-picker).
51
63
 
52
64
  Initial GitHub release. See [v0.0.1](https://github.com/scrollDynasty/react-native-drum-picker/releases/tag/v0.0.1).
53
65
 
54
- [Unreleased]: https://github.com/scrollDynasty/react-native-drum-picker/compare/v0.1.2...HEAD
66
+ [Unreleased]: https://github.com/scrollDynasty/react-native-drum-picker/compare/v0.1.4...HEAD
67
+ [0.1.4]: https://github.com/scrollDynasty/react-native-drum-picker/releases/tag/v0.1.4
55
68
  [0.1.2]: https://github.com/scrollDynasty/react-native-drum-picker/releases/tag/v0.1.2
56
69
  [0.1.1]: https://github.com/scrollDynasty/react-native-drum-picker/releases/tag/v0.1.1
57
70
  [0.1.0]: https://github.com/scrollDynasty/react-native-drum-picker/compare/v0.0.1...v0.1.0
package/README.md CHANGED
@@ -60,6 +60,30 @@ npx react-native run-android
60
60
 
61
61
  Requires **React Native 0.76+** with the **New Architecture** enabled.
62
62
 
63
+ ## Compatibility
64
+
65
+ | Environment | Status |
66
+ |-------------|--------|
67
+ | React Native New Architecture | **Required** |
68
+ | Fabric | **Required** |
69
+ | Android | Supported |
70
+ | iOS | Not supported yet |
71
+ | Expo SDK 54 | Supported via **prebuild** / development build |
72
+ | React Native 0.81+ | Supported |
73
+ | Expo Go | **Not supported** (native module) |
74
+
75
+ This package is an **Android Fabric View** library. New Architecture must be enabled (`newArchEnabled: true` in Expo, or equivalent in bare React Native).
76
+
77
+ Recommended: **React Native >= 0.76**. Use a **development build** or `expo run:android` after `expo prebuild` — not Expo Go.
78
+
79
+ ### React Native 0.81+ event dispatch
80
+
81
+ If Android Kotlin compile fails with:
82
+
83
+ `No value passed for parameter 'uiManagerType'`
84
+
85
+ upgrade to a release that dispatches Fabric events with `UIManagerType.FABRIC` (0.1.3+).
86
+
63
87
  ## Basic usage
64
88
 
65
89
  ```tsx
@@ -225,6 +249,29 @@ Use an **odd** `visibleItemCount` (e.g. `5`) for a symmetric wheel.
225
249
  | Empty or white picker | Enable New Architecture; set explicit `width` / `height` in `style` |
226
250
  | Props not applied | Rebuild app after native changes; run `yarn build` in the library before packing |
227
251
  | iOS | Not supported — Android only |
252
+ | Expo Go | Use prebuild + dev build; this library is not in Expo Go |
253
+ | RN 0.81 `uiManagerType` compile error | Upgrade to 0.1.3+ |
254
+ | Crash when leaving a screen | Upgrade to 0.1.4+ (safe `onDetachedFromWindow` with react-native-screens) |
255
+
256
+ ### Android build fails in Expo / React Native 0.81+
257
+
258
+ ```sh
259
+ cd android
260
+ ./gradlew clean
261
+ ```
262
+
263
+ Windows:
264
+
265
+ ```sh
266
+ cd android
267
+ .\gradlew clean
268
+ ```
269
+
270
+ Then rebuild the native app (`npx expo run:android` or `npx react-native run-android`).
271
+
272
+ ### Crash when leaving a screen
273
+
274
+ If the app crashes when navigating away from a screen with `DrumPicker` (especially with `react-native-screens` transitions), upgrade to **0.1.4+**, which avoids unsafe RecyclerView cleanup during `onDetachedFromWindow`.
228
275
 
229
276
  **New Architecture:** This library is a Fabric view. Ensure New Architecture is enabled in your app (required for RN 0.76+).
230
277
 
@@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReadableArray
13
13
  import com.facebook.react.bridge.ReadableType
14
14
  import com.facebook.react.bridge.ReactContext
15
15
  import com.facebook.react.uimanager.UIManagerHelper
16
+ import com.facebook.react.uimanager.common.UIManagerType
16
17
  import com.facebook.react.uimanager.events.EventDispatcher
17
18
  import kotlin.math.abs
18
19
 
@@ -47,13 +48,14 @@ class DrumPickerView @JvmOverloads constructor(
47
48
  private var selectionIndicatorHeightPx = dpToPx(selectionIndicatorHeightDp)
48
49
  private var lastEmittedIndex = -1
49
50
  private var suppressChangeEvent = false
50
- private var isDetached = false
51
+ private var isAttachedToWindow = false
52
+ private var isDisposed = false
51
53
  private var styleUpdatePosted = false
52
54
 
53
55
  private val styleUpdateRunnable =
54
56
  Runnable {
55
57
  styleUpdatePosted = false
56
- if (!isDetached) {
58
+ if (isLifecycleActive()) {
57
59
  updateVisibleItemStyles()
58
60
  }
59
61
  }
@@ -61,14 +63,14 @@ class DrumPickerView @JvmOverloads constructor(
61
63
  private val scrollListener =
62
64
  object : RecyclerView.OnScrollListener() {
63
65
  override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
64
- if (isDetached) {
66
+ if (!isLifecycleActive()) {
65
67
  return
66
68
  }
67
69
  scheduleVisibleItemStyleUpdate()
68
70
  }
69
71
 
70
72
  override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
71
- if (isDetached) {
73
+ if (!isLifecycleActive()) {
72
74
  return
73
75
  }
74
76
  when (newState) {
@@ -241,25 +243,27 @@ class DrumPickerView @JvmOverloads constructor(
241
243
 
242
244
  override fun onAttachedToWindow() {
243
245
  super.onAttachedToWindow()
244
- isDetached = false
245
- if (recyclerView.adapter == null) {
246
- recyclerView.adapter = adapter
247
- }
248
- snapHelper.attachToRecyclerView(recyclerView)
246
+ isAttachedToWindow = true
247
+ isDisposed = false
248
+ recyclerView.removeOnScrollListener(scrollListener)
249
249
  recyclerView.addOnScrollListener(scrollListener)
250
250
  }
251
251
 
252
252
  override fun onDetachedFromWindow() {
253
- isDetached = true
254
- recyclerView.removeCallbacks(styleUpdateRunnable)
253
+ isAttachedToWindow = false
254
+ isDisposed = true
255
255
  styleUpdatePosted = false
256
+ recyclerView.removeCallbacks(styleUpdateRunnable)
256
257
  removeCallbacks(null)
257
- recyclerView.clearOnScrollListeners()
258
- snapHelper.attachToRecyclerView(null)
259
- recyclerView.adapter = null
258
+ recyclerView.removeCallbacks(null)
259
+ recyclerView.removeOnScrollListener(scrollListener)
260
+ // Do not null adapter or detach SnapHelper here — react-native-screens may
261
+ // detach during transitions while RecyclerView is still laying out/scrolling.
260
262
  super.onDetachedFromWindow()
261
263
  }
262
264
 
265
+ private fun isLifecycleActive(): Boolean = isAttachedToWindow && !isDisposed
266
+
263
267
  fun setSelectedIndex(index: Int) {
264
268
  if (items.isEmpty()) {
265
269
  selectedIndex = index.coerceAtLeast(0)
@@ -388,7 +392,7 @@ class DrumPickerView @JvmOverloads constructor(
388
392
  }
389
393
 
390
394
  private fun scrollToSelectedIndex(animated: Boolean, emit: Boolean) {
391
- if (isDetached || items.isEmpty()) {
395
+ if (!isLifecycleActive() || items.isEmpty()) {
392
396
  return
393
397
  }
394
398
  suppressChangeEvent = !emit
@@ -400,7 +404,7 @@ class DrumPickerView @JvmOverloads constructor(
400
404
 
401
405
  layoutManager.scrollToPositionWithOffset(index, 0)
402
406
  recyclerView.post {
403
- if (isDetached) {
407
+ if (!isLifecycleActive()) {
404
408
  return@post
405
409
  }
406
410
  updateVisibleItemStyles()
@@ -414,11 +418,11 @@ class DrumPickerView @JvmOverloads constructor(
414
418
  }
415
419
 
416
420
  private fun runWhenAttached(block: () -> Unit) {
417
- if (isDetached) {
421
+ if (!isLifecycleActive()) {
418
422
  return
419
423
  }
420
424
  post {
421
- if (isDetached) {
425
+ if (!isLifecycleActive()) {
422
426
  return@post
423
427
  }
424
428
  block()
@@ -426,7 +430,7 @@ class DrumPickerView @JvmOverloads constructor(
426
430
  }
427
431
 
428
432
  private fun updateCenterFromSnap() {
429
- if (isDetached || items.isEmpty()) {
433
+ if (!isLifecycleActive() || items.isEmpty()) {
430
434
  return
431
435
  }
432
436
  val centerIndex = findSnapCenterIndex()
@@ -448,7 +452,7 @@ class DrumPickerView @JvmOverloads constructor(
448
452
  }
449
453
 
450
454
  private fun scheduleVisibleItemStyleUpdate() {
451
- if (styleUpdatePosted || isDetached) {
455
+ if (styleUpdatePosted || !isLifecycleActive()) {
452
456
  return
453
457
  }
454
458
  styleUpdatePosted = true
@@ -466,7 +470,7 @@ class DrumPickerView @JvmOverloads constructor(
466
470
  }
467
471
 
468
472
  private fun updateVisibleItemStyles() {
469
- if (isDetached || recyclerView.height == 0 || itemHeightPx <= 0) {
473
+ if (!isLifecycleActive() || recyclerView.height == 0 || itemHeightPx <= 0) {
470
474
  return
471
475
  }
472
476
 
@@ -483,7 +487,7 @@ class DrumPickerView @JvmOverloads constructor(
483
487
  }
484
488
 
485
489
  private fun maybeEmitChange(index: Int) {
486
- if (isDetached || suppressChangeEvent || items.isEmpty()) {
490
+ if (!isLifecycleActive() || suppressChangeEvent || items.isEmpty()) {
487
491
  return
488
492
  }
489
493
  if (index < 0 || index >= items.size) {
@@ -499,7 +503,12 @@ class DrumPickerView @JvmOverloads constructor(
499
503
  selectedIndex = clamped
500
504
 
501
505
  val reactContext = context as? ReactContext ?: return
502
- val dispatcher: EventDispatcher? = UIManagerHelper.getEventDispatcher(reactContext)
506
+ if (!reactContext.hasActiveReactInstance()) {
507
+ return
508
+ }
509
+ // Fabric-only: RN 0.81+ requires uiManagerType when resolving the event dispatcher.
510
+ val dispatcher: EventDispatcher? =
511
+ UIManagerHelper.getEventDispatcher(reactContext, UIManagerType.FABRIC)
503
512
  dispatcher?.dispatchEvent(
504
513
  DrumPickerChangeEvent(
505
514
  UIManagerHelper.getSurfaceId(reactContext),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-drum-picker",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Android-native iOS-style drum/wheel picker for React Native (Fabric)",
5
5
  "main": "./lib/module/index.js",
6
6
  "module": "./lib/module/index.js",
@@ -101,8 +101,8 @@
101
101
  "typescript": "^6.0.2"
102
102
  },
103
103
  "peerDependencies": {
104
- "react": ">=18.0.0",
105
- "react-native": ">=0.76.0"
104
+ "react": "*",
105
+ "react-native": ">=0.76"
106
106
  },
107
107
  "workspaces": [
108
108
  "example"