Effortlessly Add Pull-to-Refresh to Your Android App with Jetpack Compose
--
Up until recently, when using Jetpack Compose you needed to use the Swipe Refresh Accompanist library to implement pull to refresh. Now, the Accompanist version has been deprecated and Swipe Refresh has morphed to Pull Refresh in Compose Material 1.3.0.
If you are migrating from the old version to the new, the above mentioned Accompanist documentation has a good guide, but follow along below if you are implementing from scratch.
How to add Pull Refresh to your app
The first step is to add the Jetpack Compose Material library to your build.gradle
file.
Note — as of writing there is a bug with the Pull Refresh library where the indicator does not reset to the top of the view after the refresh occurs (and appears to get stuck), to fix this you cannot use the current stable version of the Material library (1.3.1
or Jetpack Compose Bill of Materials (BOM) 2023.01.00
). Instead, use the beta version 1.4.0-beta01
:
If you cannot use the 1.4.0-beta01
version of the Material library, check the bug responses for some suggestions on how to fix the issue.
Keeping track of the refresh
Next, you want to add a isRefreshing
flag to your ViewModel to track when the refresh action has started and finished. You can do this with a Kotlin Flow
as I have below, a LiveData
or simple boolean
variable. Using a Flow
or LiveData
is better as it will enable your UI to react to the change.
You also need a refresh method to call when the pull to refresh happens, this will likely be the same method that initially loads your data. Make sure the last thing you do is set your isRefreshing
flag to false when the data fetch has completed.
Using Kotlin Flows
and a simple data service fetch, this is as follows:
If you are chaining together multiple data fetch calls, then use an async
block and make sure to set the isRefreshing
flag after the async
so that it is only set to true when all calls are completed (I don’t really need it here as I just have one call, but I have added it above for illustration purposes).
Collecting states
In the screen composable you need to keep track of the refreshing
StateFlow
using collectAsStateWithLifecycle
. Using collectAsStateWithLifecycle
will allow your app to be more efficient by collecting flows in a lifecycle-aware manner (check out this great deep-dive article on this API). If you are using LiveData
you can observe this using your choice of method.
The next state to keep track of is the pull refresh state, this requires the refreshing
state and a lambda method that will be called when the pull to refresh action is called.
Finally, you should also collect your data state.
Implementing the Pull to Refresh action
This is the part that majorly differs from the original Accompanist Swipe Refresh, in that original implementation a SwipeRefresh
layout was wrapped around the UI layout, similar to how it was done with androidx.swiperefreshlayout.widget.SwipeRefreshLayout
in view based UIs.
Now, this work is done using the pullRefresh
modifier on your containing layout, passing in the pullRefreshState
.
You can use a layout that naturally scrolls like a LazyColumn
or you can use a layout that does not, such as a Column
or a Box
. To use a non-scrolling layout you must use a verticalScroll
modifier and remember the scroll state so that the pull action is recognised.
Finally, the PullRefreshIndicator
is then added
Putting the state tracking and this UI together:
If you use a naturally scrolling component such as a LazyColumn
there is no need to add the verticalScroll
modifier:
To see some examples of the code above, check out my Github Experiments repository here:
Fox images found via Randomfox.