Kotlin and Ktor: Your Dynamic Duo for Effortless API Fetching

Hello, Devs so recently, while developing an Android app, I encountered a scenario where I needed to use Ktor for network communication. However, during the implementation process, I faced several issues and errors. I went to the internet for solutions, I found limited and outdated resources, which made resolving these issues challenging. To assist others facing similar complications, I decided to document my experience and provide a guide on successfully integrating Ktor into Android applications.

Step 1: Add the required dependency in Your App Gradle

plugins {
    // Other Plugins
    id("kotlinx-serialization")
}

// Other Code block

dependencies {
//Ktor
    implementation("io.ktor:ktor-client-core:2.3.8")
    implementation("io.ktor:ktor-client-cio:2.3.8")
    implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.8")
    implementation("io.ktor:ktor-client-content-negotiation:2.3.8")
    implementation("io.ktor:ktor-client-logging:2.3.8")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
}

Ktor Dependencies Overview:

  1. Ktor-client-core: The foundational module for the Ktor client, providing essential components for making HTTP requests.

  2. Ktor-client-cio: Core Input/Output features for the Ktor client, offering efficient coroutine-based I/O operations.

  3. Ktor-serialization-kotlinx-json: Enables seamless integration of Kotlinx serialization library for JSON serialization and deserialization in Ktor.

  4. Ktor-client-content-negotiation: Adds support for content negotiation, allowing the client to communicate with the server using various content types.

  5. Ktor-client-logging: Incorporates logging capabilities into the Ktor client, aiding in debugging and monitoring HTTP requests.

  6. kotlinx-serialization-json: Kotlinx Serialization library for JSON support, utilized by Ktor for handling JSON data during network communication.

Step 2: Create a network module

// Im using Hilt for DI
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideHttpClient(): HttpClient {
        return HttpClient(CIO) {
            install(Logging)
            install(ContentNegotiation) {
                json(
                    json = Json {
                        prettyPrint = true
                        isLenient = true
                    },
                    contentType = ContentType.Application.Json
                )
            }
            defaultRequest {
                contentType(ContentType.Application.Json)
                accept(ContentType.Application.Json)
            }
        }
    }

    @Provides
    @Singleton
    fun provideUserService(client: HttpClient): UserApiService {
        return UserApiServiceImpl(client)
    }
}

The NetworkModule is a crucial component in your Ktor-based project, designed to streamline HTTP operations and manage network-related dependencies.

Key Features:

  1. HTTP Client Configuration: The provideHttpClient function configures the Ktor HttpClient using the CIO engine, enabling efficient and asynchronous HTTP requests. It also integrates features such as request/response logging, WebSocket support, and content negotiation for seamless JSON handling.

  2. Default Request Configuration: The defaultRequest block sets default configurations for each HTTP request, ensuring a consistent structure. It specifies that the requests and responses should use JSON as the content type, with additional headers to accommodate JSON data.

  3. API Service Providers: The provideUserService function initializes a service for user-related API interactions.

This modular and organized approach to network-related functionality enhances the maintainability and extensibility of your Ktor application, making it easier to manage and scale as your project evolves.

Step 3: Create API Service Interface

interface UserApiService {
    suspend fun registerUser(@Body userRequest: UserRequest): Results<String>

    suspend fun loginWithUsername(username: String, password:String): Results<String>

    suspend fun getAllUser(): Results<List<User>>

    suspend fun deleteUser(userId: String): Results<String>
}

The UserApiService interface serves as a streamlined contract for handling user-related operations in your Ktor-based application. It defines a set of suspending functions, each catering to specific aspects of user management.

Key Functions:

  1. Register User: Initiates the user registration process by sending a UserRequest object to the server. The result is encapsulated within a Results wrapper, providing meaningful feedback, such as success or error messages.

  2. Login with Username: Facilitates user login by submitting a username and password. The response is encapsulated within a Results wrapper, ensuring effective error handling and clarity in communication.

  3. Get All Users: Retrieves a list of all users from the server. The result is encapsulated within a Results wrapper, offering a standardized approach to handle success and failure scenarios.

  4. Delete User: Triggers the deletion of a user based on the provided userId. The result is encapsulated within a Results wrapper, simplifying the interpretation of the server's response.

This well-defined interface promotes clarity, consistency, and ease of use in your application’s interactions with user-related endpoints. By encapsulating results within a standardized wrapper, it enhances error handling and provides a solid foundation for seamless integration into your Ktor project.

Step 4: Create API Service interface implementation class

class UserApiServiceImpl @Inject constructor(private val httpClient: HttpClient) : UserApiService {
    override suspend fun registerUser(userRequest: UserRequest): Results<String> {
        return try {

            val response = httpClient.post(REGISTER_USER_API) {
                setBody(userRequest)
            }

            if (response.status == HttpStatusCode.Created) {
                Results.Success("Congratulations, Welcome to ChitChat Hub Your account has been successfully created.")
            } else {
                Results.Error(response.toString())
            }

        } catch (e: Exception) {
            Results.Error(e.message.toString())
        }
    }

    override suspend fun loginWithUsername(username: String, password: String): Results<String> {
        return try {
            val response = httpClient.get("${LOGIN_WITH_USERNAME}/${username}/${password}")
            if (response.status == HttpStatusCode.OK) {
                Results.Success("User Login Successful")
            } else {
                Results.Error(response.toString())
            }
        } catch (e: Exception) {
            Results.Error(e.message.toString())
        }
    }

    override suspend fun getAllUser(): Results<List<User>> {
        return try {
            val response = httpClient.get(GET_ALL_USER)
            if (response.status == HttpStatusCode.OK) {
                val responseBody = response.bodyAsText()

                val json = Json { ignoreUnknownKeys = true }

                val userList: List<User> = json.decodeFromString(responseBody)

                Results.Success(userList)
            } else {
                Results.Error(response.toString())
            }
        } catch (e: Exception) {
            Results.Error(e.message.toString())
        }
    }

    override suspend fun deleteUser(userId: String): Results<String> {
        return try {
            val response = httpClient.get(DELETE_USER)
            if (response.status == HttpStatusCode.OK) {
                Results.Success("User Deleted Successfully")
            } else {
                Results.Error(response.toString())
            }
        } catch (e: Exception) {
            Results.Error(e.message.toString())
        }
    }
}

The UserApiServiceImpl class is the concrete implementation of the UserApiService interface, seamlessly integrating Ktor functionalities to interact with user-related APIs. This class encapsulates the logic for various user operations, ensuring robust communication between your Ktor application and the server.

Key Features:

  1. Register User: Initiates the user registration process by sending a UserRequest to the server using a POST request. The response is then analyzed, and a corresponding Results wrapper is returned, indicating success or providing an error message.

  2. Login with Username: Facilitates user login by sending a GET request with the provided username and password. The result is encapsulated within a Results wrapper, indicating whether the login was successful or any encountered errors.

  3. Get All Users: Retrieves a list of all users from the server using a GET request. The received JSON response is deserialized into a list of User objects. The results are encapsulated within a Results wrapper, providing easy handling of success and error scenarios.

  4. Delete User: Triggers the deletion of a user by sending a GET request with the specified userId. The result is encapsulated within a Results wrapper, indicating the success or failure of the deletion process.

This implementation fosters a clean separation of concerns, promoting maintainability and scalability in your Ktor application. The encapsulation of results within the Results wrapper ensures a consistent approach to error handling and enhances the overall robustness of your user-related API interactions.

And there you have it — a comprehensive guide to implementing Ktor for efficient API interactions. While we’ve covered the backend intricacies with the UserApiServiceImpl class, remember that the frontend is equally pivotal. I did not add the ViewModel and View class code here because this blog is only about how to implement the ktor code in your project.


Connect with Me:

Hey there! If you enjoyed reading this blog and found it informative, why not connect with me on LinkedIn? 😊 You can also follow my Instagram page for more mobile development-related content. 📲👨‍💻 Let’s stay connected, share knowledge and have some fun in the exciting world of app development! 🌟

Check out my Instagram page

Check out my LinkedIn

1
Subscribe to my newsletter

Read articles from Mayursinh Parmar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mayursinh Parmar
Mayursinh Parmar

📱Mobile App Developer | Android & Flutter 🌟💡 Passionate about creating intuitive and engaging apps 💭✨ Let’s shape the future of mobile technology!