Skip to main content

How to Implement Pull-to-Refresh in Jetpack Compose

How to Implement Pull-to-Refresh in Jetpack Compose

How to Implement Pull-to-Refresh in Jetpack Compose

In modern mobile apps, providing a smooth and intuitive user experience is paramount. One common UX pattern that improves content refreshability and engagement is the "pull-to-refresh" gesture. While classic Android Views used SwipeRefreshLayout for this, Jetpack Compose now offers a clean, declarative way to incorporate pull-to-refresh behavior effortlessly.

In this blog post, we’ll explore how to implement pull-to-refresh in Jetpack Compose using the official accompanist-swiperefresh library. We will cover everything from setup to integrating with real data refresh scenarios, armed with clear explanations and practical Kotlin code samples.

Why Use Pull-to-Refresh in Jetpack Compose?

Pull-to-refresh is a widely adopted user interface pattern that allows users to refresh the contents of a screen via a downward swipe gesture. It’s especially useful in lists or feeds where content changes dynamically.

Jetpack Compose, Android’s modern UI toolkit, promotes a declarative UI development style. While classic Views rely on widgets like SwipeRefreshLayout, Compose encourages us to think in terms of states and recomposition. To integrate pull-to-refresh elegantly, we need a Compose-friendly solution.

The community-driven Accompanist library, maintained by Google, provides SwipeRefresh composable that makes implementing this pattern seamless.

Setting Up Pull-to-Refresh with Accompanist

Before jumping into code, you need to add the Accompanist SwipeRefresh library dependency to your build.gradle:


dependencies {
    implementation "com.google.accompanist:accompanist-swiperefresh:0.30.1"
}

Note: Check for the latest version at the Accompanist GitHub page.

Basic Usage Example

Here’s a minimal example demonstrating the pull-to-refresh layout containing a list of items.


@Composable
fun SimplePullToRefresh() {
    // State to control refreshing status
    var isRefreshing by remember { mutableStateOf(false) }

    // Sample data list
    val items = remember { mutableStateListOf("Apple", "Banana", "Cherry") }

    // SwipeRefresh Composable wraps the content to provide pull-to-refresh functionality
    SwipeRefresh(
        state = rememberSwipeRefreshState(isRefreshing),
        onRefresh = {
            isRefreshing = true
            // Simulate refreshing data
            LaunchedEffect(Unit) {
                delay(2000)
                // For demo: just add a new item
                items.add("New Fruit ${items.size + 1}")
                isRefreshing = false
            }
        }
    ) {
        LazyColumn {
            items(items) { item ->
                Text(
                    text = item,
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp),
                    style = MaterialTheme.typography.bodyLarge
                )
                Divider()
            }
        }
    }
}

In this snippet:

  • SwipeRefresh wraps the list to provide the pull-to-refresh UI and gesture.
  • isRefreshing state indicates if a refresh is currently happening, controlling the refresh indicator.
  • onRefresh lambda triggers when the user performs the swipe gesture; here, we simulate a data fetch with a delay.
  • We update the list to add new content and then hide the refresh indicator by toggling isRefreshing.

Deep Dive: Understanding SwipeRefresh Components

Let’s break down key elements of implementing pull-to-refresh in Compose using Accompanist:

1. SwipeRefresh Composable

The root composable that enables the swipe gesture detection combined with a refresh indicator. It accepts two main parameters:

  • state: A SwipeRefreshState object representing whether a refresh is in progress.
  • onRefresh: A lambda invoked when the swipe-to-refresh gesture is detected.

2. rememberSwipeRefreshState

This helper function creates and remembers a SwipeRefreshState object tied to your composable lifecycle. You feed your Boolean isRefreshing state here, so the UI can recompute properly when refreshing starts or ends.

3. Refresh Indicator and Content

Inside SwipeRefresh, place any composable content you want to be refreshable, most commonly scrollable lists such as LazyColumn or LazyRow. The pull-to-refresh gesture triggers the indicator to appear above the content automatically.

Handling Real-world Scenarios

In production apps, the refresh action usually involves network operations, database updates, or other asynchronous work. Let’s look at a more realistic example integrating with a ViewModel to manage data fetching properly.


// ViewModel for managing UI state and refreshing data
class FruitsViewModel : ViewModel() {
    private val _fruits = mutableStateListOf("Apple", "Banana", "Cherry")
    val fruits: List = _fruits

    // StateFlow to indicate loading state
    private val _isRefreshing = MutableStateFlow(false)
    val isRefreshing: StateFlow = _isRefreshing

    // Simulate data refresh
    fun refresh() {
        viewModelScope.launch {
            _isRefreshing.value = true
            delay(2000) // simulate network delay
            _fruits.add("New Fruit ${_fruits.size + 1}")
            _isRefreshing.value = false
        }
    }
}

@Composable
fun FruitsScreen(viewModel: FruitsViewModel = viewModel()) {
    val fruits by remember { derivedStateOf { viewModel.fruits } }
    val isRefreshing by viewModel.isRefreshing.collectAsState()

    SwipeRefresh(
        state = rememberSwipeRefreshState(isRefreshing),
        onRefresh = { viewModel.refresh() }
    ) {
        LazyColumn {
            items(fruits) { fruit ->
                Text(
                    text = fruit,
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp),
                    style = MaterialTheme.typography.bodyLarge
                )
                Divider()
            }
        }
    }
}

This approach cleanly separates UI concerns from business logic, leveraging ViewModel and StateFlow for reactive state updates. Jetpack Compose’s seamless state handling ensures your UI updates automatically once your data changes.

Customizing the Refresh Indicator

If you want to customize the appearance or behavior of the refresh indicator, SwipeRefresh has an optional indicator parameter where you can supply your own implementation.

Here’s a simple example to customize the indicator’s color:


SwipeRefresh(
    state = rememberSwipeRefreshState(isRefreshing),
    onRefresh = { /* refresh logic */ },
    indicator = { state, triggerDistance ->
        SwipeRefreshIndicator(
            state = state,
            refreshTriggerDistance = triggerDistance,
            contentColor = MaterialTheme.colorScheme.primary
        )
    }
) {
    // Content here
}

This enables you to align the refresh indicator’s look and feel with your app’s theme or branding.

Best Practices for Pull-to-Refresh in Compose

  • Avoid long blockages: The onRefresh lambda should trigger asynchronous work to avoid freezing the UI.
  • Use proper state management: Keep refresh states autosynced with your data sources (ViewModel, Repository) to ensure UI stays consistent.
  • Test across devices: Make sure the pull-to-refresh gesture works well and doesn’t conflict with other scroll gestures.
  • Use clear indicators: Always signal to the user when refreshing is in progress and when complete.

Conclusion

Jetpack Compose offers a modern, efficient, and highly customizable way to implement pull-to-refresh functionality in your Android apps. Thanks to the Accompanist library’s SwipeRefresh composable, adding this common UI pattern is straightforward and embraces Compose’s declarative style perfectly.

With its clean state management and intuitive API, pull-to-refresh in Compose helps you deliver a polished user experience that your users will appreciate. Whether you’re building social feeds, data-heavy lists, or any dynamic content screens, mastering this feature will be a valuable tool in your Android development toolkit.

Start integrating pull-to-refresh today and elevate your Compose UI designs!

Comments

Popular posts from this blog

How to Download Apk file from Url and Install Programmatically

In this post we learn about download apk file from server or website and then install it Programmatically in Phone. Sometimes we have to download external apk file from server and then install if downloading successfully finished.For this we use AsyncTask class  for background process. So here is Code Snippet for this task.Lets Start :- Before this we have to add these Permissions in Manifest.xml file : <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> DownloadNewVersion.java class DownloadNewVersion extends AsyncTask<String,Integer,Boolean> { @Override protected void onPreExecute() { super.onPreExecute(); bar = new ProgressDialog(getActivity()); bar.setCancelable(false); bar.setMessage("Downl...

Working with Android 11 (Changes and Security Features)

 Hello everyone , I am here with new article which is hot topic nowadays "Android 11" .The stable version of Android.  Android 11 is the eleventh major release and 18th version of Android, the mobile operating system developed by the Open Handset Alliance led by Google. It was released on September 8, 2020.It is comes with many security features and other features as well . And it is now compulsory in play store  to upload new apps with API lavel 30 which is compatible with Android 11 and from November onwards old apps also have to update with API 30 .Some other guidelines you can check out from here . Play Store Guidelines So its clear that we have to update our apps with API level 30 .But Android 11 comes with some changes as well which we have to do in our projects. For example from Android developer site "Android 11 (API level 30) further enhances the platform, giving better protection to app and user data on external storage. ". Scoped storage enforcement: Apps...

How to sort a list in ascending order and Add header by the first letter in the RecyclerView in Android

Hello Forks ! Hope you are doing well. In this tutorial we will understand how to sort a list of data, it could be any data like bank names , places names etc. in ascending order with a header which grouped the same type of data under it. For example we have a list of banks data and we want to group all banks starting with 'A' character in single unit and so on. So without taking too much time  let's move to the coding part and understand how we can achieve this. Step 1 : Create a Model class Named Bank.java public class Bank { private String name; public Bank (String name) { this .name = name; } public String getName () { return name; } } Step 2 : Sort the List of Banks with this Method import java.util.Collections; import java.util.Comparator; import java.util.List; public void sortBankList (List<Bank> bankList) { Collections.sort(bankList, new Comparator <Bank>() { @Override pub...