# PullToRefreshBox

Common/All

## Functions summary

|---|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `@https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/PullToRefreshBox.composable#PullToRefreshBox(kotlin.Boolean,kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.material3.pulltorefresh.PullToRefreshState,androidx.compose.ui.Alignment,kotlin.Function1,kotlin.Boolean,androidx.compose.ui.unit.Dp,kotlin.Function1)( isRefreshing: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html, onRefresh: () -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html, modifier: https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier, state: https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/PullToRefreshState, contentAlignment: https://developer.android.com/reference/kotlin/androidx/compose/ui/Alignment, indicator: @https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/BoxScope.() -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html, enabled: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html, threshold: https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Dp, content: @https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/BoxScope.() -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html )` `https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/PullToRefreshBox.composable#PullToRefreshBox(kotlin.Boolean,kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.material3.pulltorefresh.PullToRefreshState,androidx.compose.ui.Alignment,kotlin.Function1,kotlin.Boolean,androidx.compose.ui.unit.Dp,kotlin.Function1)` is a container that expects a scrollable layout as content and adds gesture support for manually refreshing when the user swipes downward at the beginning of the content. | Cmn |

## Functions

### PullToRefreshBox

Cmn Artifact: [androidx.compose.material3:material3](https://developer.android.com/jetpack/androidx/releases/compose-material3) [View Source](https://cs.android.com/search?q=file:androidx/compose/material3/pulltorefresh/PullToRefresh.kt+function:PullToRefreshBox)

```
@Composable
fun PullToRefreshBox(
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    modifier: Modifier = Modifier,
    state: PullToRefreshState = rememberPullToRefreshState(),
    contentAlignment: Alignment = Alignment.TopStart,
    indicator: @Composable BoxScope.() -> Unit = {
        Indicator(
            modifier = Modifier.align(Alignment.TopCenter),
            isRefreshing = isRefreshing,
            state = state,
        )
    },
    enabled: Boolean = true,
    threshold: Dp = PullToRefreshDefaults.PositionalThreshold,
    content: @Composable BoxScope.() -> Unit
): Unit
```

`https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/PullToRefreshBox.composable#PullToRefreshBox(kotlin.Boolean,kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.material3.pulltorefresh.PullToRefreshState,androidx.compose.ui.Alignment,kotlin.Function1,kotlin.Boolean,androidx.compose.ui.unit.Dp,kotlin.Function1)` is a container that expects a scrollable layout as content and adds gesture support for manually refreshing when the user swipes downward at the beginning of the content. By default, it uses `https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/PullToRefreshDefaults#Indicator(androidx.compose.material3.pulltorefresh.PullToRefreshState,kotlin.Boolean,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp)` as the refresh indicator, but you may also choose to set your own indicator or use `https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/PullToRefreshDefaults#LoadingIndicator(androidx.compose.material3.pulltorefresh.PullToRefreshState,kotlin.Boolean,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp)`.

```kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshState
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier

var itemCount by remember { mutableIntStateOf(15) }
var isRefreshing by remember { mutableStateOf(false) }
val state = rememberPullToRefreshState()
val coroutineScope = rememberCoroutineScope()
val onRefresh: () -> Unit = {
    isRefreshing = true
    coroutineScope.launch {
        delay(5000)
        itemCount += 5
        isRefreshing = false
    }
}

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("Title") },
            // Provide an accessible alternative to trigger refresh.
            actions = {
                IconButton(onClick = onRefresh) {
                    Icon(Icons.Filled.Refresh, "Trigger Refresh")
                }
            },
        )
    }
) {
    PullToRefreshBox(
        modifier = Modifier.padding(it),
        state = state,
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(itemCount) { ListItem { Text(text = "Item ${itemCount - it}") } }
        }
    }
}
```

Using a `https://developer.android.com/reference/kotlin/androidx/compose/material3/LoadingIndicator.composable#LoadingIndicator(kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,kotlin.collections.List)` as the `https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/PullToRefreshBox.composable#PullToRefreshBox(kotlin.Boolean,kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.material3.pulltorefresh.PullToRefreshState,androidx.compose.ui.Alignment,kotlin.Function1,kotlin.Boolean,androidx.compose.ui.unit.Dp,kotlin.Function1)` indicator can be done like this

```kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshState
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

var itemCount by remember { mutableIntStateOf(15) }
var isRefreshing by remember { mutableStateOf(false) }
val state = rememberPullToRefreshState()
val coroutineScope = rememberCoroutineScope()
val onRefresh: () -> Unit = {
    isRefreshing = true
    coroutineScope.launch {
        delay(5000)
        itemCount += 5
        isRefreshing = false
    }
}

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("Title") },
            // Provide an accessible alternative to trigger refresh.
            actions = {
                IconButton(onClick = onRefresh) {
                    Icon(Icons.Filled.Refresh, "Trigger Refresh")
                }
            },
        )
    }
) {
    PullToRefreshBox(
        modifier = Modifier.padding(it),
        state = state,
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        indicator = {
            PullToRefreshDefaults.LoadingIndicator(
                state = state,
                isRefreshing = isRefreshing,
                modifier = Modifier.align(Alignment.TopCenter),
            )
        },
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(itemCount) { ListItem { Text(text = "Item ${itemCount - it}") } }
        }
    }
}
```

View models can be used as source as truth as shown in

```kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope

val viewModel = remember {
    object : ViewModel() {
        private val refreshRequests = Channel<Unit>(1)
        var isRefreshing by mutableStateOf(false)
            private set

        var itemCount by mutableIntStateOf(15)
            private set

        init {
            viewModelScope.launch {
                for (r in refreshRequests) {
                    isRefreshing = true
                    try {
                        itemCount += 5
                        delay(5000) // simulate doing real work
                    } finally {
                        isRefreshing = false
                    }
                }
            }
        }

        fun refresh() {
            refreshRequests.trySend(Unit)
        }
    }
}

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("Title") },
            // Provide an accessible alternative to trigger refresh.
            actions = {
                IconButton(
                    enabled = !viewModel.isRefreshing,
                    onClick = { viewModel.refresh() },
                ) {
                    Icon(Icons.Filled.Refresh, "Trigger Refresh")
                }
            },
        )
    }
) {
    PullToRefreshBox(
        modifier = Modifier.padding(it),
        isRefreshing = viewModel.isRefreshing,
        onRefresh = { viewModel.refresh() },
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            if (!viewModel.isRefreshing) {
                items(viewModel.itemCount) {
                    ListItem { Text(text = "Item ${viewModel.itemCount - it}") }
                }
            }
        }
    }
}
```

A custom state implementation can be initialized like this

```kotlin
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier

var itemCount by remember { mutableIntStateOf(15) }
var isRefreshing by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val onRefresh: () -> Unit = {
    isRefreshing = true
    coroutineScope.launch {
        // fetch something
        delay(5000)
        itemCount += 5
        isRefreshing = false
    }
}

val state = remember {
    object : PullToRefreshState {
        private val anim = Animatable(0f, Float.VectorConverter)

        override val distanceFraction
            get() = anim.value

        override val isAnimating: Boolean
            get() = anim.isRunning

        override suspend fun animateToThreshold() {
            anim.animateTo(1f, spring(dampingRatio = Spring.DampingRatioHighBouncy))
        }

        override suspend fun animateToHidden() {
            anim.animateTo(0f)
        }

        override suspend fun snapTo(targetValue: Float) {
            anim.snapTo(targetValue)
        }
    }
}

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("TopAppBar") },
            // Provide an accessible alternative to trigger refresh.
            actions = {
                IconButton(onClick = onRefresh) {
                    Icon(Icons.Filled.Refresh, "Trigger Refresh")
                }
            },
        )
    }
) {
    PullToRefreshBox(
        modifier = Modifier.padding(it),
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        state = state,
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            if (!isRefreshing) {
                items(itemCount) { ListItem { Text(text = "Item ${itemCount - it}") } }
            }
        }
    }
}
```

Scaling behavior can be implemented like this

```kotlin
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshState
import androidx.compose.material3.pulltorefresh.pullToRefresh
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer

var itemCount by remember { mutableIntStateOf(15) }
var isRefreshing by remember { mutableStateOf(false) }
val state = rememberPullToRefreshState()
val coroutineScope = rememberCoroutineScope()
val onRefresh: () -> Unit = {
    isRefreshing = true
    coroutineScope.launch {
        // fetch something
        delay(5000)
        itemCount += 5
        isRefreshing = false
    }
}

val scaleFraction = {
    if (isRefreshing) 1f
    else LinearOutSlowInEasing.transform(state.distanceFraction).coerceIn(0f, 1f)
}

Scaffold(
    modifier =
        Modifier.pullToRefresh(
            state = state,
            isRefreshing = isRefreshing,
            onRefresh = onRefresh,
        ),
    topBar = {
        TopAppBar(
            title = { Text("TopAppBar") },
            // Provide an accessible alternative to trigger refresh.
            actions = {
                IconButton(onClick = onRefresh) {
                    Icon(Icons.Filled.Refresh, "Trigger Refresh")
                }
            },
        )
    },
) {
    Box(Modifier.padding(it)) {
        LazyColumn(Modifier.fillMaxSize()) {
            if (!isRefreshing) {
                items(itemCount) { ListItem { Text(text = "Item ${itemCount - it}") } }
            }
        }
        Box(
            Modifier.align(Alignment.TopCenter).graphicsLayer {
                scaleX = scaleFraction()
                scaleY = scaleFraction()
            }
        ) {
            PullToRefreshDefaults.Indicator(state = state, isRefreshing = isRefreshing)
        }
    }
}
```

Custom indicators with default transforms can be seen in

```kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshState
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

var itemCount by remember { mutableIntStateOf(15) }
var isRefreshing by remember { mutableStateOf(false) }
val state = rememberPullToRefreshState()
val coroutineScope = rememberCoroutineScope()
val onRefresh: () -> Unit = {
    isRefreshing = true
    coroutineScope.launch {
        delay(1500)
        itemCount += 5
        isRefreshing = false
    }
}

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("Title") },
            // Provide an accessible alternative to trigger refresh.
            actions = {
                IconButton(onClick = onRefresh) {
                    Icon(Icons.Filled.Refresh, "Trigger Refresh")
                }
            },
        )
    }
) {
    PullToRefreshBox(
        modifier = Modifier.padding(it),
        state = state,
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        indicator = {
            PullToRefreshDefaults.IndicatorBox(
                state = state,
                isRefreshing = isRefreshing,
                modifier = Modifier.align(Alignment.TopCenter),
                elevation = 0.dp,
            ) {
                if (isRefreshing) {
                    CircularProgressIndicator()
                } else {
                    CircularProgressIndicator(
                        progress = { state.distanceFraction },
                        trackColor = ProgressIndicatorDefaults.circularIndeterminateTrackColor,
                    )
                }
            }
        },
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(itemCount) { ListItem { Text(text = "Item ${itemCount - it}") } }
        }
    }
}
```

| Parameters |
|---|---|
| `isRefreshing: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html` | whether a refresh is occurring |
| `onRefresh: () -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | callback invoked when the user gesture crosses the threshold, thereby requesting a refresh. |
| `modifier: https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier = Modifier` | the `https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier` to be applied to this container |
| `state: https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/PullToRefreshState = rememberPullToRefreshState()` | the state that keeps track of distance pulled |
| `contentAlignment: https://developer.android.com/reference/kotlin/androidx/compose/ui/Alignment = Alignment.TopStart` | The default alignment inside the Box. |
| `indicator: @https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/BoxScope.() -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html = { Indicator( modifier = Modifier.align(Alignment.TopCenter), isRefreshing = isRefreshing, state = state, ) }` | the indicator that will be drawn on top of the content when the user begins a pull or a refresh is occurring |
| `enabled: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html = true` | whether nested scroll events should be consumed by this component |
| `threshold: https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Dp = PullToRefreshDefaults.PositionalThreshold` | how much distance can be scrolled down before `https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/package-summary#PullToRefreshBox(kotlin.Boolean,kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.material3.pulltorefresh.PullToRefreshState,androidx.compose.ui.Alignment,kotlin.Function1,kotlin.Boolean,androidx.compose.ui.unit.Dp,kotlin.Function1)` is invoked |
| `content: @https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/BoxScope.() -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | the content of the pull refresh container, typically a scrollable layout such as `https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyColumn.composable#LazyColumn(androidx.compose.ui.Modifier,androidx.compose.foundation.lazy.LazyListState,androidx.compose.foundation.layout.PaddingValues,kotlin.Boolean,androidx.compose.foundation.layout.Arrangement.Vertical,androidx.compose.ui.Alignment.Horizontal,androidx.compose.foundation.gestures.FlingBehavior,kotlin.Boolean,androidx.compose.foundation.OverscrollEffect,kotlin.Function1)` or a layout using `https://developer.android.com/reference/kotlin/androidx/compose/foundation/verticalScroll.modifier#(androidx.compose.ui.Modifier).verticalScroll(androidx.compose.foundation.ScrollState,kotlin.Boolean,androidx.compose.foundation.gestures.FlingBehavior,kotlin.Boolean)` |