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 +14 -1
- package/README.md +47 -0
- package/android/src/main/java/com/drumpicker/DrumPickerView.kt +32 -23
- package/package.json +3 -3
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.
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
254
|
-
|
|
253
|
+
isAttachedToWindow = false
|
|
254
|
+
isDisposed = true
|
|
255
255
|
styleUpdatePosted = false
|
|
256
|
+
recyclerView.removeCallbacks(styleUpdateRunnable)
|
|
256
257
|
removeCallbacks(null)
|
|
257
|
-
recyclerView.
|
|
258
|
-
|
|
259
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
421
|
+
if (!isLifecycleActive()) {
|
|
418
422
|
return
|
|
419
423
|
}
|
|
420
424
|
post {
|
|
421
|
-
if (
|
|
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 (
|
|
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 ||
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
105
|
-
"react-native": ">=0.76
|
|
104
|
+
"react": "*",
|
|
105
|
+
"react-native": ">=0.76"
|
|
106
106
|
},
|
|
107
107
|
"workspaces": [
|
|
108
108
|
"example"
|