architecture-samples icon indicating copy to clipboard operation
architecture-samples copied to clipboard

[master] Animation of refresh is not smooth

Open musilto7 opened this issue 4 years ago • 1 comments

If swipe to refresh is done animation of removing and then adding items is done. It caused by the implementation of the ` private suspend fun updateTasksFromRemoteDataSource() { val remoteTasks = tasksRemoteDataSource.getTasks()

    if (remoteTasks is Success) {
        // Real apps might want to do a proper sync, deleting, modifying or adding each task.
        tasksLocalDataSource.deleteAllTasks()
        remoteTasks.data.forEach { task ->
            tasksLocalDataSource.saveTask(task)
        }
    } else if (remoteTasks is Result.Error) {
        throw remoteTasks.exception
    }
}

` All tasks are removed and then new downloaded task are added one by one. This operation are propagated through the observers to the RecyclerView.

musilto7 avatar Jan 16 '21 22:01 musilto7

You're absolutely right — the animation glitch in swipe-to-refresh is caused by removing all tasks and then adding them back one by one, which causes the RecyclerView to trigger a complete teardown and rebuild of its items. That leads to a jarring animation instead of a smooth transition.

Problem Summary The issue stems from this logic:

tasksLocalDataSource.deleteAllTasks() remoteTasks.data.forEach { task -> tasksLocalDataSource.saveTask(task) } This causes:

RecyclerView to remove all current items → triggers removal animation.

Then it adds all items again → triggers insertion animation.

No diffing, no awareness of unchanged items → no smooth update.

Solution: Use a diffing strategy Instead of clear & insert all, you should implement a "smart sync" strategy:

Option 1: Sync with DiffUtil in UI Layer (Recommended) Let the data layer still provide all the tasks, but handle differences in the ViewModel/RecyclerView adapter:

// UI Layer: Use DiffUtil to calculate the minimal changes val diffResult = DiffUtil.calculateDiff(oldList, newList) adapter.submitList(newList) diffResult.dispatchUpdatesTo(adapter) This provides fine-grained animations.

You don’t need to change the repository code drastically.

Option 2: Optimize Repository Sync Logic Modify updateTasksFromRemoteDataSource() to:

val localTasks = tasksLocalDataSource.getTasks() // or cache them in memory val remoteTasks = tasksRemoteDataSource.getTasks()

if (remoteTasks is Success) { val newData = remoteTasks.data

val tasksToDelete = localTasks.filterNot { local -> newData.any { it.id == local.id } }
val tasksToAdd = newData.filterNot { remote -> localTasks.any { it.id == remote.id } }
val tasksToUpdate = newData.filter { remote ->
    localTasks.any { local -> local.id == remote.id && local != remote }
}

tasksToDelete.forEach { tasksLocalDataSource.deleteTask(it.id) }
tasksToAdd.forEach { tasksLocalDataSource.saveTask(it) }
tasksToUpdate.forEach { tasksLocalDataSource.updateTask(it) }

} This prevents unnecessary deletions/inserts.

Less stress on UI observers.

But more complex, and may still not give great animations unless UI uses DiffUtil.

Bonus: Use Paging 3 or Jetpack Compose If you're open to upgrading:

Paging 3 handles efficient updates + caching.

Jetpack Compose + LazyColumn + snapshotFlow handles state much smoother than RecyclerView.

Conclusion For minimal impact and best result:

Keep the current data sync as-is.

In your RecyclerView Adapter, apply DiffUtil when submitting new data.

VaradGupta23 avatar Jul 18 '25 12:07 VaradGupta23