# Why not JetPack Compose with DiffUtil?

`DiffUtil` is a tool in Android's RecyclerView library that helps update lists efficiently. It compares an old list with a new list to find the differences. Instead of replacing the whole list and redrawing everything, `DiffUtil` figures out the smallest changes needed to update the old list to match the new one. This makes animations smoother and improves performance.

> How `DiffUtil` Works

1. **Identifying Changes**: It compares items in the old and new lists based on whether the items are the same (`areItemsTheSame`) and whether their content is the same (`areContentsTheSame`).
    
2. **Finding Minimal Changes**: `DiffUtil` uses Myers' difference algorithm to find the smallest number of insertions, deletions, and moves needed to change the old list into the new one.
    
3. **Generating** `DiffResult`: After finding the changes, it creates a `DiffResult` object that lists the necessary updates.
    
4. **Applying Changes**: The `DiffResult` is then used by a `RecyclerView.Adapter` to update the list efficiently, with animations for the changes.
    

> Key Concepts

* **Myers' Algorithm**: This algorithm is key to `DiffUtil`. It compares sequences to find the smallest set of changes needed, making it efficient even for large datasets.
    
* **Performance**: `DiffUtil` boosts performance by reducing the number of operations needed to update the UI and provides smooth animations for changes.
    
* **Ease of Use**: It hides the complexity of comparing lists and applying updates, making it easier for developers to handle dynamic lists in a RecyclerView.
    

> Benefits

* **Performance optimization**: It slashes unnecessary updates to the UI, making everything faster!
    
* **Smooth user experience**: It effortlessly handles animations for changes, making everything look so cool!
    
* **Developer-friendly**: It makes updating lists a breeze, saving you tons of time and effort!
    

In summary, `DiffUtil` is an essential tool in the RecyclerView ecosystem. It efficiently manages list updates, boosting performance and enhancing the user experience.

---

### Jetpack Compose with DiffUtil?

In Jetpack Compose, we don't use `RecyclerView` directly as you would in a traditional Android View-based UI. Instead, Compose provides the `LazyColumn` and `LazyRow` components, which can efficiently display lists of items. But guess what? We might still want the functionality of `DiffUtil` to efficiently update the list when the underlying data changes!

Here is we you can create a utility function in Jetpack Compose that replicates the behavior of `DiffUtil` for a `LazyColumn` or `LazyRow`:

> Create a `DiffUtil`\-like function

First, create a function that calculates the difference between two lists:

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import androidx.compose.runtime.*
import kotlinx.coroutines.launch

// Define a function to calculate the diff between two lists
suspend fun <T> calculateDiff(
    oldList: List<T>,
    newList: List<T>,
    areItemsTheSame: (T, T) -> Boolean,
    areContentsTheSame: (T, T) -> Boolean = areItemsTheSame
): List<DiffResult<T>> {
    return withContext(Dispatchers.Default) {
        val diffResult = mutableListOf<DiffResult<T>>()

        val oldSize = oldList.size
        val newSize = newList.size

        val usedOldItems = BooleanArray(oldSize)
        for (newItem in newList) {
            var found = false
            for (i in oldList.indices) {
                if (!usedOldItems[i] && areItemsTheSame(oldList[i], newItem)) {
                    found = true
                    usedOldItems[i] = true
                    if (!areContentsTheSame(oldList[i], newItem)) {
                        diffResult.add(DiffResult.ItemUpdated(oldList[i], newItem))
                    }
                    break
                }
            }
            if (!found) {
                diffResult.add(DiffResult.ItemInserted(newItem))
            }
        }

        for (i in oldList.indices) {
            if (!usedOldItems[i]) {
                diffResult.add(DiffResult.ItemRemoved(oldList[i]))
            }
        }

        diffResult
    }
}

// Define a sealed class to represent the diff result
sealed class DiffResult<T> {
    data class ItemInserted<T>(val item: T) : DiffResult<T>()
    data class ItemRemoved<T>(val item: T) : DiffResult<T>()
    data class ItemUpdated<T>(val oldItem: T, val newItem: T) : DiffResult<T>()
}
```

> Use the `DiffUtil`\-like function with `LazyColumn`

Now, let's create a `Composable` that uses this awesome utility function to efficiently update a `LazyColumn`!

```kotlin
@Composable
fun <T> DiffableLazyColumn(
    items: List<T>,
    key: (T) -> Any,
    content: @Composable (T) -> Unit,
    areItemsTheSame: (T, T) -> Boolean,
    areContentsTheSame: (T, T) -> Boolean = areItemsTheSame
) {
    val oldItems = remember { mutableStateListOf<T>() }

    val diffResult = remember { mutableStateOf<List<DiffResult<T>>>(emptyList()) }
    val coroutineScope = rememberCoroutineScope()

    LaunchedEffect(items) {
        coroutineScope.launch {
            diffResult.value = calculateDiff(
                oldItems,
                items,
                areItemsTheSame,
                areContentsTheSame
            )
            oldItems.clear()
            oldItems.addAll(items)
        }
    }

    LazyColumn {
        itemsIndexed(diffResult.value) { index, diff ->
            when (diff) {
                is DiffResult.ItemInserted -> content(diff.item)
                is DiffResult.ItemRemoved -> {}
                is DiffResult.ItemUpdated -> content(diff.newItem)
            }
        }
    }
}
```

> Example Usage

Here is an example of how to utilize the `DiffableLazyColumn` in our Compose UI:

```kotlin
@Composable
fun MyScreen() {
    var items by remember { mutableStateOf(listOf("Item 1", "Item 2", "Item 3")) }

    DiffableLazyColumn(
        items = items,
        key = { it },
        areItemsTheSame = { oldItem, newItem -> oldItem == newItem },
        areContentsTheSame = { oldItem, newItem -> oldItem == newItem }
    ) { item ->
        Text(text = item)
    }

    Button(onClick = {
        // Update the list with new items or changes
        items = listOf("Item 1", "Item 2", "Item 4")
    }) {
        Text("Update List")
    }
}
```

![](https://media1.tenor.com/m/2w1XsfvQD5kAAAAC/hhgf.gif align="center")

> Explanation

* **Diff Calculation**: The `calculateDiff` function figures out the difference between the old and new list, just like `DiffUtil` does. How cool is that?!
    
* **State Management**: The `DiffableLazyColumn` keeps track of the old list using `remember` and updates it whenever the new list changes. It's like magic!
    
* **Rendering**: The `LazyColumn` is used to render the items based on the diff results. Super efficient!
    

This approach gives you the awesome efficiency of `DiffUtil` in a Jetpack Compose context, making sure that only the necessary items are re-rendered when the list changes. How amazing is that?!

<details data-node-type="hn-details-summary"><summary>SUCCESS!!!</summary><div data-type="detailsContent">Yes, we have integrated our own DiffUtil with Jetpack Compose!</div></details>

### BUT!!!

![](https://media1.tenor.com/m/IJwsfw7ToiQAAAAd/wait-what.gif align="center")

### What about Space and Time Complexity?

> #### Time Complexity

The time complexity of the provided `calculateDiff` function can be broken down as follows:

* **Outer Loop (**`newList`): This loop goes through each item in the `newList`, so it runs in `O(n)` where `n` is the size of `newList`.
    
* **Inner Loop (**`oldList`): For each item in the `newList`, the function checks each item in the `oldList` to find a match. This runs in `O(m)` where `m` is the size of `oldList`.
    

Wait, what?! Since the inner loop runs for each item in the `newList`, the overall time complexity is `O(n * m)`.

> #### Space Complexity

* **Space for the Result List**: The space needed to store the diff results will be proportional to the number of changes, so in the worst case, it could be `O(n + m)`.
    
* **Space for the Used Old Items**: The function also uses an auxiliary array `usedOldItems` to track which items in the `oldList` have been matched, which has a space complexity of `O(m)`.
    

Can you believe it? Thus, the space complexity is `O(n + m)`.

### Is It Good to Use with Compose?

While this `DiffUtil`\-like function is conceptually similar to `DiffUtil` in RecyclerView, it may not be the most optimal approach in Compose for the following reasons:

1. **Compose Efficiency**: Jetpack Compose is designed to be efficient in re-composition. The Compose framework inherently minimizes unnecessary recompositions, especially when combined with stable, immutable state and key-based optimizations (like the `key` parameter in `LazyColumn`).
    
2. **Manual Diffing Overhead**: The provided approach introduces manual diffing and adds an extra layer of complexity that might not always be necessary. In many cases, the built-in behavior of `LazyColumn` with proper key handling can be sufficient and more performant, especially for small to medium-sized lists.
    
3. **Composability**: Compose encourages thinking in terms of composability and state. The library approach provided might be seen as a more imperative or traditional approach, which may not align well with Compose’s declarative nature.
    

### Recommendations

* **For Small/Medium Lists**: Utilize Compose’s native list handling with `LazyColumn`, ensuring proper use of keys for list items. This approach should be efficient for most scenarios.
    
* **For Large Lists with Complex Diffing Needs**: When dealing with very large lists and aiming to minimize recompositions, consider a more advanced diffing algorithm or structure our data to reduce updates. The current `O(n * m)` approach may become a performance issue with large datasets.
    
* **Use Stable Keys**: Ensure each item in our `LazyColumn` has a stable key by using the `key` parameter to avoid unnecessary recompositions. This is typically sufficient for handling most dynamic list scenarios in Compose.
    

In summary, while the provided approach is functional, it is generally advisable to rely on Compose's built-in optimizations. Custom diffing logic should only be employed if there is a specific requirement that Compose's native capabilities cannot efficiently address.

---

That's it for today. Happy Coding...

%%[buymeacoffee-donate]
