Kotlin Multiplatform Mobile (KMM) Success Story: Effortlessly Build a High-Performance News Application

Table of Contents

In this blog, we’ll walk through a practical use case for Kotlin Multiplatform Mobile (KMM) by creating a simple News Application. This app will fetch and display the latest news articles from a remote API, with shared business logic across Android and iOS platforms, but with platform-specific UI components to maintain the native look and feel.

The key components of the app will include:

  1. Fetching News Data: The app will retrieve news articles from an API. This will be done using shared code, so both Android and iOS will use the same logic for data retrieval.
  2. Displaying Articles: The user interface will be platform-specific. We’ll use Jetpack Compose for Android and SwiftUI for iOS.
  3. Modeling the Data: We’ll define common data models to represent the articles, shared across both platforms.

Setting Up the KMM Project

If you’re new to KMM, setting up a project might seem daunting, but it’s pretty straightforward.

Step 1: Create the KMM Project

First, open Android Studio and create a new Kotlin Multiplatform Mobile (KMM) project.

  • Choose a KMM Project Template: Select a KMM template to generate the shared and platform-specific code.
  • Configure Android & iOS Modules: Android Studio will automatically create separate modules for Android (androidApp) and iOS (iosApp), as well as the shared module where you’ll write most of the logic.

Once your project structure is in place, you’ll have something like this:

Kotlin
- MyNewsApp/
    - androidApp/         # Android-specific UI code
    - iosApp/             # iOS-specific UI code
    - shared/             # Shared business logic
        - src/
            - commonMain/  # Shared logic between Android and iOS
            - androidMain/ # Android-specific logic
            - iosMain/     # iOS-specific logic

Fetching News Articles (Shared Logic)

The first thing we need is a way to retrieve news articles from a public API. For this use case, let’s assume we’re using the News API, which provides a free service to fetch the latest articles.

Step 1: Create a News Data Model

In the shared module, create a data class that will represent a news article:

Kotlin
// shared/src/commonMain/kotlin/com/mynewsapp/models/NewsArticle.kt
package com.mynewsapp.models

data class NewsArticle(
    val title: String,
    val description: String,
    val url: String,
    val urlToImage: String?
)

Step 2: Implement the News API Client

Next, we’ll implement the logic for fetching news from the API. We’ll use Ktor (a Kotlin networking library) to make the network request, which works on both Android and iOS.

First, add Ktor as a dependency in the shared module:

Kotlin
// shared/build.gradle.kts
kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-core:2.x.x")
                implementation("io.ktor:ktor-client-json:2.x.x")
                implementation("io.ktor:ktor-client-serialization:2.x.x")
            }
        }
    }
}

Then, write the API client to fetch the latest news:

Kotlin
// shared/src/commonMain/kotlin/com/mynewsapp/network/NewsApiClient.kt
package com.mynewsapp.network

import com.mynewsapp.models.NewsArticle
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.logging.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

class NewsApiClient {
    private val client = HttpClient {
        install(JsonFeature) {
            serializer = KotlinxSerializer(Json { ignoreUnknownKeys = true })
        }
        install(Logging) {
            level = LogLevel.INFO
        }
    }

    suspend fun getTopHeadlines(): List<NewsArticle> {
        val response: NewsApiResponse = client.get("https://newsapi.org/v2/top-headlines?country=us&apiKey=YOUR_API_KEY")
        return response.articles.map {
            NewsArticle(
                title = it.title,
                description = it.description,
                url = it.url,
                urlToImage = it.urlToImage
            )
        }
    }
}

@Serializable
data class NewsApiResponse(val articles: List<ArticleDto>)

@Serializable
data class ArticleDto(
    val title: String,
    val description: String,
    val url: String,
    val urlToImage: String?
)

The NewsApiClient class handles the network request to fetch top news headlines. It parses the JSON response into Kotlin data models, which are then used throughout the app.

Step 3: Handle Business Logic in a Repository

To keep things organized, let’s create a NewsRepository that will manage data retrieval and handle any business logic related to articles.

Kotlin
// shared/src/commonMain/kotlin/com/mynewsapp/repository/NewsRepository.kt
package com.mynewsapp.repository

import com.mynewsapp.models.NewsArticle
import com.mynewsapp.network.NewsApiClient

class NewsRepository(private val apiClient: NewsApiClient) {
    suspend fun getTopHeadlines(): List<NewsArticle> {
        return apiClient.getTopHeadlines()
    }
}

Now, both the Android and iOS apps will use this repository to get the latest news.

Note – To keep it simple, I haven’t gone into much detail about different architectures like MVVM in Android and VIPER in iOS. While these architectures usually include an additional layer like the ViewModel in Android MVVM or the Presenter in VIPER, I have directly used the repository here to simplify the implementation, though it’s typically recommended to use those layers for better separation of concerns.

Displaying News on Android (Jetpack Compose)

Now that we’ve handled the shared logic for fetching news articles, let’s build the Android user interface using Jetpack Compose.

Step 1: Set Up Compose in the Android Module

Ensure you’ve added the necessary Compose dependencies in the androidApp module:

Kotlin
// androidApp/build.gradle.kts
dependencies {
    implementation("androidx.compose.ui:ui:1.x.x")
    implementation("androidx.compose.material:material:1.x.x")
    implementation("androidx.compose.ui:ui-tooling:1.x.x")
    implementation("androidx.activity:activity-compose:1.x.x")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.x.x")
}

Step 2: Create the UI in Compose

In the Android module, create a simple UI that fetches and displays the news articles using Jetpack Compose.

Kotlin
// androidApp/src/main/java/com/mynewsapp/NewsActivity.kt
package com.mynewsapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.rememberImagePainter
import com.mynewsapp.repository.NewsRepository
import kotlinx.coroutines.launch

class NewsActivity : ComponentActivity() {
    private val newsRepository = NewsRepository(NewsApiClient())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsApp()
        }
    }

    @Composable
    fun NewsApp() {
        val newsArticles = remember { mutableStateOf<List<NewsArticle>?>(null) }
        val coroutineScope = rememberCoroutineScope()

        LaunchedEffect(Unit) {
            coroutineScope.launch {
                val articles = newsRepository.getTopHeadlines()
                newsArticles.value = articles
            }
        }

        Scaffold(
            topBar = { TopAppBar(title = { Text("Latest News") }) }
        ) {
            NewsList(newsArticles.value)
        }
    }

    @Composable
    fun NewsList(newsArticles: List<NewsArticle>?) {
        LazyColumn {
            newsArticles?.let { articles ->
                items(articles.size) { index ->
                    NewsItem(articles[index])
                }
            }
        }
    }

    @Composable
    fun NewsItem(article: NewsArticle) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(article.title, style = MaterialTheme.typography.h6)
            Spacer(modifier = Modifier.height(8.dp))
            article.urlToImage?.let {
                Image(
                    painter = rememberImagePainter(data = it),
                    contentDescription = null,
                    modifier = Modifier.height(180.dp).fillMaxWidth(),
                    contentScale = ContentScale.Crop
                )
            }
            Spacer(modifier = Modifier.height(8.dp))
            Text(article.description ?: "", style = MaterialTheme.typography.body2)
        }
    }
}

Here, we use Jetpack Compose to create a list of news articles. The NewsItem composable displays each article’s title, description, and image, fetching the data from the NewsRepository.

Displaying News on iOS (SwiftUI)

Let’s now build the UI for iOS using SwiftUI. The data retrieval logic remains the same, as we’ve already handled it in the shared module.

Step 1: Set Up the SwiftUI View

In the iOS module, create a SwiftUI view that will display the news articles.

Swift
// iosApp/NewsView.swift
import SwiftUI
import shared

struct NewsView: View {
    @State private var newsArticles: [NewsArticle] = []
    let repository = NewsRepository(apiClient: NewsApiClient())

    var body: some View {
        NavigationView {
            List(newsArticles, id: \.title) { article in
                NewsRow(article: article)
            }
            .navigationTitle("Latest News")
            .onAppear {
                repository.getTopHeadlines { articles, error in
                    if let articles = articles {
                        self.newsArticles = articles
                    }
                }
            }
        }
    }
}

struct NewsRow: View {
    var article: NewsArticle

    var body: some View {
        VStack(alignment: .leading) {
            Text(article.title).font(.headline)
            if let urlToImage = article.urlToImage {
                AsyncImage(url: URL(string: urlToImage)) { image in
                    image.resizable()
                } placeholder: {
                    ProgressView()
                }
                .frame(height: 200)
                .cornerRadius(10)
            }
            Text(article.description ?? "").font(.subheadline)
        }
        .padding()
    }
}

struct NewsView_Previews: PreviewProvider {
    static var previews: some View {
        NewsView()
    }
}

In SwiftUI, we use a List to display the news articles. Similar to Compose, the NewsRow component shows the title, image, and description of each article.

Testing and Running the App

Once you’ve implemented the UI for both Android and iOS, you can now test and run your news app.

  • For Android: Simply run the androidApp module using an Android emulator or a real Android device via Android Studio.
  • For iOS: Open the iosApp module in Xcode and run the app on an iPhone simulator or physical device.

You’ll have a fully functional news app running on both platforms with shared logic for fetching news and platform-specific UI for displaying it.

Conclusion

This KMM use case demonstrates the power and flexibility of Kotlin Multiplatform Mobile. With shared business logic and platform-specific UI, you get the best of both worlds: a streamlined codebase with the flexibility to create native experiences on each platform.

By using KMM, you save time and effort by writing shared code once, which allows you to focus on crafting unique user experiences tailored to Android and iOS. This approach also reduces duplication, helps prevent bugs, and makes maintaining and scaling your app easier in the long run.

Now that you’ve got the basics down, why not extend this news app with more features, like categories or offline caching? With KMM, the possibilities are endless!

Skill Up: Software & AI Updates!

Receive our latest insights and updates directly to your inbox

Related Posts

error: Content is protected !!