Implementing Pull-to-Refresh in Android: A Comprehensive Guide from SwipeRefreshLayout to Jetpack Compose

Nov 23, 2025 · Programming · 6 views · 7.8

Keywords: Android | Pull-to-Refresh | SwipeRefreshLayout | Jetpack Compose | PullToRefreshBox

Abstract: This article provides an in-depth exploration of pull-to-refresh implementation on Android, focusing on the official SwipeRefreshLayout usage including XML layout configuration, Java/Kotlin code implementation, and dependency management. It also compares third-party custom solutions and extends to Jetpack Compose's PullToRefreshBox component, covering basic usage, custom indicators, and advanced animation effects. Through detailed code examples and architectural analysis, the article offers complete pull-to-refresh solutions for developers.

Overview of Pull-to-Refresh Functionality

Pull-to-refresh is a common interaction pattern in modern mobile applications where users trigger content updates by swiping down from the top of the screen. In the Android ecosystem, this functionality has evolved from third-party libraries to official support.

Official SwipeRefreshLayout Implementation

Google provides an official pull-to-refresh solution in the Android Support Library – SwipeRefreshLayout. This component serves as a container view that can wrap any scrollable content view such as ListView, RecyclerView, or ScrollView.

Layout Configuration

In XML layout files, set SwipeRefreshLayout as the parent container of the target view:

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pullToRefresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

Code Implementation

Set up the refresh listener in Activity or Fragment:

protected void onCreate(Bundle savedInstanceState) {
    final SwipeRefreshLayout pullToRefresh = findViewById(R.id.pullToRefresh);
    pullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            refreshData(); // Execute data refresh logic
            pullToRefresh.setRefreshing(false); // End refresh state
        }
    });
}

Dependency Management

Since Android Support Library has migrated to AndroidX, add the dependency in the project's build.gradle file:

implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'

Alternatively, use Android Studio's Refactor &gt; Migrate to AndroidX feature to handle dependency migration automatically.

Third-Party Custom Implementation Solutions

Before the official solution emerged, various custom pull-to-refresh implementations appeared in the developer community. Taking the android-pulltorefresh project on GitHub as an example, it implements custom pull-to-refresh functionality by extending the ListView class.

<com.markupartist.android.widget.PullToRefreshListView
    android:id="@+id/android:list"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent"
/>

This implementation approach provides greater customization space by overriding touch event handling and scrolling logic, but requires developers to handle more underlying details.

Pull-to-Refresh in Jetpack Compose

With the popularity of Jetpack Compose, Android provides a declarative pull-to-refresh solution &ndash; the PullToRefreshBox component.

Basic Usage

Use PullToRefreshBox directly in Compose functions:

@Composable 
fun PullToRefreshBasicSample(
    items: List<String>, 
    isRefreshing: Boolean, 
    onRefresh: () -> Unit, 
    modifier: Modifier = Modifier
) {
    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) { 
                ListItem({ Text(text = it) }) 
            }
        }
    }
}

Custom Indicator

PullToRefreshBox supports fully customizable refresh indicators:

@Composable
fun PullToRefreshCustomIndicatorSample(
    items: List<String>, 
    isRefreshing: Boolean, 
    onRefresh: () -> Unit, 
    modifier: Modifier = Modifier
) {
    val state = rememberPullToRefreshState()
    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier,
        state = state,
        indicator = {
            MyCustomIndicator(
                state = state,
                isRefreshing = isRefreshing,
                modifier = Modifier.align(Alignment.TopCenter)
            )
        }
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) { 
                ListItem({ Text(text = it) }) 
            }
        }
    }
}

@Composable
fun MyCustomIndicator(
    state: PullToRefreshState,
    isRefreshing: Boolean,
    modifier: Modifier = Modifier,
) {
    Box(
        modifier = modifier.pullToRefreshIndicator(
            state = state,
            isRefreshing = isRefreshing,
            threshold = PositionalThreshold,
            onRefresh = { }
        ),
        contentAlignment = Alignment.Center
    ) {
        Crossfade(
            targetState = isRefreshing,
            animationSpec = tween(durationMillis = CROSSFADE_DURATION_MILLIS),
            modifier = Modifier.align(Alignment.Center)
        ) { refreshing ->
            if (refreshing) {
                CircularProgressIndicator(Modifier.size(SPINNER_SIZE))
            } else {
                val distanceFraction = { state.distanceFraction.coerceIn(0f, 1f) }
                Icon(
                    imageVector = Icons.Filled.CloudDownload,
                    contentDescription = "Refresh",
                    modifier = Modifier
                        .size(18.dp)
                        .graphicsLayer {
                            val progress = distanceFraction()
                            this.alpha = progress
                            this.scaleX = progress
                            this.scaleY = progress
                        }
                )
            }
        }
    }
}

Implementation Solution Comparison Analysis

The official SwipeRefreshLayout provides a stable and reliable solution suitable for most traditional Android projects. Third-party custom implementations, while flexible, have higher maintenance costs. Jetpack Compose's PullToRefreshBox represents the future direction, offering better declarative programming experience and animation effects.

When choosing an implementation solution, developers should consider project architecture, team technology stack, and user experience requirements. For new projects, the Jetpack Compose solution is recommended as the priority; for existing projects, SwipeRefreshLayout provides good backward compatibility.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.