Skip to main content

Network Calls and DTOs

Communication between a server and a client over a network is an important topic that is worth to talk about. Network calls and DTOs (Data Transfer Object) are concepts that we use all the time in Narbase. So It's time to learn what they are and how we use them.

What is a Data Transfer Object?

A Data Transfer Object is a simple object that is used to transfer data from the server to the client (and back and forth). It shouldn't contain any business logic or validation. Let's make a simple DTO.

data class TodoDto(val id: String?, val task: String, val isDone: Boolean)

The DTOs we make are typically data classes since they only carry data. The DTOs have to match if they have the same data even if they are not the same references.  We send DTOs from the server to the client. Let's see how we do that by making a simple controller that fetches a single todo from a repository (see DAOs and Repositories).

class TodoDtoSenderController :
    EndpointHandler<TodoEndPoint.Request, TodoEndPoint.Response>(
        TodoEndPoint.Request::class,
        TodoEndPoint
    ) {
    override fun process(
        requestDto: TodoEndPoint.Request,
        clientData: AuthorizedClientData?,
    ): DataResponse<TodoEndPoint.Response> {
        val dto = TodoRepository.fetchTodo().toDto()
        return DataResponse(
            TodoEndPoint.Response(dto = dto)
        )
    }
}

Note that in case of models that have CRUDs operations like a todo, we'd normally have the controller to extend the EndPointCrudController instead of EndPointHandler, but this is just a simplified example to illustrate the usage of DTOs.

Here, we first make our controller and process the request. The requests and the responses typically contain the DTO that we want to transfer. Note that repositories should never know about DTOs, instead they only deal with models. We use conversion methods to convert from DTOs to models and from models to DTOs (see Database and Migrations).

Network calls and DTOs

Network calls is basically a way to communicate over a network. We typically use network calls to communicate with the server. The simplest way to do a network call is via basicNetworkCall. Let's see an example of it

class ViewModel {
   val dto: TodoDto? = null
   val uiState: Observable<BasicUiState> = Observable()
  
    fun fetchTodo() {
        basicNetworkCall(uiState) {
            dto = TodoEndPoint.remoteProcess(TodoEndPoint.Request()).data
         }
     }
}

Let's now display our todo DTO.

class MyComponentThatDisplaysMyDto: Component(){   
     private val vm = ViewModel()
     override fun onViewMounted(lifecycleOwner: LifecycleOwner) {
       super.onViewMounted(lifecycleOwner)
       vm.fetchTodo()
     }
     override fun View?.getView(): View {
        return verticalLayout {
          withLoadingAndError(vm.uiState, onLoaded = {
              val dto = vm.data!!
              textView {
                    text = data.toString().task
               }
          },
            onRetryClicked = {vm.fetchTodo()})
       }
    }
}

As explained briefly in Components and view models, withLoadingAndError is a method that takes care of showing a loading indicator when the state of BasicUiState is Loading, shows an error message when BasicUiState is Error, and finally calls onLoaded when the BasicUiState is Loaded. it internally listens to the supplied observable.

Summary

In summary, we learned about DTOs and how they're used to transfer the data from one part (e.g the client) to other part (e.g a server). We then showed a simple example of fetching the DTO from the server and display its data in the client.