Remote API

Connecting to the Internet

Permissions

  1. Go to app -> manifests, and add
<!-- Declare permission -->
<uses-permission android:name="android.permission.INTERNET" />
<application></application>
  • Retrofit: Retrofit is a type-safe HTTP client for Android, developed by Square. This is industry standard and recommended by Google.

    • GET: Retrieve data from the server.
    • POST: Create a new resource or submit data.
    • PUT: Update or replace an existing resource.
    • DELETE: Remove a resource.
    • Typical JSON API
    • https://my-json-server.typicode.com/GithubGenericUsername/find-your-pet/contacts
    • https://my-json-server.typicode.com/GithubGenericUsername/find-your-pet/contacts/4
  1. Go to Gradle script and add dependency

in Geadle script (module)

// on the top
plugins {    
    // ...
    id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21"
}    

// at the bottom
dependencies {

    // room setup
    // ...

    // retrofit
    val retrofit_version = "3.0.0"
    implementation("com.squareup.retrofit2:converter-kotlinx-serialization:$retrofit_version")
    implementation("com.squareup.retrofit2:retrofit:$retrofit_version")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
}    
  1. Create a new package under the main pacakage called remote and under the package create a new kotlin class called ContactDto.kt
package at.uastw.contactsapp.remote

import kotlinx.serialization.Serializable

@Serializable
data class ContactDto (
    val id: Int,
    val name: String,
    val telephoneNumber: Int,
    val age: Int
)
  1. Create an interface under remote called ContactRemoteService.kt

in ContactRemoteService.kt

package at.uastw.contactsapp.remote

import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path

interface ContactRemoteService {

    @GET("contacts")
    suspend fun getAllContacts(): List<ContactDto>

    @GET("contacts/{contactId}")
    suspend fun getAllContactById(@Path("contactId") id: Int): ContactDto

    @POST("contacts")
    suspend fun addContact(@Body contactDto: ContactDto)
}

in ContactRepository.kt

// update
// class ContactRepository(private val contactsDao: ContactsDao) {...}
class ContactRepository(
    private val contactsDao: ContactsDao,
    private val contactRemoteService: ContactRemoteService
) {

    // newly add on top
    suspend fun loadInitialContacts(){
        Log.w("REPOSITORY", "Start loading")
        val remoteContacts = contactRemoteService.getAllContacts()
        Log.w("REPOSITORY", "Done loading")

        // map the data from the Internet to our database
        remoteContacts.map{
            dto ->
            ContactEntity(0,dto.name, dto.telephoneNumber.toString(),dto.age,)
        }.forEach{
            entity -> contactsDao.addContact(entity)        
        }
    }

    // ...
}
  1. Until here, if we run the app, it will crash because ContactRepository(contactsDao) in ContactsApplication.kt is not correct.

in ContactsApplication.kt

class ContactsApplication : Application() { // only one instance

    // by lazy: only execute this code once, once it is accessed somewhere
    val contactRepository by lazy {
        val contactsDao = ContactsDatabase.getDatabase(this).contactsDao()

        // setup a retrofit service
        val retrofit = Retrofit.Builder()
            .baseUrl("https://my-json-server.typicode.com/GithubGenericUsername/find-your-pet/")
            // without contacts because we added it in the ContactRemoteService
            .addConverterFactory(Json { ignoreUnknownKeys = true }.asConverterFactory("application/json".toMediaType()))
            // do the serialization
            .build()

        // Create an object for us that implements this interface
        val contactRemoteService = retrofit.create(ContactRemoteService::class.java)
        
        ContactRepository(contactsDao, contactRemoteService)
    }
}
  1. Now we need to use this loadInitialContacts() in the ContactsViewModel

in ContactsViewModel.kt

    val contactsUiState = repository.contacts.stateIn(
        viewModelScope,
        SharingStarted.WhileSubscribed(5000),
        // when it should start the state, and how long it should keep it
        // it will remain 5 sec after the viewmodel scope is closed
        emptyList() // Define the initial value
    )

    // newly add
    // only call for the first time
    init {
        viewModelScope.launch {
            repository.loadInitialContacts()
        }
    }

Until now, the json file should be appended after the existing data.

Potential errors

  • apk-build-fails-with-error-failed-to-open-apk-inconsistent-information: Solution: File -> Invalidate caches & restart This is because Android Studio may keep some information to speed up the build process.

Terminology

  • API: An application programming interface (API) is a connection between computers or between computer programs. It is a type of software interface.

  • Data Transfer Object (DTO): DTOs are a crucial design pattern used to effectively transmit data from one layer to another while keeping the required data pieces in a lightweight structure.

Reflection

  • One can move database package and remote package under the data pacakage

Open Questions