Load next data when on click of button using the Paging 3 library for Compose - android-jetpack-compose

Currently Paging library handles when to load the data automatically when a user scrolls down. But what if you want to give the user full authority for when they want the next page of data to be loaded i.e when button is clicked show next page of movies. How can you handle this in Paging library? See below how I've implemented the paging to load data as a user scrolls down
Here below this how I implemented the Paging to load next page when user scrolls down
class MoviesPagingDataSource(
private val repo: MoviesRepository,
) : PagingSource<Int, Movies>() {
override fun getRefreshKey(state: PagingState<Int, Movies>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movies> {
return try {
val nextPageNumber = params.key ?: 0
val response = repo.getMovies(page = nextPageNumber, size = 10)
LoadResult.Page(
data = response.content,
prevKey = null,
nextKey = if (response.content.isNotEmpty()) response.number + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
This is how I emit the state in ViewModel for the UI to observe
#HiltViewModel
class MoviesViewModel #Inject constructor(
private val moviesRepository: MoviesRepository
): ViewModel {
....
//emitting the data to the UI to observe
val moviesPagingDataSource = Pager(PagingConfig(pageSize = 10)) {
MoviesPagingDataSource(moviesRepository)
}.flow.cachedIn(viewModelScope)
}
How I'm observing it in the UI
#Composable
fun MoviesList(viewModel: MoviesViewModel) {
val moviesList = viewModel.moviesPagingDataSource.collectAsLazyPagingItems()
LazyColumn {
items(moviesList) { item ->
item?.let { MoviesCard(movie = it) }
}
when (moviesList.loadState.append) {
is LoadState.NotLoading -> Unit
LoadState.Loading -> {
item {
LoadingItem()
}
}
is LoadState.Error -> {
item {
ErrorItem(message = "Some error occurred")
}
}
}
when (moviesList.loadState.refresh) {
is LoadState.NotLoading -> Unit
LoadState.Loading -> {
item {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Center
) {
CircularProgressIndicator()
}
}
}
is LoadState.Error -> TODO()
}
}
}
So currently I'm adding 1 to the previous page every time a user clicks the button to load more movies then saving this movies to the list state. Also making sure the current page is greater than or equal to total pages before loading more data and adding to the list state of previous loaded movies

you can use Channel to block LoadResult returning from load, when user clicks the button, send an element to the Channel. here is the simple
class MoviesPagingDataSource(
private val repo: MoviesRepository
) : PagingSource<Int, Movies>() {
private val channel: Channel<Unit> = Channel(1, BufferOverflow.DROP_LATEST)
override fun getRefreshKey(state: PagingState<Int, Movies>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movies> {
return try {
val nextPageNumber = params.key ?: 0
// load initial page
if (nextPageNumber == 0) loadNextPage()
val response = repo.getMovies(page = nextPageNumber, size = 10)
/*
* block next page data return, when user click the button,
* call loadNextPage. if you don't want to automatically
* request data as the user scrolls toward the end of the
* loaded data. put this line above the repo.getMovies.
**/
channel.receive()
LoadResult.Page(
data = response.content,
prevKey = null,
nextKey = if (response.content.isNotEmpty()) response.number + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
suspend fun loadNextPage() {
channel.send(Unit)
}
}

Related

how to refresh recycyerview when pulldown recycyerview by user

i want refresh retrofit request when user pulldown the page .
i add an icon in option menu when click on icon refresh the data .
GlobalScope.launch {
val result = quotesApi.getQuotes()
withContext(Dispatchers.Main) {
if (result != null)
// Checking the results
prg.visibility = View.GONE
Log.d("T", result.body().toString())
}
}
val pullToRefresh = findViewById<SwipeRefreshLayout>(R.id.pullToRefresh)
pullToRefresh.setOnRefreshListener {
refreshData() // your code
pullToRefresh.isRefreshing = false
}
}
private fun refreshData() {
val quotesApi = RetrofitHelper

Android dataStore with flow not get update after edit

I'm use DataStore with flow but I cant get any update on the flow when editing DataStore.
Store.kt
private class IStore(private val context: Context): Store {
val eventIDKey = stringPreferencesKey("EventID")
override suspend fun setEventID(eventID: String) {
context.dataStoreSettings.edit { settings ->
settings[eventIDKey] = eventID
}
}
override fun getEventID(): Flow<String> {
return context.dataStoreSettings.data.map { settings -> settings[eventIDKey].orEmpty() }
}
}
and manipulate getEventID() with data from room database in event service
EventService.kt
fun getSelectedEventLive() = store.getEventID()
.onEach { Log.d("EventService", "income new event id $it") }
.flatMapConcat { if(it.isNotBlank()) eventDao.get(it) else flowOf(null) }
onEach called when I collect the data but when updated it's not called again and need to close and open the app to show the latest data
MainViewModel.kt
val selectedEvent = eventService.getSelectedEventLive()
.stateIn(viewModelScope, SharingStarted.Lazily, null)
and use on Compose with this
val currentEvent by mainViewModel.selectedEvent.collectAsState()
Maybe I doing wrong or maybe there is something I miss?
Usually, you want to use flow.collect {...}, since Flow is cold and need to know that it is being collected to start producing new values.
// MainViewModel.kt
private val _selectedEvent = MutableStateFlow<TypeOfYourEvent>()
val selectedEvent: StateFlow<TypeOfYourEvent> = _selectedEvent
init {
viewModelScope.launch {
getSelectedEventLive().collect { it ->
_selectedEvent.value = it
}
}
}
This example should be fine with your composable's code, you still can collect selectedEvent as state.
Yeah i found the solusion its works if i change the flatMapConcat with flatMapLatest in EventService.kt
fun getSelectedEventLive() = store.getEventID()
.filterNot { it.isBlank() }
.flatMapLatest { eventDao.get(it) }

How to make the first item in a lazy column clickable and make the rest unclickable

How to make the first item in a lazy column clickable and make the rest unclickable.
for example if I have a list of tasks to complete, enable first item only which is not completed
you have to complete tasks one at a time
LazyColumn(
modifier.padding(top = 40.dp),
) {
itemsIndexed(
items = todayRoute.sortedBy { it.sequence },
) { index, item ->
Row(
modifier = modifier
.fillMaxWidth()
.height(90.dp)
.padding(12.dp)
.clickable(
enabled = item.completed == "0" &&
item.arrived == "0" &&
item.missed == "0"
) {
})
{
}
}
}
Use Hashmap like this
As soon as you get all the item call replaceAll.
When the item is completed, call taskCompleted
class TaskViewModel {
private var _taskStatus = MutableStateFlow<HashMap<String, Boolean>>(hashMapOf())
val taskStatus = _taskStatus.asStateFlow()
fun taskCompleted(item: Task) {
_taskStatus.replace(item.id, true)
}
fun replaceAll(tasks: List<Task>) {
_taskStatus.value.clear()
tasks.forEach {
_taskStatus.value[it.id] = it.completed
}
}
private fun MutableStateFlow<HashMap<String, Boolean>>.replace(key: String, value: Boolean) {
val helper = HashMap<String, Boolean>(this.value)
helper[key] = value
this.value = helper
}
}
On UI, Adjust according to your need
val taskStatus by remember { viewModel.taskStatus }.collectAsState()
LazyColumn {
itemsIndexed(todayRoute) { item, index ->
Row(modifier = Modifier.clickable(
enabled = if (index > 0)
taskStatus[index - 1] ?: false
else
true
)
) {}
}
}

Randomly Rearrange items in a LazyRow - Jetpack Compose

I have a LazyRow. Everything works fine. I just want the items to be randomly rearranged every time this LazyRow is drawn on the screen. Here is my code:
LazyRow(
reverseLayout = true,
contentPadding = PaddingValues(top = twelveDp, end = eightDp)
) {
itemsIndexed(
items = featureUsersLazyPagingItems,
key = { _, featuredPerson ->
featuredPerson.uid
}
) { _, featuredUser ->
featuredUser?.let {
//Daw the age suggested People
DrawSuggestedPerson(featuredUser.toPersonUser(),) {
homeViewModel.deleteFeaturedUserFromLocalDb(featuredUser.uid)
}
}
}
featureUsersLazyPagingItems.apply {
when {
loadState.refresh is LoadState.Loading -> {
item {
ShowLazyColumnIsLoadingProgressBar()
}
}
loadState.append is LoadState.Loading -> {
item {
ShowLazyColumnIsLoadingProgressBar()
}
}
loadState.refresh is LoadState.Error -> {
val e = featureUsersLazyPagingItems.loadState.refresh as LoadState.Error
item {
LazyColumnErrorView(
message = e.error.localizedMessage!!,
onClickRetry = { retry() }
)
}
}
loadState.append is LoadState.Error -> {
val e = featureUsersLazyPagingItems.loadState.append as
LoadState.Error
item {
LazyColumnErrorView(
message = e.error.localizedMessage!!,
onClickRetry = { retry() }
)
}
}
}
}
So the LazyRow displays the same set of 30 or so items but only 3- 4 items are visible on screen, for a bit of variety, I would like the items to be re-arranged so that the user can see different items on the screen. Is there a way to achieve this?
You can shuffle your list inside remember, by doing this you're sure that during one view appearance your order will be the same, and it'll be shuffled on the next view appearance. I'm passing featureUsersLazyPagingItems as a key, so if featureUsersLazyPagingItems changes shuffledItems will be recalculated.
val shuffledItems = remember(featureUsersLazyPagingItems) {
featureUsersLazyPagingItems.shuffled()
}
The only problem of remember is that it'll be reset on screen rotation. Not sure if you need that, and if you wanna save state after rotation, you need to use rememberSaveable. As it can only store simple types, which your class isn't, you can store indices instead, like this:
val shuffledItemIndices = rememberSaveable(featureUsersLazyPagingItems) {
featureUsersLazyPagingItems.indices.shuffled()
}
val shuffledItems = remember(featureUsersLazyPagingItems, shuffledItemIndices) {
featureUsersLazyPagingItems.indices
.map(featureUsersLazyPagingItems::get)
}
I suggest you checkout out documentation for details of how state works in compose.

How to do update the list view by changing the drop-down item

I want to change my list as I change my drop-down item..The list is dynamic so as I changed my option from drop-down..my list should update which will change only when that API that is called to form a list is changed by the selected drop-down item...The whole url will be same ,the only change is in the item.
Future<ParsedDataModel> fetchTicketListData(String queueName) async {
String openURL = "example.com/query=Queue='$queueName'";
// hitting login api
final response3 = await http.get(openURL);
print(response3.body);
if (response3.statusCode == 200 && response3.body.contains("200 Ok"))
{
// parsing plain data to desired model
ParsedDataModel ticketDataModel =
DashBoardViewController().plainDataToParsedModelData(response3.body.trim());
return ticketDataModel;
} else {
throw Exception('There was a problem');
}
}
Future<List<QueueModel>> fetchTicketsList() async {
var dataList = await fetchTicketListData('InternalTools');
if (dataList.isNoResult) {
throw Exception('No result found.');
} else {
List<QueueModel> openTicketList = makeTicketList(dataList);
openTicketsList = openTicketList;
return openTicketsList;
}
}
// function to parse the data into list of (QueueModel)
List<QueueModel> makeTicketList(ParsedDataModel parsedData) {
return
DashBoardViewController().parsedDataToQueueModelData(parsedData.dataList);
}

Resources