Blog Infos
Author
Published
Topics
,
Published

Sometimes being in a lazy state feels good!!

There is a common scenario that we as Android app developers face a lot and that is to have a list and on each item click we would have to process some data in background and reflect the end result back on the list item.

Consider a list like instagram and say we’ve a ‘Like’ button, clicking on which updates the data on server and on successful result, the ‘Like’ button background changes to different color. Now this is a single action on the list. A user can click ‘Like’ button on different items in list and we’ve to update our list for each item correctly as per the result obtained.

In this article we’ll see how can we manage our list state and process data in background concurrently for multiple items and updating them on UI correctly.

End result

By the end we’ll be able to achieve the following result.

Presumption

We’ll assume that we’ve a list of 20 items (could be more/less) and clicking on each item initiates a background task for some time. We’ll be simulating the long running task using delay function and this delay will be randomly assigned to each item so that we can see all items updating themselves randomly.

Control Flow

 

Control Flow

Let’s code the flow

We’ll have a basic list item for tutorial purpose(no fancy stuff here).

In case you want to checkout how to create custom views/layouts in jetpack compose, do give this and this a read. You won’t be disappointed.

So let’s create our list item first.

@Composable
fun ListItem(
item: AppItem,
background: Color,
onItemClick: (Int) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
.background(background)
.clickable {
onItemClick(item.id)
},
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f).padding(horizontal = 16.dp),
text = item.text,
style = TextStyle(
fontSize = 20.sp,
fontWeight = FontWeight.SemiBold
)
)
Box(
modifier = Modifier.size(60.dp),
contentAlignment = Alignment.Center
) {
/** show indicator only for loading state */
if (item.taskStatus == TaskStatus.IN_PROGRESS)
CircularProgressIndicator(
modifier = Modifier.size(30.dp),
strokeWidth = 3.dp,
color = Color.Magenta,
)
else
Icon(
if (item.taskStatus == TaskStatus.NOT_STARTED)
Icons.Default.PlayArrow
else Icons.Default.Check,
"action icon",
tint = if (item.taskStatus == TaskStatus.NOT_STARTED)
Color.Gray
else
Color.Blue,
)
}
}
}

Job Offers

Job Offers


    Senior C++ Developer for Android Automotive (m/w/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

    Android Engineer

    American Express
    Phoenix, USA
    • Full Time
    apply now

    Android Engineer

    American Express
    London | New York | Phoenix
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

,

Writing backwards-compatible Gradle plugins

Gradle plugins are powerful – you can integrate custom logic into the build process to make it fit your needs. In the Android world, most Gradle plugins will have to interact with the Android Gradle…
Watch Video

Writing backwards-compatible Gradle plugins

SIMON SCHILLER
Software Engineer
Spotify

Writing backwards-compatible Gradle plugins

SIMON SCHILLER
Software Engineer
Spotify

Writing backwards-compatible Gradle plugins

SIMON SCHILLER
Software Engineer
Spotify

Jobs

And it looks like this

List item — Not started

Our list item, when clicked, will start a loader and initiates a background task of some random duration and it looks like as follows

List item — In progress

And once the task returns, then it marks it as checked.

List item — Completed

Alright! So now we know how our list item will operate. Let’s add it in our list.

@Composable
fun HomeScreen(
viewModel: HomeViewModel = viewModel()
) {
val state = viewModel.itemsState
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(Color.White),
verticalArrangement = Arrangement.Top
) {
items(
items = state,
key = {
it.id
}
) { item ->
ListItem(
item = item,
background = if (item.id % 2 == 0) Color.LightGray else Color.White,
onItemClick = {
viewModel.uiEvent(UiEvent.ItemClick(item))
}
)
}
}
}

The code is self explanatory. Few key points to note here:

  • Our list item is stateless which is important. We should follow state hoisting.
  • Event handling is done in View model.
  • State is updated in list item as it is changed.

Let’s move to view model which provides us with state and data handling.

We prepare a itemList of 20 items with id as index. We create the state for our list by using mutableStateListOf<AppItem>() and also creates an immutable state of this list as itemsState. Finally in init block we add our list objects into our mutable state.

private val itemList = (0..20).map { i ->
AppItem(
id = i,
text = "Item ${i+1}"
)
}
private val _itemsState = mutableStateListOf<AppItem>()
val itemsState: List<AppItem> = _itemsState
init {
_itemsState.addAll(itemList)
}
view raw ItemlistPrep.kt hosted with ❤ by GitHub

Now once our item is clicked, we check if it is in Not Started state then we launch a new coroutine and pass the item id. We then generate a small random delay and once the delay is over, we update our item id task status which in turn forces a recomposition of our affected list item.

private fun processTask(id: Int) {
viewModelScope.launch(Dispatchers.IO) {
try {
Log.e("Tag","thread = ${Thread.currentThread().name}")
backgroundTask(id)
} finally {
Log.e("Tag","cancelled $id")
}
}
}
private suspend fun backgroundTask(id: Int) {
val delay = id + Random.nextInt(5)
Log.e("Tag","delay for id $id is $delay")
delay(delay * 1000L)
_itemsState[id] = itemsState[id].copy(
taskStatus = TaskStatus.COMPLETED
)
}
view raw ProcessTask.kt hosted with ❤ by GitHub

We use IO dispatcher here just to simulate that we’re performing some I/O operation. Lastly, we cancel our coroutines once our scope is finished.

override fun onCleared() {
    super.onCleared()
    viewModelScope.cancel()
}

Bamn! We now have the output in which we’re making concurrent calls and updating our items accordingly.

Once again below is the output for reference.

Checkout the complete code at

Let’s connect on medium or on github.

And I’ll see you again in next interesting tutorial.

Until next time…

Cheers!

This article was originally published on proandroiddev.com on June 27, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
blog
Yes! You heard it right. We’ll try to understand the complete OTP (one time…
READ MORE

Leave a Reply

Your email address will not be published.

Fill out this field
Fill out this field
Please enter a valid email address.

Menu