Implementing drag and drop in RecyclerView
RecyclerView gives Android apps an efficient way to present changing collections, but displaying rows is only half the job. When order matters, users need a direct way to rearrange content while the underlying data remains consistent.
Long-press activation is a familiar Android pattern, and ItemTouchHelper supplies the gesture plumbing without forcing a custom gesture system. The difficult part is deciding how a visual move becomes a durable change in the app’s model.
A robust implementation also handles filtering, refreshes, persistence failures, and accessibility. Without those pieces, a row may appear to move correctly on screen and then snap back after the next update or restart.
Australian products add useful context. An order may sit beside a price in Australian dollars with GST included, a delivery window using a 24-hour clock, or a destination such as Parramatta NSW, Melbourne VIC, or Townsville QLD. Those values should travel with the item rather than depend on its current row number.
Put ordering in the data model
Start by treating order as model data. Give every item a stable identifier and an explicit position or sort key; the adapter should render a list, not invent the business meaning of its arrangement.
A common model contains id, label, and order. When a row moves, update that collection once, then notify the adapter with a move operation. Re-sorting the whole list can make several rows flash and may undermine TalkBack focus.
Filtering needs separate state. Keep the complete ordered collection and the visible query or category separately, so hiding a row does not accidentally rewrite the hidden order. Use notifyItemMoved for a simple adjacent drag and test non-adjacent destinations against the exact collection being displayed.
Connect the gesture to item movement
ItemTouchHelper works with a callback that reports drag direction, movement, and swipe events. Attach it to the RecyclerView in the activity or fragment, and enable long-press dragging unless the product requires a different start gesture.
Within getMovementFlags, return the drag and up-or-down flags appropriate to the layout. A vertical list usually allows upward and downward movement; a grid can restrict movement to rows or columns. The callback’s onMove method should move the item in the backing list and call notifyItemMoved with the original and destination positions.
Give feedback while the finger is down by changing elevation, scale, or background in onSelectedChanged, then restore the row in clearView. Keep that visual state separate from the saved order, and avoid animating every item when only one row is active.
Persist the new order without losing user work
Updating the adapter is not persistence. Save the moved identifier and its neighbouring items, or send the complete ordered identifiers, through a repository so the new order survives process death and appears on another device.
For a network-backed list, use an explicit reorder operation containing stable IDs rather than array indexes that may differ between clients. If MySQL runs on a DigitalOcean Droplet, keep phpMyAdmin for administration and send the app through a secured backend endpoint; database credentials must never ship inside the APK. Apply the change optimistically, show a busy state, and roll back with a clear message if the server rejects the move.
Australian users in regional New South Wales or Queensland may lose coverage while using a train, bus, or farm connection. A local queue can retain the pending move, but the sync process must detect conflicts when someone else has reordered the same collection.
Make movement usable and accessible
Dragging must not be the only way to reorder. Long-press gestures can be awkward with switch access, voice control, or limited mobility, so pair the interaction with named accessibility actions such as Move up and Move down.
- Announce the item name and new position after each move, not just “Item moved.”
- Keep move controls at least 48dp high and provide visible focus for keyboard and switch users.
- Preserve the dragged row’s contrast and disable distracting motion when reduced animation is requested.
- Keep Australian dates, distances, temperatures, and A$ prices attached to the item.
- Use plain Australian English, including organise, cancelled, and colour, in labels and announcements.
- Avoid relying on colour alone; pair the active state with an icon, outline, or text.
For a Melbourne delivery list, GST-inclusive pricing should remain unchanged when a meal changes position. For a Parramatta pickup list, the suburb and NSW label should stay attached to the booking. These details make the feature feel local rather than like a generic Android sample.
Test the behaviour under changing list states
The hardest state is a filtered or searched list. A companion example on filtering a RecyclerView shows how query state can remain separate from item data; reuse that separation when calculating the drag destination.
Test moving an item across a filtered boundary, dragging while results refresh, and returning to an empty result. Confirm that hidden rows retain their order and that a rejected move restores both the visible list and the spoken focus.
Add instrumentation tests for upward, downward, and filtered moves. Also check rapid repeated gestures, a slow network request, and a configuration change, because these expose assumptions that a manual happy path can hide.
Put the feature into production
Before release, run the interaction with screen readers, keyboard navigation, dark mode, large fonts, and both portrait and landscape layouts. Keep persistence work off the main thread, expose loading and failure states, and ensure the final order matches the repository after every refresh.
Open the next sprint board now and turn the checklist into tasks for gesture handling, durable ordering, accessibility, and filtered-state testing.