Pertemuan 14

Latihan News App dengan REST API

Nama  : Triana Velia Hutabalian
NRP   : 5025231190
Kelas  : PPB (B)

Pada latihan ini, saya membuat aplikasi News App menggunakan Kotlin dan Jetpack Compose di Android Studio. Aplikasi ini menampilkan berita terkini dari REST API menggunakan layanan NewsAPI.org dengan menerapkan arsitektur MVVM (Model-View-ViewModel). Konsep utama yang diterapkan meliputi pengambilan data dari network menggunakan Retrofit, pemodelan UI state reaktif dengan StateFlow dan collectAsState(), pemisahan lapisan data menggunakan Repository Pattern, navigasi antar layar dengan Navigation Compose, serta pemuatan gambar secara asinkron menggunakan Coil.

1
Membuat project baru di Android Studio dengan template Empty Activity (Compose), nama project NewsApp, package com.example.newsapp, bahasa Kotlin, dan Minimum SDK API 26.
2
Menambahkan dependency di app/build.gradle.kts yaitu lifecycle-viewmodel-compose:2.9.0, navigation-compose:2.9.0, retrofit:2.11.0, converter-gson:2.11.0, kotlinx-coroutines-android:1.10.2, dan coil-compose:2.7.0, kemudian melakukan Sync Gradle.
3
Menambahkan izin internet di AndroidManifest.xml dengan menambahkan tag <uses-permission android:name="android.permission.INTERNET"/> di atas blok <application> agar aplikasi dapat mengakses jaringan.
4
Membuat package data/model/ dan mendefinisikan dua data class: Article berisi properti title, description, content, author, urlToImage, dan publishedAt; serta NewsResponse berisi status, totalResults, dan articles: List<Article> sebagai model dari respons JSON API.
5
Membuat package data/remote/ dan mendefinisikan ApiService berupa interface Retrofit dengan anotasi @GET("top-headlines") dan fungsi suspend getTopHeadlines() yang menerima parameter country dan apiKey via @Query, mengembalikan NewsResponse.
6
Membuat RetrofitClient sebagai object (Singleton) dengan BASE_URL = "https://newsapi.org/v2/", menggunakan GsonConverterFactory untuk parsing JSON, dan menginisialisasi apiService secara lazy melalui Retrofit.Builder().
7
Membuat package data/repository/ dan membuat NewsRepository sebagai layer perantara antara data source dan ViewModel, berisi fungsi suspend getNews() yang memanggil RetrofitClient.apiService.getTopHeadlines() dengan API key dari NewsAPI.org.
8
Membuat package ui/state/ dan mendefinisikan NewsUiState sebagai sealed class dengan tiga kondisi: Loading (object), Success (data class berisi List<Article>), dan Error (data class berisi message: String) untuk merepresentasikan seluruh kemungkinan state UI.
9
Membuat package ui/viewmodel/ dan membuat NewsViewModel yang mengextend ViewModel. ViewModel menyimpan MutableStateFlow<NewsUiState> yang diekspos sebagai StateFlow, memanggil repository.getNews() dalam viewModelScope.launch, dan menangani exception untuk memperbarui state menjadi Error.
10
Membuat package ui/components/ dan membuat composable NewsCard berupa Card yang clickable. Setiap kartu menampilkan gambar artikel menggunakan AsyncImage dari Coil dengan tinggi 200.dp, diikuti judul artikel dengan MaterialTheme.typography.titleMedium.
11
Membuat composable HomeScreen di package ui/screens/ yang mengonsumsi uiState via collectAsState() dan merender tiga kondisi: CircularProgressIndicator saat Loading, LazyColumn berisi NewsCard saat Success, serta teks error dan tombol "Retry" saat Error.
12
Membuat composable DetailScreen yang menerima objek Article dan menampilkan gambar penuh (250.dp), judul dengan headlineSmall, deskripsi, dan konten artikel dalam Column dengan verticalScroll agar dapat di-scroll.
13
Membuat NavGraph di package ui/navigation/ menggunakan NavHost dengan dua route: "home" dan "detail/{article}". Data artikel dikirim antar screen dengan cara meng-encode objek ke JSON menggunakan Gson lalu meng-encode URL-nya dengan URLEncoder.
14
Memperbarui MainActivity untuk memanggil AppNavGraph() di dalam setContent { MaterialTheme { } }, menggantikan boilerplate Greeting yang dibuat secara default oleh Android Studio.
MVVM Architecture

Arsitektur yang memisahkan UI (View), logika bisnis (ViewModel), dan data (Model/Repository), sehingga kode lebih terstruktur, mudah diuji, dan lifecycle-aware.

Retrofit & REST API

Library HTTP client untuk Android yang mengubah HTTP API menjadi interface Kotlin. Mendukung fungsi suspend untuk integrasi langsung dengan Kotlin Coroutines.

StateFlow & Sealed Class

StateFlow sebagai observable state holder yang reaktif. sealed class untuk merepresentasikan UI state secara exhaustive: Loading, Success, dan Error.

Repository Pattern

Layer abstraksi antara ViewModel dan sumber data, memudahkan penggantian data source (network/database) tanpa mengubah logika ViewModel.

Navigation Compose

Library navigasi resmi untuk Jetpack Compose menggunakan NavHost dan NavController. Mendukung passing data antar composable melalui route argument.

Coil & AsyncImage

Library image loading modern untuk Android dan Compose. AsyncImage memuat gambar dari URL secara asinkron dengan dukungan placeholder, caching, dan transformasi.

data/model/Article.kt
package com.example.newsapp.data.model

data class Article(
    val title: String,
    val description: String?,
    val content: String?,
    val author: String?,
    val urlToImage: String?,
    val publishedAt: String
)

data class NewsResponse(
    val status: String,
    val totalResults: Int,
    val articles: List<Article>
)
data/remote/ApiService.kt & RetrofitClient.kt
package com.example.newsapp.data.remote

import retrofit2.http.GET
import retrofit2.http.Query

interface ApiService {
    @GET("top-headlines")
    suspend fun getTopHeadlines(
        @Query("country") country: String = "us",
        @Query("apiKey") apiKey: String
    ): NewsResponse
}

object RetrofitClient {
    private const val BASE_URL = "https://newsapi.org/v2/"

    val apiService: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}
ui/state/NewsUiState.kt & data/repository/NewsRepository.kt
// ui/state/NewsUiState.kt
sealed class NewsUiState {
    object Loading : NewsUiState()
    data class Success(val articles: List<Article>) : NewsUiState()
    data class Error(val message: String) : NewsUiState()
}

// data/repository/NewsRepository.kt
class NewsRepository {
    suspend fun getNews() =
        RetrofitClient.apiService.getTopHeadlines(
            apiKey = "YOUR_API_KEY"
        )
}
ui/viewmodel/NewsViewModel.kt
package com.example.newsapp.ui.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class NewsViewModel : ViewModel() {

    private val repository = NewsRepository()

    private val _uiState = MutableStateFlow<NewsUiState>(NewsUiState.Loading)
    val uiState = _uiState.asStateFlow()

    init { loadNews() }

    fun loadNews() {
        viewModelScope.launch {
            try {
                _uiState.value = NewsUiState.Loading
                val response = repository.getNews()
                _uiState.value = NewsUiState.Success(response.articles)
            } catch (e: Exception) {
                _uiState.value = NewsUiState.Error(e.message ?: "Unknown Error")
            }
        }
    }
}
ui/screens/HomeScreen.kt
@Composable
fun HomeScreen(
    viewModel: NewsViewModel,
    onDetailClick: (Article) -> Unit
) {
    val state by viewModel.uiState.collectAsState()

    when (state) {
        is NewsUiState.Loading -> {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                CircularProgressIndicator()
            }
        }
        is NewsUiState.Success -> {
            val articles = (state as NewsUiState.Success).articles
            LazyColumn {
                items(articles) { article ->
                    NewsCard(article = article) { onDetailClick(article) }
                }
            }
        }
        is NewsUiState.Error -> {
            val error = state as NewsUiState.Error
            Column(horizontalAlignment = Alignment.CenterHorizontally) {
                Text(error.message)
                Button(onClick = { viewModel.loadNews() }) { Text("Retry") }
            }
        }
    }
}
ui/navigation/NavGraph.kt
@Composable
fun AppNavGraph() {
    val navController = rememberNavController()
    val viewModel = viewModel<NewsViewModel>()

    NavHost(navController = navController, startDestination = "home") {
        composable("home") {
            HomeScreen(viewModel = viewModel) { article ->
                val json = Gson().toJson(article)
                val encoded = URLEncoder.encode(json, "UTF-8")
                navController.navigate("detail/$encoded")
            }
        }
        composable("detail/{article}") { backStackEntry ->
            val json = backStackEntry.arguments?.getString("article") ?: return@composable
            val article = Gson().fromJson(json, Article::class.java)
            DetailScreen(article = article)
        }
    }
}
Hasil Output
NewsApp — Android Studio · Pixel 6 API 34 · News App MVVM + Retrofit
Screenshot NewsApp - Tampilan daftar berita terkini dari NewsAPI menggunakan arsitektur MVVM, Retrofit, dan Jetpack Compose pada emulator Pixel 6 API 34

Komentar

Postingan populer dari blog ini

Pertemuan 4&5 PPB (B) - 25/03/2026

Pertemuan 2 PPB (B) - 04/03/2026

Pertemuan 7 - 08/04/2026