Chapter 12 - Developing the Composable UI ArtListScreen
Let us now code the composable for the ArtListScreen and complete the flow.
package androidavatar.learning.artlearnersapp.presentation.artlist
import androidavatar.learning.artlearnersapp.domain.model.Art
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddBox
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Divider
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
@Composable
fun ArtListScreen(
artList: List<Art>,
isLoading: Boolean = false,
onFabClick: () -> Unit = {},
onDeleteClick: (Art) -> Unit = {},
errorMessage: String? = null
) {
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = onFabClick) {
Icon(Icons.Default.AddBox, contentDescription = "Add Art")
}
}
) { paddingValues ->
Box(modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
when {
// ✅ Loading State
isLoading -> {
CircularProgressIndicator(
modifier = Modifier
.align(Alignment.Center)
.testTag("Progress")
)
}
// ✅ Error State
errorMessage != null -> {
Text(
text = errorMessage,
color = Color.Red,
modifier = Modifier
.align(Alignment.Center)
.testTag("ErrorMessage")
)
}
// ✅ Empty List State
artList.isEmpty() -> {
Text(
text = "No artworks found!",
modifier = Modifier.align(Alignment.Center)
)
}
// ✅ Normal List
else -> {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(artList) { art ->
ListItem(
headlineContent = { Text(art.name) },
supportingContent = { Text("${art.artistName} (${art.year})") },
trailingContent = {
IconButton(onClick = { onDeleteClick(art) }) {
Icon(
Icons.Default.Delete,
contentDescription = "Delete"
)
}
}
)
Divider()
}
}
}
}
}
}
}
The layout has a “fab” button at the right-end bottom corner. The list of image records are displayed from the Room DB using Lazy Column. We have already discussed this process in depth in the earlier chapters.
Modifying the MainActivity to visualise the screen
Now modify the MainActivity (this is the entry point) of the app.
class MainActivity : ComponentActivity() {
// Fake repository for now
private val fakeRepository = object : ArtRepositoryInterface {
override fun getArt(): Flow<List<Art>> = flow {
emit(
listOf(
Art(1, "Starry Horse", "Viru Pandey", "1904", "url1"),
Art(2, "Mano ya na mano", "Satish Shah", "1998", "url2")
)
)
}
override fun insertArt(art: Art) {}
override fun deleteArt(art: Art) {}
override fun searchImage(userQuery: String): Flow<Resource<List<ImageResult>>> =
flow { emit(Resource.Success(emptyList())) }
}
private val getArtUseCase = GetArtUseCase(fakeRepository)
private val viewModel by viewModels<ArtViewModel> {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ArtViewModel(getArtUseCase) as T
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val arts by viewModel.arts.collectAsState()
ArtListScreen(
artList = arts,
onFabClick = { /* handle add */ },
onDeleteClick = { }
)
}
}
}
Now we have introduced the fake repository and wired all the connections. Later on we will remove the fake dependencies and introduce Hilt to upgrade our code that will be production ready.
You will see the two Art objects as two list elements.
Kudos for coming thus far and you have the first composable screen with end-to-end code.
In the next chapter we will add the tests for the composable ArtListScreen and wrap up the code!