Ionut
Ionut

Reputation: 929

Jetpack Compose Material3 Pull to Refresh functionality

I am trying to migrate a project from Material2 specs to Material 3 compose library following this guide.

The current LazyColumn in Material2 has integrate Pull to refresh functionality, found here

However, i did not find any such functionality for Material3. There is an issue tracker open here but they say this functionality will be considered only mid this year, which is too long to wait.

Does anyone have any idea on how this situation should be handled? Pull to refresh is an important flow of our apps user flow, so not having it is not an option.

Should we use accompanist/swipeToRefresh even though it is deprecated?

I am not proficient in writing custom compose components yet, so any help in figuring this out would be appreciated

Upvotes: 22

Views: 16023

Answers (7)

Randy
Randy

Reputation: 1146

I finally figure out how to do this properly. (Thanks to everyone who mentioned PullToRefreshBox, because I looked at that code to see how it works.)

First and foremost, the obvious. Replace the material dependencies in your build.gradle with the material3 dependencies:

    //api(compose.material)
    api(compose.material3)

    // For desktop - if like me you're using KMP 
    //implementation("org.jetbrains.compose.material:material-desktop:<version>")
    implementation("org.jetbrains.compose.material3:material3-desktop:<version>")

    // For Android 
    //implementation("androidx.compose.material:material:<version>")
    implementation("androidx.compose.material3:material3-android:<version>")

Now, on to the real changes. Step 1:

Change the pullRefreshState to a pullToRefreshState. This sounds like an easy task, but it also requires you to move some things. In material2 pullRefreshState contains some things, including isRefreshing and onRefresh. These things are now in the modifier in material3. But don't just delete them, you probably want to copy (or cut) them so that you can use them in step 2.

Step 2:

Change your modifier from .pullRefresh(pullRefreshState) to .pullToRefresh(isRefreshing, pullToRefreshState, onRefresh)

Remember that this requires you to put some information that you removed from step 1 into these values.

Step 3: (This is the part that most people seem to be missing)

Change your PullRefreshIndicator to PullToRefreshDefaults.Indicator. These have very similar parameters, but slightly different names.

  • backgroundColor is now containerColor
  • contentColor is now just color
  • scale is gone

The indicator also seems to behave slightly differently. Mostly that in material2 you could pull down to see the indicator and change your mind part way through, pushing the indicator back up, and it wouldn't refresh. That doesn't seem to be possible in material3. You just pull down on your list and eventually the indicator shows that it's refreshing.

Upvotes: 0

Joel
Joel

Reputation: 483

First add version 1.3.0-beta04 of material3

implementation("androidx.compose.material3:material3:1.3.0-beta04")

Now implement pull to refresh as given below

var isRefreshing by remember { mutableStateOf(false) }

PullToRefreshBox(
    isRefreshing = isRefreshing,
    onRefresh = {
        coroutineScope.launch {
            isRefreshing = true
            viewModel.refreshData()
            isRefreshing = false
        }
    },
) {
    LazyColumn {
        items(items) { item->
            // Render Items
        }
    }
}

Upvotes: 9

Frank
Frank

Reputation: 12300

In 2024, you can use these material3 components:

val pullToRefreshState = rememberPullToRefreshState()
if (pullToRefreshState.isRefreshing) {
    LaunchedEffect(true) {
        delay(1000)
        pullToRefreshState.endRefresh()
    }
}

and around your scrolling Column:

  Box(Modifier.nestedScroll(pullToRefreshState.nestedScrollConnection)) {
       Column(modifier = Modifier
           .fillMaxSize()
           .verticalScroll(rememberScrollState())) { 
           ... 
      }
      PullToRefreshContainer(
          modifier = Modifier.align(Alignment.TopCenter),
          state = pullToRefreshState,
      )
  }

Upvotes: 7

Marc
Marc

Reputation: 118

So in the end, I resolved this not by upgrading to the latest beta 1.3.0-beta01 as this introduced other problems, most notably with PullToRefresh that is broken in this build. But instead by simply ensuring that my ModalBottomSheet's content was explicitly adding sufficient padding:

ModalBottomSheet(
// ... however you want to configure it
) { 
   Column(
      modifier = Modifier.navigationBarsPadding() // This is where the magic happens
   ) { ... }
}

This handles cases where there is a software navigation bar as well as when there is not (such as when the device is configured to use gestures instead)

Upvotes: -3

Roberto Leinardi
Roberto Leinardi

Reputation: 14399

In androidx.compose.material3:material3 version 1.3.0-beta01 is available the Composable PullToRefreshBox which can be used to replace the Box + PullRefreshIndicator from M2:

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}") })
                }
            }
        }
    }
}

More samples are available here

Upvotes: 1

Jez
Jez

Reputation: 1256

It looks like currently there's a dedicated Material3 component for this: PullToRefreshContainer.

See docs at https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/package-summary

At the time of writing the api is experimental and I'd expect it to be updated to make it easier to hoist the state out of the UI element, but the example in the docs provides a starting point for migrating. It lacks a callback on refresh starting, which is something you'd have to work around in the interim.

Upvotes: 4

Manny Tirado
Manny Tirado

Reputation: 256

Our team ran into this same problem. Ultimately, we opted to bring the MD2 implementation of the PullToRefresh component into our project as a temporary workaround. Then, we just updated any MD2 references to their MD3 counterparts.

The full MD2 implementation consists of the following files:

  • PullRefresh.kt
  • PullRefreshIndicator.kt
  • PullRefreshIndicatorTransform.kt
  • PullRefreshState.kt

This approach was suggested in this discussion. You can find the op's full solution here.

Upvotes: 24

Related Questions