KMP #02: Merancang Struktur Weather App


Di artikel sebelumnya, kita sudah berhasil membuat project Kotlin Multiplatform pertama menggunakan Android Studio. Kita juga sudah mengenal struktur commonMain, androidMain, dan iosMain, serta memahami bagaimana Kotlin Multiplatform memungkinkan kita berbagi logic antara Android dan iOS.

Kalau teman-teman belum mengikuti artikel sebelumnya, saya sarankan membacanya terlebih dahulu supaya lebih mudah mengikuti tutorial kali ini.

Pada artikel ini, kita belum akan menulis banyak kode Kotlin. Sebelum mulai mengimplementasikan fitur, ada baiknya kita memahami terlebih dahulu aplikasi seperti apa yang ingin kita buat, bagaimana aplikasi mendapatkan data cuaca, dan bagaimana data tersebut akan direpresentasikan di dalam project.

Kita akan mulai merancang Weather App yang nantinya dapat berjalan di Android dan iOS dengan pendekatan:

  • Shared logic: Kotlin Multiplatform.

  • Android UI: Jetpack Compose.

  • iOS UI: SwiftUI.

  • Networking: Ktor Client.

  • Weather API: Open-Meteo.

Seperti biasa, kita akan mengembangkan aplikasi yang sama secara bertahap, bukan membuat project baru untuk setiap artikel.

Apa yang akan kita buat?

Bayangkan kita ingin membuat aplikasi cuaca sederhana. Ketika aplikasi dibuka, pengguna bisa mencari nama kota, memilih lokasi yang sesuai, lalu melihat kondisi cuaca saat ini dan prakiraan cuaca untuk tujuh hari ke depan.

Untuk versi awal, kita akan membuat tiga bagian utama:

  1. Location Search — mencari kota berdasarkan nama.

  2. Current Weather — menampilkan kondisi cuaca saat ini.

  3. 7-Day Forecast — menampilkan prakiraan cuaca harian selama tujuh hari.

Gambaran sederhananya seperti berikut.

flowchart TB APP["Weather App"] APP --> SEARCH["Location Search"] APP --> CURRENT["Current Weather"] APP --> FORECAST["7-Day Forecast"] SEARCH --> GEO["Geocoding API"] CURRENT --> API["Forecast API"] FORECAST --> API GEO --> LOCATION["Location Data"] API --> WEATHER["Weather Data"]

Diagram ini menunjukkan hubungan antara fitur aplikasi dan API yang akan kita gunakan. Satu API digunakan untuk mencari lokasi, sedangkan API lainnya menyediakan data cuaca berdasarkan koordinat lokasi tersebut.

Untuk sementara, kita belum membahas detail UI Android dan iOS. Fokus kita adalah memastikan alur data dan struktur modelnya sudah jelas sebelum masuk ke implementasi.

Kenalan dengan Open-Meteo API

Untuk mendapatkan data cuaca, kita membutuhkan layanan yang menyediakan informasi cuaca melalui API.

Ada banyak layanan yang bisa digunakan, tetapi untuk tutorial ini saya memilih Open-Meteo.

Kenapa Open-Meteo?

  • Gratis untuk penggunaan yang sesuai dengan ketentuan layanannya.

  • Menyediakan data cuaca melalui REST API.

  • Tidak membutuhkan API key untuk penggunaan gratis yang memenuhi ketentuannya.

  • Menyediakan endpoint untuk pencarian lokasi dan prakiraan cuaca.

  • Menggunakan format response JSON yang mudah diproses menggunakan Kotlin.

Website resminya bisa teman-teman kunjungi di:

https://open-meteo.com/

Ada dua endpoint utama yang akan kita gunakan dalam tutorial ini.

EndpointFungsi
Geocoding APIMencari lokasi berdasarkan nama kota
Forecast APIMengambil data cuaca berdasarkan koordinat

Kita akan membahas keduanya satu per satu.

1. Geocoding API: Mencari Lokasi

Misalnya, pengguna mengetik Jakarta pada kolom pencarian aplikasi.

Masalahnya, API cuaca umumnya membutuhkan koordinat geografis berupa latitude dan longitude, bukan sekadar nama kota.

Di sinilah Geocoding API dibutuhkan.

Endpoint yang akan kita gunakan:

https://geocoding-api.open-meteo.com/v1/search

Kita bisa menambahkan beberapa query parameter untuk menentukan pencarian.

Contohnya:

GET https://geocoding-api.open-meteo.com/v1/search?name=Jakarta&count=5&language=id&format=json

Penjelasan parameter:

ParameterFungsi
nameNama lokasi yang dicari
countJumlah maksimum hasil yang diminta
languageBahasa untuk informasi lokasi
formatFormat response, dalam hal ini JSON

Jika request berhasil, API akan mengembalikan daftar lokasi yang cocok dengan pencarian.

Contoh response yang disederhanakan:

{
  "results": [
    {
      "id": 1642911,
      "name": "Jakarta",
      "latitude": -6.2088,
      "longitude": 106.8456,
      "country": "Indonesia",
      "admin1": "Jakarta"
    }
  ],
  "generationtime_ms": 0.2
}

Catatan: JSON di atas merupakan ilustrasi response dengan struktur yang disederhanakan. Hasil aktual, termasuk jumlah lokasi dan informasi administratifnya, bergantung pada hasil pencarian API.

Perhatikan bahwa data yang kita butuhkan bukan hanya nama kota.

Ada beberapa informasi penting di dalam response tersebut:

  • name: nama lokasi.

  • latitude: koordinat lintang.

  • longitude: koordinat bujur.

  • country: negara tempat lokasi berada.

  • admin1: informasi administratif, jika tersedia.

Latitude dan longitude inilah yang akan digunakan untuk mengambil data cuaca.

Contohnya, jika pengguna memilih Jakarta, aplikasi akan menggunakan koordinat lokasi tersebut untuk meminta data cuaca dari Forecast API.

Hal ini juga penting untuk menghindari kesalahan ketika ada beberapa kota dengan nama yang sama.

Jadi, kita tidak bisa langsung menganggap bahwa satu nama kota selalu merujuk pada satu lokasi.

2. Forecast API: Mengambil Data Cuaca

Setelah pengguna memilih lokasi, kita bisa mengambil data cuaca berdasarkan koordinatnya.

Endpoint yang akan digunakan adalah:

https://api.open-meteo.com/v1/forecast

Untuk versi awal aplikasi, kita membutuhkan informasi berikut:

  • Suhu saat ini.

  • Kelembapan relatif.

  • Suhu yang dirasakan.

  • Kode kondisi cuaca.

  • Kecepatan angin.

  • Suhu maksimum dan minimum untuk prakiraan harian.

Kita juga ingin mendapatkan prakiraan untuk tujuh hari.

Contoh request:

GET https://api.open-meteo.com/v1/forecast?latitude=-6.2088&longitude=106.8456&current=temperature_2m,relative_humidity_2m,apparent_temperature,is_day,precipitation,weather_code,wind_speed_10m&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=auto&forecast_days=7

Supaya lebih mudah dipahami, berikut penjelasan parameter utamanya.

ParameterFungsi
latitudeKoordinat lintang lokasi
longitudeKoordinat bujur lokasi
currentVariabel cuaca untuk kondisi saat ini
dailyVariabel untuk prakiraan harian
timezone=autoMenggunakan zona waktu lokasi
forecast_days=7Meminta prakiraan untuk tujuh hari

Kita menggunakan weather_code untuk mengetahui kategori kondisi cuaca, seperti cerah, berawan, atau hujan. Nantinya kode ini akan kita mapping menjadi teks dan ikon pada masing-masing UI.

Contoh response Forecast API

Response aktual memiliki lebih banyak informasi, termasuk metadata waktu dan satuan. Berikut contoh sederhana untuk menunjukkan struktur data yang akan kita olah:

{
  "latitude": -6.2088,
  "longitude": 106.8456,
  "timezone": "Asia/Jakarta",
  "current": {
    "temperature_2m": 31.2,
    "relative_humidity_2m": 68,
    "apparent_temperature": 35.1,
    "is_day": 1,
    "precipitation": 0.0,
    "weather_code": 2,
    "wind_speed_10m": 12.5
  },
  "daily": {
    "time": [
      "2026-10-15",
      "2026-10-16",
      "2026-10-17"
    ],
    "weather_code": [2, 3, 61],
    "temperature_2m_max": [32.1, 31.5, 30.8],
    "temperature_2m_min": [25.2, 24.9, 24.7]
  }
}

Angka suhu, kelembapan, dan nilai cuaca di atas hanya contoh ilustratif, bukan hasil pengambilan data real-time. Daftar tanggal juga dipersingkat agar mudah dibaca.

Ada dua bagian yang perlu kita perhatikan.

Pertama, current.

Bagian ini menyimpan informasi kondisi cuaca saat ini. Kita akan menggunakan data tersebut untuk menampilkan suhu utama, kondisi cuaca, kelembapan, dan kecepatan angin.

Kedua, daily.

Bagian ini berisi beberapa array yang saling berhubungan berdasarkan indeks. Misalnya, elemen pertama pada time, weather_code, temperature_2m_max, dan temperature_2m_min merujuk pada hari yang sama.

Untuk aplikasi kita, data tersebut nantinya akan diubah menjadi daftar prakiraan harian yang lebih mudah digunakan oleh UI.

Perlu diingat juga bahwa current merupakan kondisi cuaca terkini yang disediakan oleh API, bukan jaminan pengukuran langsung dari sensor di lokasi pengguna.

Memahami Alur Data Weather App

Sekarang kita sudah mengenal dua endpoint yang akan digunakan. Mari kita hubungkan keduanya dalam satu alur aplikasi.

sequenceDiagram actor User as Pengguna participant App as Weather App participant Geo as Geocoding API participant Forecast as Forecast API User->>App: Memasukkan nama kota App->>Geo: Cari lokasi Geo-->>App: Daftar lokasi dan koordinat App-->>User: Tampilkan hasil pencarian User->>App: Memilih lokasi App->>Forecast: Request cuaca berdasarkan koordinat Forecast-->>App: Current weather dan daily forecast App-->>User: Tampilkan kondisi cuaca

Ada beberapa hal penting dalam alur ini.

Pertama, aplikasi tidak langsung meminta data cuaca ketika pengguna mengetik nama kota. Kita perlu mencari lokasi terlebih dahulu.

Kedua, pengguna harus bisa memilih lokasi yang sesuai apabila pencarian menghasilkan beberapa pilihan.

Ketiga, setelah lokasi dipilih, aplikasi dapat menggunakan koordinatnya untuk mengambil data cuaca.

Keempat, hasil API tidak akan langsung ditampilkan begitu saja. Data JSON perlu diparsing, dipetakan ke model aplikasi, lalu diteruskan ke UI Android atau iOS.

Bagian terakhir inilah yang akan kita persiapkan melalui desain model data.

3. Merancang Model Data

Sekarang kita masuk ke bagian Kotlin Multiplatform.

Kita sudah mengetahui informasi yang dibutuhkan aplikasi. Pertanyaannya, bagaimana sebaiknya kita merepresentasikan data tersebut dalam kode Kotlin?

Untuk versi awal, saya akan menggunakan tiga model utama:

  1. Location

  2. CurrentWeather

  3. DailyForecast

Ketiganya akan ditempatkan pada shared module karena merupakan representasi data yang dibutuhkan oleh logic aplikasi di Android maupun iOS.

Location

Model Location merepresentasikan lokasi yang dipilih pengguna.

Contoh implementasi:

data class Location(
    val id: Long?,
    val name: String,
    val country: String,
    val adminArea: String?,
    val latitude: Double,
    val longitude: Double
)

Penjelasan beberapa property:

  • id: identifier lokasi jika tersedia.

  • name: nama kota atau lokasi.

  • country: nama negara.

  • adminArea: informasi administratif tambahan, jika tersedia.

  • latitude: koordinat lintang.

  • longitude: koordinat bujur.

Kenapa kita menggunakan Double untuk koordinat?

Karena latitude dan longitude bisa memiliki nilai desimal. Jika kita menggunakan integer, informasi koordinat akan kehilangan presisi.

Sementara itu, id dan adminArea dibuat nullable karena informasi tersebut tidak selalu tersedia dalam setiap sumber data.

CurrentWeather

Model berikutnya adalah CurrentWeather.

Model ini menyimpan informasi kondisi cuaca saat ini.

data class CurrentWeather(
    val temperature: Double,
    val apparentTemperature: Double,
    val relativeHumidity: Int,
    val isDay: Boolean,
    val precipitation: Double,
    val weatherCode: Int,
    val windSpeed: Double
)

Beberapa property mungkin sudah cukup familiar.

temperature merupakan suhu udara, sedangkan apparentTemperature merupakan suhu yang dirasakan berdasarkan perhitungan API.

relativeHumidity menunjukkan kelembapan relatif dalam persen.

isDay digunakan untuk membedakan kondisi siang dan malam berdasarkan data API.

Sementara itu, weatherCode akan menjadi dasar untuk menentukan deskripsi kondisi cuaca dan ikon yang ditampilkan pada UI.

Contohnya, kode cuaca tertentu bisa dipetakan menjadi teks seperti "Cerah", "Berawan", atau "Hujan".

Namun, mapping tersebut belum kita implementasikan sekarang. Kita akan membahasnya ketika mulai membangun UI cuaca.

Perlu diperhatikan bahwa nama property pada model aplikasi tidak harus sama persis dengan nama field pada response JSON. Kita bisa menggunakan mapper agar struktur API tidak terlalu terikat dengan struktur model domain.

DailyForecast

Model ketiga adalah DailyForecast.

data class DailyForecast(
    val date: String,
    val weatherCode: Int,
    val temperatureMax: Double,
    val temperatureMin: Double
)

Model ini merepresentasikan prakiraan cuaca untuk satu hari.

Contohnya, satu objek DailyForecast dapat berisi tanggal, kode kondisi cuaca, suhu maksimum, dan suhu minimum.

Dengan struktur seperti ini, kita tidak perlu mengelola beberapa array secara terpisah ketika menampilkan daftar prakiraan cuaca.

Nantinya, data daily dari API akan dipetakan menjadi List<DailyForecast>.

4. Bagaimana Model Data Ini Digunakan?

Ketiga model tadi memiliki tanggung jawab masing-masing.

classDiagram class Location { +Long? id +String name +String country +String? adminArea +Double latitude +Double longitude } class CurrentWeather { +Double temperature +Double apparentTemperature +Int relativeHumidity +Boolean isDay +Double precipitation +Int weatherCode +Double windSpeed } class DailyForecast { +String date +Int weatherCode +Double temperatureMax +Double temperatureMin } class WeatherRepository { +searchLocation(name) +getWeather(latitude, longitude) } WeatherRepository ..> Location : returns search results WeatherRepository ..> CurrentWeather : returns current data WeatherRepository ..> DailyForecast : returns daily list

Diagram tersebut merupakan gambaran konseptual, bukan implementasi class yang sudah lengkap.

WeatherRepository nantinya bertanggung jawab menyediakan data yang dibutuhkan aplikasi. UI tidak perlu mengetahui detail URL, query parameter, ataupun struktur response dari Open-Meteo.

Sebagai contoh, UI cukup meminta data cuaca untuk lokasi yang dipilih. Detail bagaimana request dibuat dan bagaimana JSON diproses akan menjadi tanggung jawab layer data.

Pemisahan ini akan membuat shared module lebih mudah dikembangkan dan diuji.

5. Menentukan Struktur Shared Module

Sebelum mulai menambahkan dependency Ktor, kita juga perlu menentukan di mana kode-kode tersebut akan diletakkan.

Untuk tutorial ini, kita akan menggunakan struktur yang memisahkan domain, data, dan network.

Gambaran struktur yang akan kita kembangkan:

shared/
└── src/
    ├── commonMain/
    │   └── kotlin/
    │       └── com/example/weather/
    │           ├── domain/
    │           │   ├── model/
    │           │   │   ├── Location.kt
    │           │   │   ├── CurrentWeather.kt
    │           │   │   └── DailyForecast.kt
    │           │   └── repository/
    │           │       └── WeatherRepository.kt
    │           │
    │           ├── data/
    │           │   ├── remote/
    │           │   │   ├── WeatherApiClient.kt
    │           │   │   └── dto/
    │           │   ├── mapper/
    │           │   └── repository/
    │           │       └── WeatherRepositoryImpl.kt
    │           │
    │           └── domain/
    │               └── usecase/
    │
    ├── androidMain/
    └── iosMain/

Struktur ini adalah target organisasi kode yang akan kita bangun secara bertahap. Tidak semua file perlu dibuat sekarang.

Ada tiga bagian penting yang perlu dipahami.

Domain layer

Berisi model domain dan kontrak repository. Nantinya, use case juga ditempatkan di sini untuk merepresentasikan operasi yang dibutuhkan aplikasi.

Data layer

Berisi implementasi repository, DTO untuk response API, serta mapper yang mengubah data dari API menjadi model domain.

Remote data source

Berisi kode untuk berkomunikasi dengan Open-Meteo API. Di sinilah WeatherApiClient akan menggunakan Ktor Client pada artikel berikutnya.

Semua bagian tersebut akan kita usahakan tetap berada di commonMain selama tidak membutuhkan API khusus platform.

Dengan begitu, logic yang sama dapat digunakan oleh aplikasi Android dan iOS.

6. Memisahkan DTO dan Domain Model

Mungkin teman-teman bertanya, kenapa kita membutuhkan DTO jika sudah memiliki model Location, CurrentWeather, dan DailyForecast?

DTO atau Data Transfer Object digunakan untuk merepresentasikan struktur data yang diterima dari API.

Sebagai contoh, response Open-Meteo menggunakan nama field seperti:

{
  "temperature_2m": 31.2,
  "apparent_temperature": 35.1,
  "relative_humidity_2m": 68,
  "weather_code": 2
}

Sedangkan model domain kita menggunakan nama property yang lebih sesuai dengan kebutuhan aplikasi:

data class CurrentWeather(
    val temperature: Double,
    val apparentTemperature: Double,
    val relativeHumidity: Int,
    val isDay: Boolean,
    val precipitation: Double,
    val weatherCode: Int,
    val windSpeed: Double
)

Keduanya memiliki tujuan yang berbeda.

DTO mengikuti kontrak API, sedangkan domain model merepresentasikan data yang dibutuhkan oleh aplikasi.

Nantinya, mapper akan bertugas mengubah DTO menjadi domain model. Dengan cara ini, perubahan pada struktur response API tidak harus langsung memengaruhi UI Android dan iOS.

Kita juga dapat menambahkan aturan validasi dan transformasi data di tempat yang tepat.

7. Bagaimana Android dan iOS Menggunakan Data yang Sama?

Salah satu tujuan utama tutorial ini adalah berbagi logic tanpa menyatukan UI.

Karena itu, kita tidak akan membuat satu UI yang digunakan bersama oleh Android dan iOS.

Sebaliknya, kedua platform akan menggunakan model dan logic yang sama dari shared module.

flowchart TB API["Open-Meteo API"] subgraph SHARED["Shared Module - Kotlin"] CLIENT["WeatherApiClient"] REPO["WeatherRepository"] MODEL["Domain Models"] CLIENT --> REPO REPO --> MODEL end subgraph ANDROID["Android App"] AVM["WeatherViewModel"] AUI["Jetpack Compose UI"] AVM --> AUI end subgraph IOS["iOS App"] IVM["WeatherObservable"] IUI["SwiftUI"] IVM --> IUI end API --> CLIENT MODEL --> AVM MODEL --> IVM

Pada Android, data akan dikonsumsi melalui ViewModel dan ditampilkan menggunakan Jetpack Compose.

Pada iOS, data yang sama akan digunakan oleh wrapper atau observable object di sisi Swift, kemudian ditampilkan melalui SwiftUI.

Jadi, kita tidak perlu membuat ulang request API dan transformasi data hanya karena UI-nya berbeda.

Tentu saja, integrasi coroutine, StateFlow, dan Kotlin-to-Swift interop akan kita bahas pada artikel-artikel berikutnya.

Untuk sekarang, cukup pahami bahwa shared module menyediakan data dan logic, sedangkan setiap platform menentukan bagaimana data tersebut ditampilkan kepada pengguna.

8. Hal yang Perlu Diperhatikan Sebelum Coding

Sebelum menutup artikel ini, ada beberapa keputusan desain yang perlu kita ingat ketika mulai mengimplementasikan API.

Pertama, pencarian lokasi dapat menghasilkan lebih dari satu hasil.

Karena itu, aplikasi perlu menampilkan daftar lokasi dan membiarkan pengguna memilih hasil yang benar.

Kedua, koordinat merupakan bagian penting dari pemilihan lokasi.

Jangan hanya menyimpan nama kota. Latitude dan longitude dibutuhkan untuk meminta data cuaca yang sesuai.

Ketiga, response API bukan domain model.

Gunakan DTO dan mapper untuk memisahkan struktur response dari model yang digunakan aplikasi.

Keempat, perhatikan satuan data.

Suhu, kecepatan angin, dan variabel cuaca lainnya memiliki satuan yang perlu dipahami. Jangan mengasumsikan satuan tanpa memeriksa dokumentasi API.

Kelima, jangan mengikat UI pada format response API.

Baik Jetpack Compose maupun SwiftUI sebaiknya menggunakan data yang sudah diproses oleh shared module.

Dengan prinsip-prinsip tersebut, kita mempunyai fondasi yang lebih jelas sebelum mulai menulis networking code.

Kesimpulan

Pada artikel ketiga ini, kita sudah merancang fondasi Weather App yang akan dikembangkan sepanjang seri tutorial Kotlin Multiplatform.

Kita sudah membahas:

  • Open-Meteo Geocoding API untuk mencari lokasi.

  • Open-Meteo Forecast API untuk mendapatkan data cuaca.

  • Alur pencarian lokasi hingga menampilkan prakiraan cuaca.

  • Model domain Location, CurrentWeather, dan DailyForecast.

  • Pemisahan DTO, mapper, repository, dan domain model.

  • Struktur awal shared module untuk Android dan iOS.

Kita memang belum menulis implementasi networking. Namun, sekarang kita sudah memahami data apa yang dibutuhkan dan bagaimana data tersebut akan mengalir di dalam aplikasi.

Pada artikel berikutnya, kita akan mulai masuk ke bagian yang lebih teknis: Setup Networking dengan Ktor Client di Shared Module.

Kita akan menambahkan dependency yang diperlukan, mengonfigurasi HTTP engine untuk Android dan iOS, lalu mulai membuat WeatherApiClient untuk mengambil data dari Open-Meteo.

Jadi, pastikan project dari artikel sebelumnya tetap tersimpan karena kita akan melanjutkan pengembangan dari project yang sama.

Referensi:

Source code: Repository GitHub Weather App KMP akan menjadi tempat kita menyimpan progress project berdasarkan setiap artikel. Tambahkan link repository setelah source code artikel ini tersedia.

Sekian, terima kasih.

0 Komentar

Post a Comment

Don't be a Silent Reader!
Please leave your comments, critiques or suggestions.
Because your opinion means a lot for this blog :)

← Previous Post Next Post → Home