{% setvar book_path %}/reference/kotlin/androidx/_book.yaml{% endsetvar %} {% include "_shared/_reference-head-tags.html" %}

androidx.compose.material3.pulltorefresh

Interfaces

PullToRefreshState

The state that is associated with a PullToRefreshContainer.

Cmn

Objects

PullToRefreshDefaults

Contains the default values for PullToRefreshContainer

Cmn

Top-level functions summary

Unit
@Composable
@ExperimentalMaterial3Api
PullToRefreshContainer(
    state: PullToRefreshState,
    modifier: Modifier,
    indicator: @Composable (PullToRefreshState) -> Unit,
    shape: Shape,
    containerColor: Color,
    contentColor: Color
)

Material Design pull-to-refresh indicator

Cmn
PullToRefreshState
@ExperimentalMaterial3Api
PullToRefreshState(
    positionalThresholdPx: Float,
    initialRefreshing: Boolean,
    enabled: () -> Boolean
)

Creates a PullToRefreshState.

Cmn
PullToRefreshState
@Composable
@ExperimentalMaterial3Api
rememberPullToRefreshState(
    positionalThreshold: Dp,
    enabled: () -> Boolean
)

Create and remember the default PullToRefreshState.

Cmn

Top-level functions

PullToRefreshContainer

@Composable
@ExperimentalMaterial3Api
fun PullToRefreshContainer(
    state: PullToRefreshState,
    modifier: Modifier = Modifier,
    indicator: @Composable (PullToRefreshState) -> Unit = { pullRefreshState -> Indicator(state = pullRefreshState) },
    shape: Shape = PullToRefreshDefaults.shape,
    containerColor: Color = PullToRefreshDefaults.containerColor,
    contentColor: Color = PullToRefreshDefaults.contentColor
): Unit

Material Design pull-to-refresh indicator

A pull-to-refresh container contains a progress indicator to indicate a users drag progress towards triggering a refresh. On a refresh the progress indicator inside this container is indeterminate.

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ListItem
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
import androidx.compose.material3.pulltorefresh.PullToRefreshState
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll

var itemCount by remember { mutableStateOf(15) }
val state = rememberPullToRefreshState()
if (state.isRefreshing) {
    LaunchedEffect(true) {
        // fetch something
        delay(1500)
        itemCount += 5
        state.endRefresh()
    }
}
Box(Modifier.nestedScroll(state.nestedScrollConnection)) {
    LazyColumn(Modifier.fillMaxSize()) {
        if (!state.isRefreshing) {
            items(itemCount) {
                ListItem({ Text(text = "Item ${itemCount - it}") })
            }
        }
    }
    PullToRefreshContainer(
        modifier = Modifier.align(Alignment.TopCenter),
        state = state,
    )
}

A custom state implementation can be initialized like this

import androidx.compose.animation.core.animate
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ListItem
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
import androidx.compose.material3.pulltorefresh.PullToRefreshState
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.Velocity

var itemCount by remember { mutableStateOf(15) }
val state = remember {
    object : PullToRefreshState {
        override val positionalThreshold: Float = 100f
        override val progress get() = verticalOffset / positionalThreshold
        override var verticalOffset: Float by mutableFloatStateOf(0f)
        override var isRefreshing: Boolean by mutableStateOf(false)

        override fun startRefresh() {
            isRefreshing = true
        }
        override fun endRefresh() {
            isRefreshing = false
        }

        // Provide logic for the PullRefreshContainer to consume scrolls within a nested scroll
        override var nestedScrollConnection: NestedScrollConnection =
            object : NestedScrollConnection {
                // Pre and post scroll provide the drag logic for PullRefreshContainer.
                override fun onPreScroll(
                    available: Offset,
                    source: NestedScrollSource,
                ): Offset = when {
                    source == NestedScrollSource.Drag && available.y < 0 -> {
                        // Swiping up
                        val y = if (isRefreshing) 0f else {
                            val newOffset = (verticalOffset + available.y).coerceAtLeast(0f)
                            val dragConsumed = newOffset - verticalOffset
                            verticalOffset = newOffset
                            dragConsumed
                        }
                        Offset(0f, y)
                    }

                    else -> Offset.Zero
                }

                override fun onPostScroll(
                    consumed: Offset,
                    available: Offset,
                    source: NestedScrollSource
                ): Offset = when {
                    source == NestedScrollSource.Drag && available.y > 0 -> {
                        // Swiping Down
                        val y = if (isRefreshing) 0f else {
                            val newOffset = (verticalOffset + available.y).coerceAtLeast(0f)
                            val dragConsumed = newOffset - verticalOffset
                            verticalOffset = newOffset
                            dragConsumed
                        }
                        Offset(0f, y)
                    }

                    else -> Offset.Zero
                }

                // Pre-Fling is called when the user releases a drag. This is where you can provide
                // refresh logic, and verify exceeding positional threshold.
                override suspend fun onPreFling(available: Velocity): Velocity {
                    if (isRefreshing) return Velocity.Zero
                    if (verticalOffset > positionalThreshold) {
                        startRefresh()
                        itemCount += 5
                        endRefresh()
                    }
                    animate(verticalOffset, 0f) { value, _ ->
                        verticalOffset = value
                    }
                    val consumed = when {
                        verticalOffset == 0f -> 0f
                        available.y < 0f -> 0f
                        else -> available.y
                    }
                    return Velocity(0f, consumed)
                }
            }
    }
}

Box(Modifier.nestedScroll(state.nestedScrollConnection)) {
    LazyColumn(Modifier.fillMaxSize()) {
        if (!state.isRefreshing) {
            items(itemCount) {
                ListItem({ Text(text = "Item ${itemCount - it}") })
            }
        }
    }
    PullToRefreshContainer(
        modifier = Modifier.align(Alignment.TopCenter),
        state = state,
    )
}

Scaling behavior can be implemented like this

import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ListItem
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
import androidx.compose.material3.pulltorefresh.PullToRefreshState
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll

var itemCount by remember { mutableStateOf(15) }
val state = rememberPullToRefreshState()
if (state.isRefreshing) {
    LaunchedEffect(true) {
        // fetch something
        delay(1500)
        itemCount += 5
        state.endRefresh()
    }
}
val scaleFraction = if (state.isRefreshing) 1f else
    LinearOutSlowInEasing.transform(state.progress).coerceIn(0f, 1f)

Box(Modifier.nestedScroll(state.nestedScrollConnection)) {
    LazyColumn(Modifier.fillMaxSize()) {
        if (!state.isRefreshing) {
            items(itemCount) {
                ListItem({ Text(text = "Item ${itemCount - it}") })
            }
        }
    }
    PullToRefreshContainer(
        modifier = Modifier
            .align(Alignment.TopCenter)
            .graphicsLayer(scaleX = scaleFraction, scaleY = scaleFraction),
        state = state,
    )
}
Parameters
state: PullToRefreshState

the state of this PullToRefreshContainer

modifier: Modifier = Modifier

the Modifier to be applied to this container

indicator: @Composable (PullToRefreshState) -> Unit = { pullRefreshState -> Indicator(state = pullRefreshState) }

The indicator placed inside of the PullToRefreshContainer. Has access to state

shape: Shape = PullToRefreshDefaults.shape

the Shape of this container

containerColor: Color = PullToRefreshDefaults.containerColor

the color of this container

contentColor: Color = PullToRefreshDefaults.contentColor

the color of the progress indicator

PullToRefreshState

@ExperimentalMaterial3Api
fun PullToRefreshState(
    positionalThresholdPx: Float,
    initialRefreshing: Boolean = false,
    enabled: () -> Boolean = { true }
): PullToRefreshState

Creates a PullToRefreshState.

Note that in most cases, you are advised to use rememberPullToRefreshState when in composition.

Parameters
positionalThresholdPx: Float

The positional threshold, in pixels, in which a refresh is triggered

initialRefreshing: Boolean = false

The initial refreshing value of PullToRefreshState

enabled: () -> Boolean = { true }

a callback used to determine whether scroll events are to be handled by this PullToRefreshState

rememberPullToRefreshState

@Composable
@ExperimentalMaterial3Api
fun rememberPullToRefreshState(
    positionalThreshold: Dp = PullToRefreshDefaults.PositionalThreshold,
    enabled: () -> Boolean = { true }
): PullToRefreshState

Create and remember the default PullToRefreshState.

Parameters
positionalThreshold: Dp = PullToRefreshDefaults.PositionalThreshold

The positional threshold when a refresh would be triggered

enabled: () -> Boolean = { true }

a callback used to determine whether scroll events are to be handled by this PullToRefreshState