Jetpack Compose: Swipe to Refresh

One of the most common UI patterns in mobile apps is the “swipe-to-refresh” functionality, which allows users to pull down on a list or grid to refresh its content. In Jetpack Compose, implementing this feature is straightforward thanks to the androidx.compose.material:material library. This blog post will guide you through implementing swipe-to-refresh in Jetpack Compose with practical code examples and best practices.

What is Swipe to Refresh?

Swipe-to-refresh is a user interface pattern where the user swipes down (or pulls) on a view, typically a list or grid, to initiate a refresh of the content displayed. This provides a quick and intuitive way for users to get the latest data in an application.

Why Use Swipe to Refresh?

  • Improved User Experience: Provides a quick and intuitive way for users to refresh content.
  • Real-time Updates: Ensures users always have access to the latest data.
  • Visual Feedback: Offers immediate visual feedback that content is being refreshed.

Implementing Swipe to Refresh in Jetpack Compose

To implement swipe-to-refresh in Jetpack Compose, we’ll use the SwipeRefresh composable from the androidx.compose.material:material library.

Step 1: Add Dependency

First, ensure you have the necessary dependency in your build.gradle file:

dependencies {
    implementation "androidx.compose.material:material:1.6.0" // or a newer version
    implementation "androidx.compose.material:material-icons-core:1.6.0"
    implementation "androidx.compose.material:material-icons-extended:1.6.0"
    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.2.0"
    implementation("androidx.compose.ui:ui:1.6.0")
}

Step 2: Basic Implementation of SwipeRefresh

Here’s how to implement a basic swipe-to-refresh in Jetpack Compose:


import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay

@Composable
fun SwipeRefreshExample() {
    var refreshing by remember { mutableStateOf(false) }

    LaunchedEffect(refreshing) {
        if (refreshing) {
            delay(3000) // Simulate a network request
            refreshing = false
        }
    }

    SwipeRefresh(
        state = rememberSwipeRefreshState(isRefreshing = refreshing),
        onRefresh = { refreshing = true },
    ) {
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "Pull down to refresh!")
            Spacer(modifier = Modifier.height(16.dp))
            if (refreshing) {
                CircularProgressIndicator()
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun SwipeRefreshExamplePreview() {
    SwipeRefreshExample()
}

In this example:

  • SwipeRefresh composable wraps the content you want to be refreshable.
  • refreshing is a state variable that tracks whether the refresh indicator is active.
  • rememberSwipeRefreshState is used to manage the swipe-to-refresh state.
  • onRefresh is a lambda that is called when the user triggers the refresh.
  • Inside the LaunchedEffect, we simulate a network request by using delay, then set refreshing back to false.

Step 3: Integrating with a List or Grid

In real applications, you’ll likely integrate swipe-to-refresh with a LazyColumn (for lists) or LazyVerticalGrid (for grids). Here’s an example with a LazyColumn:


import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay

@Composable
fun SwipeRefreshListExample() {
    var refreshing by remember { mutableStateOf(false) }
    var items by remember { mutableStateOf((1..10).toList()) }

    LaunchedEffect(refreshing) {
        if (refreshing) {
            delay(2000) // Simulate loading data
            items = (1..10).map { "Item $it - Refreshed" }
            refreshing = false
        }
    }

    SwipeRefresh(
        state = rememberSwipeRefreshState(isRefreshing = refreshing),
        onRefresh = { refreshing = true },
    ) {
        LazyColumn(
            modifier = Modifier.fillMaxSize(),
            contentPadding = PaddingValues(16.dp)
        ) {
            items(items) { item ->
                Card(
                    modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
                ) {
                    Text(
                        text = item,
                        modifier = Modifier.padding(16.dp)
                    )
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun SwipeRefreshListExamplePreview() {
    SwipeRefreshListExample()
}

In this enhanced example:

  • We use LazyColumn to display a list of items.
  • When the user swipes to refresh, the LaunchedEffect simulates loading new data and updates the list.

Step 4: Customizing the Refresh Indicator

You can customize the appearance of the refresh indicator by modifying properties like indicatorBackgroundColor and indicatorColor.


import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay

@Composable
fun CustomSwipeRefreshExample() {
    var refreshing by remember { mutableStateOf(false) }
    var items by remember { mutableStateOf((1..10).toList()) }

    LaunchedEffect(refreshing) {
        if (refreshing) {
            delay(2000) // Simulate loading data
            items = (1..10).map { "Item $it - Refreshed" }
            refreshing = false
        }
    }

    SwipeRefresh(
        state = rememberSwipeRefreshState(isRefreshing = refreshing),
        onRefresh = { refreshing = true },
        indicator = { state, refreshTriggerDistance ->
            SwipeRefreshIndicator(
                state = state,
                refreshTriggerDistance = refreshTriggerDistance,
                backgroundColor = MaterialTheme.colors.secondary,
                contentColor = MaterialTheme.colors.onSecondary
            )
        }
    ) {
        LazyColumn(
            modifier = Modifier.fillMaxSize(),
            contentPadding = PaddingValues(16.dp)
        ) {
            items(items) { item ->
                Card(
                    modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
                ) {
                    Text(
                        text = item,
                        modifier = Modifier.padding(16.dp)
                    )
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun CustomSwipeRefreshExamplePreview() {
    CustomSwipeRefreshExample()
}
  • Pass the `indicator` lambda, where you create `SwipeRefreshIndicator` to specify customizations like background color and content color

Best Practices for Swipe to Refresh

  • Provide Clear Visual Feedback: Ensure that the refresh indicator is clearly visible and animates smoothly.
  • Handle Errors Gracefully: If refreshing fails, inform the user with an appropriate error message.
  • Avoid Excessive Refreshing: Implement measures to prevent users from excessively refreshing, such as throttling or debouncing.
  • Indicate Loading State: Display a loading indicator while fetching new data to provide continuous feedback.

Conclusion

Swipe-to-refresh is a vital feature for modern mobile applications, providing users with an intuitive way to update content. In Jetpack Compose, it can be easily implemented using the SwipeRefresh composable from the androidx.compose.material:material library. By following the examples and best practices outlined in this guide, you can enhance your app’s user experience with this essential functionality.