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:
- 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.
- Displaying Articles: The user interface will be platform-specific. We’ll use Jetpack Compose for Android and SwiftUI for iOS.
- 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:
- 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:
// 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:
// 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:
// 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.
// 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:
// 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.
// 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.
// 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!