Pertemuan 11
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.
com.example.marketsiswa, dan Minimum SDK API 24.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.id (auto-generated dari timestamp), name, price, dan description sebagai model data produk yang digunakan di seluruh aplikasi.Scaffold dari Material3 yang mencakup: CenterAlignedTopAppBar dengan judul dinamis, NavigationBar dengan dua item (Home dan Profile), serta ExtendedFloatingActionButton untuk navigasi ke halaman tambah produk.LaunchedEffect(Unit) agar data muncul saat aplikasi pertama kali dibuka, tanpa mengulang inisialisasi saat recomposition.LazyColumn untuk menampilkan daftar produk secara efisien. Setiap produk ditampilkan dalam komponen Card Material3 dengan nama, harga berwarna ungu, deskripsi singkat, dan tombol Beli.OutlinedTextField untuk input nama, harga (dengan KeyboardType.Number), dan deskripsi. Tombol Simpan menampilkan CircularProgressIndicator selama 1 detik simulasi loading menggunakan delay(1000) dan Coroutine.snackbarHostState.showSnackbar() yang dipanggil dalam coroutine scope.AccountCircle berukuran besar berwarna ungu sebagai avatar.lightColorScheme Material Design 3 dengan warna primary ungu #6200EE dan secondary teal #03DAC6, lalu menjalankan aplikasi di emulator Pixel 6 API 34.Struktur layout utama Material Design 3 yang menyediakan slot terintegrasi untuk TopBar, BottomBar, FAB, Snackbar, dan konten utama.
State untuk list yang reaktif — penambahan item otomatis memicu recomposition sehingga UI diperbarui tanpa kode tambahan.
Komponen list efisien yang hanya me-render item yang terlihat di layar, cocok untuk daftar produk yang terus bertambah dinamis.
Komponen bottom navigation Material3 dengan NavigationBarItem yang mendukung ikon, label, dan state selected secara otomatis.
Simulasi proses asinkron (loading 1 detik) menggunakan rememberCoroutineScope dan delay() tanpa memblokir UI thread.
Mekanisme notifikasi sementara di bagian bawah layar yang dipanggil lewat coroutine setelah aksi pengguna berhasil dijalankan.
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) }
Komentar
Posting Komentar