I am trying to learn compose and retrofit and for that I am developing a very easy app, fetching jokes from a public API and showing them in a lazy list. But it is not working and I am not able to see any jokes. I am new to Kotlin and Jetpack compose. Please help me debug this.
I have a joke
class
data class Joke(
val id: Int,.
val punchline: String,
val setup: String,
val type: String
)
This is the API I am GETing from:
https://official-joke-api.appspot.com/jokes/:id
This is the response:
{"type":"general","setup":"What did the fish say when it hit the wall?","punchline":"Dam.","id":1}
This is the retrofit api service:
const val BASE_URL = "https://official-joke-api.appspot.com/"
interface JokeRepository {
@GET("jokes/{id}")
suspend fun getJoke(@Path("id") id: String ) : Joke
companion object {
var apiService: JokeRepository? = null
fun getInstance(): JokeRepository {
if (apiService == null) {
apiService = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build().create(JokeRepository::class.java)
}
return apiService!!
}
}
}
This is the Jokes view model:
class JokeViewModel : ViewModel() {
private val _jokeList = mutableListOf<Joke>()
var errorMessage by mutableStateOf("")
val jokeList: List<Joke> get() = _jokeList
fun getJokeList() {
viewModelScope.launch {
val apiService = JokeRepository.getInstance()
try {
_jokeList.clear()
// for(i in 1..100) {
// var jokeWithId = apiService.getJoke(i.toString())
// _jokeList.add(jokeWithId)
// Log.d("DEBUGGG", jokeWithId.setup)
// }
var joke = apiService.getJoke("1")
_jokeList.add(joke)
}
catch (e: Exception) {
errorMessage = e.message.toString()
}
}
}
}
This is the Main Activity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val jokeViewModel = JokeViewModel()
super.onCreate(savedInstanceState)
setContent {
HasyamTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
JokeView(jvm = jokeViewModel)
}
}
}
}
}
This is the Joke Component and view
@Composable
fun JokeView(jvm: JokeViewModel) {
LaunchedEffect(Unit, block = {
jvm.getJokeList()
})
Text(text = jvm.errorMessage)
LazyColumn() {
items(jvm.jokeList) {
joke -> JokeComponent(joke)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun JokeComponent(joke: Joke) {
var opened by remember { mutableStateOf(false)}
Column(
modifier = Modifier.padding(15.dp)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { },
elevation = CardDefaults.cardElevation(
defaultElevation = 5.dp
),
onClick = { opened = !opened}
) {
Text(modifier = Modifier.padding(15.dp), text = joke.setup)
}
if (opened) {
Text(modifier = Modifier.padding(15.dp), text = joke.punchline)
}
}
}
Thank you so much
The issue here is that you are not using stateFlow. The screen is not recomposed so your LazyColumn
is not recreated with the updated values.
class JokeViewModel : ViewModel() {
var errorMessage by mutableStateOf("")
private val _jokes = MutableStateFlow(emptyList<Joke>())
val jokes = _jokes.asStateFlow()
fun getJokeList() {
viewModelScope.launch {
val apiService = JokeRepository.getInstance()
try {
var jokes = apiService.getJoke("1")
_jokes.update { jokes }
} catch (e: Exception) {
errorMessage = e.message.toString()
}
}
}
}
@Composable
fun JokeView(jvm: JokeViewModel) {
val jokes by jvm.jokes.collectAsState()
LaunchedEffect(Unit, block = {
jvm.getJokeList()
})
Text(text = jvm.errorMessage)
LazyColumn {
items(jokes) {
joke -> JokeComponent(joke)
}
}
}
You should read the following documentation about states : https://developer.android.com/jetpack/compose/state