Issue
I am completely new to Jetpack Compose AND Kotlin, but not to Android development in Java. Wanting to make first contact with both technologies, I wanted to make a really simple app which populates a LazyColumn with images from Dog API.
All the Retrofit connection part works OK, as I've managed to populate one card with a random puppy, but when the time comes to populate the list, it's just impossible. This is what happens:
- The interface is created and a white screen is shown.
- The API is called.
- Wait about 20 seconds (there's about 400 images!).
dogImagesgets updated automatically.- The LazyColumn never gets recomposed again so the white screen stays like that.
Do you have any ideas? I can't find any tutorial on this matter, just vague explanations about state for scroll listening.
Here's my code:
class MainActivity : ComponentActivity() {
private val dogImages = mutableStateListOf<String>()
@ExperimentalCoilApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PuppyWallpapersTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
DogList(dogImages)
searchByName("poodle")
}
}
}
}
private fun getRetrofit():Retrofit {
return Retrofit.Builder()
.baseUrl("https://dog.ceo/api/breed/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private fun searchByName(query: String) {
CoroutineScope(Dispatchers.IO).launch {
val call = getRetrofit().create(APIService::class.java).getDogsByBreed("$query/images")
val puppies = call.body()
runOnUiThread {
if (call.isSuccessful) {
val images = puppies?.images ?: emptyList()
dogImages.clear()
dogImages.addAll(images)
}
}
}
}
@ExperimentalCoilApi
@Composable
fun DogList(dogs: SnapshotStateList<String>) {
LazyColumn() {
items(dogs) { dog ->
DogCard(dog)
}
}
}
@ExperimentalCoilApi
@Composable
fun DogCard(dog: String) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(15.dp),
elevation = 10.dp
) {
Image(
painter = rememberImagePainter(dog),
contentDescription = null
)
}
}
}
Thank you in advance! :)
Solution
Your view of the image cannot determine the aspect ratio before it loads, and it does not start loading because the calculated height is zero. See this reply for more information.
Also a couple of tips about your code.
- Storing state inside
MainActivityis bad practice, you can use view models. Inside a view model you can useviewModelScope, which will be bound to your screen: all tasks will be cancelled, and the object will be destroyed when the screen is closed. - You should not make state-modifying calls directly from the view constructor, as you do with
searchByName. This code can be called many times during recomposition, so your call will be repetitive. You should do this with side effects. In this case you can useLaunchedEffect, but you can also do it in theinitview model, because it will be created when your screen appears. - It's very convenient to pass Modifier as the last argument, in this case you don't need to add a comma at the end and you can easily add/remove modifiers.
- You may have many composables, storing them all inside
MainActivityis not very convenient. A good practice is to store them simply in a file, and separate them logically by files.
Your code can be updated to the following:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PuppyWallpapersTheme {
DogsListScreen()
}
}
}
}
@Composable
fun DogsListScreen(
// pass the view model in this form for convenient testing
viewModel: DogsModel = viewModel()
) {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
DogList(viewModel.dogImages)
}
}
@Composable
fun DogList(dogs: SnapshotStateList<String>) {
LazyColumn {
items(dogs) { dog ->
DogCard(dog)
}
}
}
@Composable
fun DogCard(dog: String) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(15.dp),
elevation = 10.dp
) {
Image(
painter = rememberImagePainter(
data = dog,
builder = {
// don't use it blindly, it can be tricky.
// check out https://stackoverflow.com/a/68908392/3585796
size(OriginalSize)
},
),
contentDescription = null,
)
}
}
class DogsModel : ViewModel() {
val dogImages = mutableStateListOf<String>()
init {
searchByName("poodle")
}
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://dog.ceo/api/breed/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private fun searchByName(query: String) {
viewModelScope
.launch {
val call = getRetrofit()
.create(APIService::class.java)
.getDogsByBreed("$query/images")
val puppies = call.body()
if (call.isSuccessful) {
val images = puppies?.images ?: emptyList()
dogImages.clear()
dogImages.addAll(images)
}
}
}
}
Answered By - Philip Dukhov
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.