Jetpack Compose Swipe To Refresh
A short guide on how to implement swipe-to-refresh functionality along with placeholders when loading items

Today, a lot of apps have data that need to be refreshed at some point. You can refresh data after some time or use sockets to have always up-to-date data, but what if you want functionality that allows users to start refreshing data?
That can be done with a button, but in some cases, a better UX would be swipe-to-refresh. Today, we are gonna implement that using Accompanist
library.
Swipe-to-Refresh
First, let’s add a dependency:
implementation "com.google.accompanist:accompanist-swiperefresh:0.25.1"
Note: Check if there is a newer version of this dependency.
Next is to create a simpleViewModel
which will hold our data and logic around refreshing. Here, items will contain random images and numbers. This is what it looks like:
isRefreshing
is a boolean that we will use in our swipeRefreshState
which we will explain later. items
is just a list of 20 items with random images and numbers.
Now, let’s create our screen:
We are collecting our states and creating swipeRefreshState
with the value of isRefreshing
. This state we are passing to SwipeRefresh
but also we can access its properties isRefreshing
and isSwipeInProgress
if needed. Inside SwipeRefresh
we have a title, current time, and list of items. Every row of items is just showing an image and number.
SwipeRefresh
has three mandatory parameters:
state: SwipeRefreshState
— A state object that can be hoisted to control and observe changes for SwipeRefreshonRefresh: () -> Unit
— Lambda which is invoked when a swipe-to-refresh gesture is completedcontent: @Composable () -> Unit
— The content containing a scroll composable
Some of the interesting optional parameters are:
swipeEnabled: Boolean
— Whether the layout should react to swipe gestures or notrefreshTriggerDistance: Dp
— The minimum swipe distance which would trigger a refreshindicatorAlignment: Alignment
— The alignment of the indicator. Defaults toAlignment.TopCenter
indicatorPadding: PaddingValues
— Content padding for the indicator, to inset the indicator in if requiredindicator: @Composable (state: SwipeRefreshState, refreshTrigger: Dp)
— the indicator that represents the current state. By default, this will use aSwipeRefreshIndicator
For indicator
parameter, you can create your own composable, but the library provides us with SwipeRefreshIndicator
which is a pretty nice composable that we can use.
It requires two parameters:
state: SwipeRefreshState
— The SwipeRefreshState passed into the SwipeRefresh indicator blockrefreshTriggerDistance: Dp
— The minimum swipe distance which would trigger a refresh
Some optional parameters are:
fade: Boolean
— Whether the arrow should fade in/out as it is scrolled in, defaults to truescale: Boolean
— Whether the indicator should scale up/down as it is scrolled in, defaults to falsearrowEnabled: Boolean
— Whether an arrow should be drawn on the indicator, defaults to truebackgroundColor: Color
— The color of the indicator background surface
There are more parameters, but no need to go through all of them. Make sure to check it out in the official documentation, if you want to learn more.
That is all for SwipeRefresh
, now let’s implement placeholders, which is another nice library from the Accompanist.
Placeholder
Usually, the loading of the items is shown by some sort of loading spinner. Another way of showing that items are loading is with placeholders.
Accompanist
created a library that provides us with a modifier for displaying a placeholder. There are actually two placeholder libraries. One is Foundation
and other is Material
. It is recommended that we use Material
but feel free to use whatever you need. There isn’t much difference, APIs are mostly equivalent. In this blog, we are using Material
. So, let’s import it with this command:
implementation "com.google.accompanist:accompanist-placeholder-material:0.25.1"
Note: Check if there is a newer version of this dependency.
Let’s quickly edit MainViewModel
before proceeding with the MainScreen
. Add init
and isLoading
StateFlow
. Also, initialize items
with 20 default RowItems
.
Our ViewModel
looks like this now:
Next is to collect isLoading
in our screen, then to use it for our placeholder. We are adding a new parameter to the Item
composable, childModifier: Modifier
.
As you can see, the library provided placeholder
a modifier. The required parameter is visible: Boolean
which determines if the placeholder should be shown or the content. If visible
is true, instead of the content there would be a placeholder filling the size of the composable on which it is applied.
Optional parameters are:
color: Color
— the color used to draw the placeholder UI. IfColor.Unspecified
is provided, the placeholder will usePlaceholderDefaults.color
shape: Shape
— the desired shape of the placeholder. If null is provided the placeholder will use the small shape set inMaterialTheme.shapes
highlight: PlaceholderHighlight
— the optional highlight animation. There are two pre-created animations for placeholder,fade
andshimmer
placeholderFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float>
— The transition spec to use when fading the placeholder on/off the screen. The boolean parameter defined for the transition is visiblecontentFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float>
— The transition spec to use when fading the content on/off the screen. The boolean parameter defined for the transition is visible
Our MainScreen
now looks like this:
That’s all. I hope you liked it.
You can find all of the source code in my GitHub repo.
Want to Connect?GitHub
Portfolio website
If you want to learn more about Jetpack Compose, take a look at these articles:
- Implement Horizontal and Vertical ViewPager in Jetpack Compose
- Build a Camera Android App in Jetpack Compose Using CameraX
- Jetpack Compose Clean Navigation