Lesson 20: Working with JSON and Data Models
Working with JSON and Data Models
Our TaskApi from lesson 19 returns a list of TaskDto — let's define that properly and understand why it's named that way.
Defining the DTO
JSONPlaceholder's /todos endpoint returns JSON like this:
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}Model it with a data class matching the same field names:
data class TaskDto(
val userId: Int,
val id: Int,
val title: String,
val completed: Boolean
)Gson (the JSON converter we added in lesson 19) automatically matches JSON field names to your data class properties — as long as the names match, it fills them in for you with no manual parsing code required.
Why "DTO"?
DTO stands for Data Transfer Object — it represents exactly what the network sends, nothing more. It's a deliberate convention to name it separately from your app's own internal model:
data class Task(val id: Int, val title: String, val isDone: Boolean)
fun TaskDto.toTask(): Task = Task(
id = this.id,
title = this.title,
isDone = this.completed
)This extension function (from lesson 4/Kotlin Part 2) converts the network shape into your app's own shape. It looks like unnecessary duplication for a small app, but it means if the API ever renames a field or changes its shape, only this one conversion function needs updating — not every screen that uses Task.
Handle the possibility of failure
viewModelScope.launch {
try {
tasks = taskApi.getTasks().map { it.toTask() }
} catch (e: Exception) {
// no internet, server error, etc. — handle this in lesson 21
}
}Every real network call needs a try/catch — assume it will fail sometimes (no signal, server downtime) and handle that case deliberately rather than letting the app crash.
Next: Lesson 21 — displaying this network data properly, including loading and error states.