https://kotlinlang.org logo
#compose-android
Title
# compose-android
b

bryankeltonadams

03/11/2024, 5:19 PM
Does someone understand the new Material 3 PullToRefresh a better better than I? It seems like it's a step backwards from the Material2 PullRefresh. Having to use LaunchedEffects in order to check if the state is refreshing, and then a LaunchedEffect from my own viewModel where I have a boolean for isRefreshing in order to stop refreshing after the network success etc. Unless there's a way to use your own boolean state for m3 pull to refresh, or maybe I'm not tightly integrated the pullToRefreshState into my viewModel as much as I should be? The example on the docs doesn't show an example with a viewModel so you usually have to infer how to make the necessary changes yourself. 🧵
Copy code
val rememberPullToRefreshState = rememberPullToRefreshState(enabled = {
        true
    })

    if (rememberPullToRefreshState.isRefreshing) {
        LaunchedEffect(true) {
            // fetch something
            refresh()
        }
    }
    //from viewModel, set to true when refresh is called, and set to false at the end of refresh on error or success
    if (!isRefreshing) {
        LaunchedEffect(true) {
            if(rememberPullToRefreshState.isRefreshing) {
                rememberPullToRefreshState.endRefresh()
            }
        }
    }
here's the m2 pullRefresh which just takes a boolean either from a local remember or from a viewModel state or something.
Copy code
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ListItem
import androidx.compose.material.Text
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope

val refreshScope = rememberCoroutineScope()
var refreshing by remember { mutableStateOf(false) }
var itemCount by remember { mutableStateOf(15) }

fun refresh() = refreshScope.launch {
    refreshing = true
    delay(1500)
    itemCount += 5
    refreshing = false
}

val state = rememberPullRefreshState(refreshing, ::refresh)

Box(Modifier.pullRefresh(state)) {
    LazyColumn(Modifier.fillMaxSize()) {
        if (!refreshing) {
            items(itemCount) {
                ListItem { Text(text = "Item ${itemCount - it}") }
            }
        }
    }

    PullRefreshIndicator(refreshing, state, Modifier.align(Alignment.TopCenter))
}
Should I be putting a PullToRefreshState in my viewModel instead of using rememberPullToRefreshState in my Composable?
s

sindrenm

03/11/2024, 9:38 PM
I agree that this API feels a little bit more awkward. I would be fine with putting the
PullToRefreshState
in my view model, but that's also not easy because of the required
positionalThreshold: Float
that it needs to build it. 🤔
s

Stylianos Gakis

03/11/2024, 9:48 PM
State holder objects that want to be in the ViewModel but are making it hard to move the to the ViewModel because they take in a parameter that does not fit the ViewModel. Name a more iconic duo 🫣
🙌 1
b

bryankeltonadams

03/11/2024, 9:57 PM
Thanks for the replies. I'll probably just stick with the LaunchedEffects and having a separate boolean on my uiState in my viewModel. The M2 API seemed really nice so I wonder why they went this route with M3.
Have you guys had much luck using the enabled parameter of the rememberPullToRefreshState? Doesn't seem to work for me and I've opted for having my conditional apply to the nestedScrollConnection modifier instead.
s

sindrenm

03/13/2024, 2:31 PM
I haven't played around enough with this yet, as we're on an older version of compose-material3, unfortunately.
s

Stylianos Gakis

03/13/2024, 2:34 PM
Yeah and we just got the code from accompanist if I remember correctly https://github.com/HedvigInsurance/android/tree/5f1f762af6f5af12cc93946f7113bc4c9c[…]p/ui/pullrefresh/src/main/kotlin/com/hedvig/android/pullrefresh and just moved it in our code directly after the deprecation. We possibly did some adjustments to it too, I really do not remember. Haven’t touched it since because it just works for us 🤷
a

André Kindwall

04/16/2024, 10:54 AM
Seems like they are doing some big changes to the api https://android-review.googlesource.com/c/platform/frameworks/support/+/3029663
thank you color 1
s

Stylianos Gakis

04/16/2024, 10:58 AM
Yeah and the sample code looks good
Copy code
@Preview
@Sampled
fun PullToRefreshViewModelSample() {
    val viewModel = remember {
        object : ViewModel() {
            private val refreshRequests = Channel<Unit>(1)
            var isRefreshing by mutableStateOf(false)
                private set

            var itemCount by mutableStateOf(15)
                private set

            init {
                viewModelScope.launch {
                    for (r in refreshRequests) {
                        isRefreshing = true
                        try {
                            itemCount += 5
                            delay(1000) // 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}") })
                    }
                }
            }
        }
    }
}
With the VM holding the single source of truth for the refreshing state!
30 Views