Pertemuan 11

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

Pada pertemuan kesebelas ini, kami membuat aplikasi MarketSiswa menggunakan Kotlin dan Jetpack Compose di Android Studio. Aplikasi ini merupakan marketplace sederhana berbasis Material Design 3 yang memungkinkan siswa menjual dan melihat produk. Konsep utama yang diterapkan meliputi pengelolaan State dengan remember dan mutableStateListOf, navigasi antar halaman menggunakan Scaffold, komponen Bottom Navigation Bar, Floating Action Button, serta Snackbar untuk notifikasi pengguna.

1
Membuat project baru di Android Studio dengan template Empty Activity (Jetpack Compose), nama project MarketSiswa, package com.example.marketsiswa, dan Minimum SDK API 24.
2
Menambahkan dependency tambahan di app/build.gradle.kts, yaitu material-icons-extended untuk ikon-ikon tambahan dan kotlinx-coroutines-android untuk simulasi proses loading asinkron, lalu melakukan Gradle Sync.
3
Membuat data class Product dengan properti id (auto-generated dari timestamp), name, price, dan description sebagai model data produk yang digunakan di seluruh aplikasi.
4
Membangun MainScreen menggunakan Scaffold dari Material3 yang mencakup: CenterAlignedTopAppBar dengan judul dinamis, NavigationBar dengan dua item (Home dan Profile), serta ExtendedFloatingActionButton untuk navigasi ke halaman tambah produk.
5
Menginisialisasi tiga produk awal (Brownies Lumer, Kaos Custom, Jasa Print Tugas) menggunakan LaunchedEffect(Unit) agar data muncul saat aplikasi pertama kali dibuka, tanpa mengulang inisialisasi saat recomposition.
6
Membuat HomeScreen menggunakan LazyColumn untuk menampilkan daftar produk secara efisien. Setiap produk ditampilkan dalam komponen Card Material3 dengan nama, harga berwarna ungu, deskripsi singkat, dan tombol Beli.
7
Membuat AddProductScreen berisi tiga OutlinedTextField untuk input nama, harga (dengan KeyboardType.Number), dan deskripsi. Tombol Simpan menampilkan CircularProgressIndicator selama 1 detik simulasi loading menggunakan delay(1000) dan Coroutine.
8
Setelah produk berhasil ditambahkan, aplikasi kembali ke Home dan menampilkan Snackbar konfirmasi "✅ Produk berhasil ditambahkan!" melalui snackbarHostState.showSnackbar() yang dipanggil dalam coroutine scope.
9
Membuat ProfileScreen yang menampilkan informasi siswa (nama dan kelas) dengan ikon AccountCircle berukuran besar berwarna ungu sebagai avatar.
10
Mendefinisikan MarketplaceTheme menggunakan lightColorScheme Material Design 3 dengan warna primary ungu #6200EE dan secondary teal #03DAC6, lalu menjalankan aplikasi di emulator Pixel 6 API 34.
Scaffold & Material3

Struktur layout utama Material Design 3 yang menyediakan slot terintegrasi untuk TopBar, BottomBar, FAB, Snackbar, dan konten utama.

mutableStateListOf

State untuk list yang reaktif — penambahan item otomatis memicu recomposition sehingga UI diperbarui tanpa kode tambahan.

LazyColumn

Komponen list efisien yang hanya me-render item yang terlihat di layar, cocok untuk daftar produk yang terus bertambah dinamis.

NavigationBar

Komponen bottom navigation Material3 dengan NavigationBarItem yang mendukung ikon, label, dan state selected secara otomatis.

Coroutines + delay()

Simulasi proses asinkron (loading 1 detik) menggunakan rememberCoroutineScope dan delay() tanpa memblokir UI thread.

SnackbarHostState

Mekanisme notifikasi sementara di bagian bawah layar yang dipanggil lewat coroutine setelah aksi pengguna berhasil dijalankan.

MainActivity.kt
package com.example.marketsiswa

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

// 1. Data Model
data class Product(
    val id: Long = System.currentTimeMillis(),
    val name: String,
    val price: String,
    val description: String
)

// 2. Main Activity
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MarketplaceTheme {
                MainScreen()
            }
        }
    }
}

// 3. Main Screen — Navigation Hub
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
    var currentScreen by remember { mutableStateOf("home") }
    val productList = remember { mutableStateListOf<Product>() }
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    LaunchedEffect(Unit) {
        if (productList.isEmpty()) {
            productList.add(Product(name = "Brownies Lumer", price = "15000",
                description = "Cokelat melimpah, lembut di mulut."))
            productList.add(Product(name = "Kaos Custom", price = "85000",
                description = "Bahan adem, bisa request desain sendiri."))
            productList.add(Product(name = "Jasa Print Tugas", price = "2000",
                description = "Print hitam putih / warna, cepat dan murah."))
        }
    }

    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) },
        topBar = {
            CenterAlignedTopAppBar(
                title = {
                    Text(
                        if (currentScreen == "add") "Tambah Produk" else "MarketSiswa",
                        fontWeight = FontWeight.Bold
                    )
                },
                navigationIcon = {
                    if (currentScreen == "add") {
                        IconButton(onClick = { currentScreen = "home" }) {
                            Icon(Icons.Default.ArrowBack, contentDescription = "Kembali")
                        }
                    }
                }
            )
        },
        bottomBar = {
            NavigationBar {
                NavigationBarItem(
                    selected = currentScreen == "home",
                    onClick = { currentScreen = "home" },
                    label = { Text("Home") },
                    icon = { Icon(Icons.Default.Home, contentDescription = null) }
                )
                NavigationBarItem(
                    selected = currentScreen == "profile",
                    onClick = { currentScreen = "profile" },
                    label = { Text("Profile") },
                    icon = { Icon(Icons.Default.Person, contentDescription = null) }
                )
            }
        },
        floatingActionButton = {
            if (currentScreen == "home") {
                ExtendedFloatingActionButton(
                    onClick = { currentScreen = "add" },
                    containerColor = MaterialTheme.colorScheme.primaryContainer,
                    contentColor = MaterialTheme.colorScheme.onPrimaryContainer
                ) {
                    Icon(Icons.Default.Add, contentDescription = null)
                    Spacer(Modifier.width(8.dp))
                    Text("Jual")
                }
            }
        }
    ) { innerPadding ->
        Box(modifier = Modifier.padding(innerPadding)) {
            when (currentScreen) {
                "home"    -> HomeScreen(productList)
                "add"     -> AddProductScreen(
                    onProductAdded = { newProduct ->
                        productList.add(0, newProduct)
                        scope.launch {
                            currentScreen = "home"
                            snackbarHostState.showSnackbar("✅ Produk berhasil ditambahkan!")
                        }
                    }
                )
                "profile" -> ProfileScreen()
            }
        }
    }
}

// 4. Home Screen — Daftar Produk
@Composable
fun HomeScreen(products: List<Product>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        item {
            Text("Halo, Siswa! 👋", fontSize = 24.sp, fontWeight = FontWeight.ExtraBold)
            Text("Mau belanja apa hari ini?", color = Color.Gray,
                modifier = Modifier.padding(bottom = 8.dp))
        }
        items(products) { product ->
            Card(
                modifier = Modifier.fillMaxWidth(),
                shape = RoundedCornerShape(16.dp),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(modifier = Modifier.padding(16.dp)) {
                    Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
                        Text(product.name, fontWeight = FontWeight.Bold, fontSize = 18.sp)
                        Text("Rp \${product.price}", color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Black)
                    }
                    Spacer(Modifier.height(4.dp))
                    Text(product.description, color = Color.DarkGray, fontSize = 14.sp)
                    Spacer(Modifier.height(8.dp))
                    Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
                        Button(onClick = { }) { Text("Beli") }
                    }
                }
            }
        }
    }
}

// 5. Add Product Screen
@Composable
fun AddProductScreen(onProductAdded: (Product) -> Unit) {
    var name    by remember { mutableStateOf("") }
    var price   by remember { mutableStateOf("") }
    var desc    by remember { mutableStateOf("") }
    var isLoading by remember { mutableStateOf(false) }
    val scope = rememberCoroutineScope()

    Column(modifier = Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
        OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("Nama Produk") }, modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(12.dp))
        OutlinedTextField(value = price, onValueChange = { price = it }, label = { Text("Harga (Rp)") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), shape = RoundedCornerShape(12.dp))
        OutlinedTextField(value = desc, onValueChange = { desc = it }, label = { Text("Deskripsi") }, modifier = Modifier.fillMaxWidth(), minLines = 3, shape = RoundedCornerShape(12.dp))
        Spacer(Modifier.weight(1f))
        Button(
            onClick = { isLoading = true; scope.launch { delay(1000); onProductAdded(Product(name = name, price = price, description = desc)); isLoading = false } },
            modifier = Modifier.fillMaxWidth().height(56.dp),
            enabled = name.isNotBlank() && price.isNotBlank() && !isLoading,
            shape = RoundedCornerShape(12.dp)
        ) {
            if (isLoading) CircularProgressIndicator(modifier = Modifier.size(24.dp), color = Color.White, strokeWidth = 2.dp)
            else Text("Simpan Produk", fontWeight = FontWeight.Bold)
        }
    }
}

// 6. Profile Screen
@Composable
fun ProfileScreen() {
    Column(modifier = Modifier.fillMaxSize().padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
        Icon(Icons.Default.AccountCircle, contentDescription = null, modifier = Modifier.size(100.dp), tint = MaterialTheme.colorScheme.primary)
        Spacer(Modifier.height(12.dp))
        Text("John Siswa", fontSize = 20.sp, fontWeight = FontWeight.Bold)
        Text("XII RPL 1", color = Color.Gray)
    }
}

// 7. Theme
@Composable
fun MarketplaceTheme(content: @Composable () -> Unit) {
    MaterialTheme(colorScheme = lightColorScheme(primary = Color(0xFF6200EE), secondary = Color(0xFF03DAC6)), content = content)
}
📸 Hasil Output
MarketSiswa — Android Studio · Pixel 6 API 34
Screenshot MarketSiswa Android Jetpack Compose

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