In this post, I will show you how to use the PullRefreshIndicator
composable to add Pull to refresh behavior to your list.
Here is a GIF of what we are going to create:
Prequesties
In your app-level build.gradle
add the following to your dependencies {...}
:
dependencies {
// that is the library that contains the PullRefreshIndicator composable
implementation("androidx.compose.material:material:1.5.3") // or later version
// These are used to use viewmodel and flows with Compose
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
}
You only need implementation("androidx.compose.material:material:1.5.3")
, but in my code, I will use ViewModel and will collect a Flow as a state so the other libraries added for this purpose
Implementation
Create PullRefreshState state
First, you will need to create a remembered PullRefreshState
object:
val pullRefreshState = rememberPullRefreshState(
refreshing = mainViewModel.isRefreshing,
onRefresh = {
mainViewModel.getItems()
})
MainActivity.ktLet’s break it down:
- The
rememberPullRefreshState
is a function from the compose material library that helps you create and remember the object. refreshing
: A boolean representing whether a refresh is currently occurring. When it is true, the indicator will be visible and animating. In our case, we are reading a mutable state in themainViewModel
as you will see lateronRefresh
: a callback that will fire when a user pulls down and triggers a refresh, in this case, we are calling a function onmainViewModel
to get the items
Adding the PullRefreshIndicator Composable
Next, you need to put your list along with the List inside a Box
composable as follows:
// Box is used to place the pull to refresh indicator on top of the content
Box(Modifier.pullRefresh(pullRefreshState)) {
// Your list or content
ItemsList(data = items, modifier = Modifier.fillMaxSize())
// the pull to refresh indicator
PullRefreshIndicator(
refreshing = mainViewModel.isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
contentColor = MaterialTheme.colorScheme.primary,
)
}
MainActivity.ktLet’s break it down:
Modifier.pullRefresh(pullRefreshState)
is a nested scroll modifier that provides scroll events to the state.ItemsList
is a composable that containsLazyColumn
which shows our items.PullRefreshIndicator
is the composable of the pull-to-refresh component.refreshing
A Boolean represents whether a refresh is occurring.modifier = Modifier.align(Alignment.TopCenter)
this will make the indicator show on the top center, you can change it as you like.
Here is the Full code:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.codinginsideout.pulltorefresh.ui.theme.PullToRefreshTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PullToRefreshTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
HomeScreen(modifier = Modifier.fillMaxSize())
}
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun HomeScreen(
modifier: Modifier = Modifier,
mainViewModel: MainViewModel = viewModel()
) {
val pullRefreshState = rememberPullRefreshState(
refreshing = mainViewModel.isRefreshing,
onRefresh = {
mainViewModel.getItems()
})
val items by mainViewModel.items.collectAsStateWithLifecycle(initialValue = emptyList())
// Box is used to place the pull to refresh indicator on top of the content
Box(modifier.pullRefresh(pullRefreshState)) {
// Your list or content
ItemsList(data = items, modifier = Modifier.fillMaxSize())
// the pull to refresh indicator
PullRefreshIndicator(
refreshing = mainViewModel.isRefreshing,
state = pullRefreshState,
Modifier.align(Alignment.TopCenter),
contentColor = MaterialTheme.colorScheme.primary,
)
}
}
@Composable
fun ItemsList(data: List<String>, modifier: Modifier = Modifier) {
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(data) {
Card(modifier = Modifier.fillMaxWidth()) {
Text(text = it, modifier = Modifier.padding(16.dp))
}
}
}
}
MainActivity.ktThe Data Layer
That is just our ViewModel. In a real app, you would have a repository, models, and UiState object.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class MainViewModel : ViewModel() {
var isRefreshing: Boolean by mutableStateOf(false)
private set
private val _items: MutableStateFlow<List<String>> = MutableStateFlow(emptyList())
val items: Flow<List<String>> = _items.asStateFlow()
init {
getItems()
}
fun getItems() {
viewModelScope.launch {
isRefreshing = true
/*
Here you would get data from an api or database
but for this example we will just generate some random data
*/
val items = (0..100).shuffled().map { "Item $it" }
delay(1000) // simulate network
_items.value = items
isRefreshing = false
}
}
}
MainViewModel.ktAs you can see the isRefreshing
is a mutable state which is very important so changing it triggers the PullRefreshIndicator to recompose and show on the screen. The getItems
is nothing special. It just simulates fetching data.
Conclusion
In this post, we have learned how to use the PullRefreshIndicator
composable to add a pull-to-refresh feature to our list in a Jetpack Compose app. We have seen the necessary steps and code snippets to implement this behavior.
First, we added the required dependencies to our build.gradle
file. Then, we created a PullRefreshState
object using the rememberPullRefreshState
function, which keeps track of the refresh state and provides a callback for when a refresh is triggered.
Next, we wrapped our list and the PullRefreshIndicator
in a Box
composable, using the Modifier.pullRefresh
function to handle scroll events and pass them to the state. The PullRefreshIndicator
component takes the refreshing state, the pull refresh state object, and other modifiers to customize its appearance.
We also provided the full code for the example, including the MainActivity
and the HomeScreen
composable, which uses the ItemsList
component to display the list of items. Additionally, we included the MainViewModel
that handles the refresh logic and provides the data to the UI.
Remember, these code snippets are just a starting point, and in real-world scenarios, you would have more complex data layers with repositories and models.
By following the steps outlined in this post, you can easily add the pull-to-refresh functionality to your Jetpack Compose app and provide a better user experience. So go ahead and give it a try in your own projects!
You can find the full code in this GitHub repository.
Thank you! I was struggling with this