Tugas 6 (Membuat Kalkulator Konversi Mata Uang)

 Kalkulator Konversi Mata Uang

Pada kesempatan kali ini, di dapatkan tugas untuk membuat sebuah kalkulator konversi mata uang dengan menggunakan bahasa kotlin
Dalam artikel kali ini, akan saya tampilkan sebuah kalkulator biasa dan kalkulator konversi mata uang
Berikut hasil dari kalkulatornya


Tampilan Kalkulator Biasa

Tampilan Kalkulator Konversi Mata Uang



Readable Source Code 

package com.zienzidan.calculator

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.zienzidan.calculator.ui.theme.CalculatorTheme

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CalculatorTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier
.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
CalculatorApp()
}
}
}
}
}

Kode ini adalah entry point aplikasi kalkulator Android berbasis Jetpack Compose. Saat aplikasi dijalankan:

  1. MainActivity dipanggil.

  2. UI disusun menggunakan Jetpack Compose dalam setContent.

  3. Tema dan tampilan latar diterapkan dengan CalculatorTheme dan Surface.

  4. Fungsi CalculatorApp() akan menjalankan logika dan UI utama dari kalkulator tersebut.

package com.zienzidan.calculator

import android.annotation.SuppressLint
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Calculate
import androidx.compose.material.icons.filled.Money
import androidx.compose.material.icons.outlined.Calculate
import androidx.compose.material.icons.outlined.Money
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.zienzidan.calculator.data.DataSource

data class TabBarItem(
val title: String,
val selectedIcon: ImageVector,
val unselectedIcon: ImageVector
)

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "SuspiciousIndentation")
@Composable
fun CalculatorApp() {
val dataSource = DataSource()
val context = LocalContext.current

val calculatorTab = TabBarItem(
title = stringResource(id = R.string.app_name),
selectedIcon = Icons.Filled.Calculate,
unselectedIcon = Icons.Outlined.Calculate
)
val converterTab = TabBarItem(
title = stringResource(id = R.string.currency_converter),
selectedIcon = Icons.Filled.Money,
unselectedIcon = Icons.Outlined.Money
)

val tabBarItems = listOf(calculatorTab, converterTab)

//Create NavController
val navController = rememberNavController()

//Bottom Navigation Bar
Scaffold(
bottomBar = {
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
tabBarItems.forEach { tabBarItem ->
NavigationBarItem(
icon = {
TabBarIconView(
isSelected = currentDestination?.hierarchy?.any { it.route == tabBarItem.title } == true,
selectedIcon = tabBarItem.selectedIcon,
unselectedIcon = tabBarItem.unselectedIcon,
title = tabBarItem.title
)
},
label = { Text(tabBarItem.title) },
selected = currentDestination?.hierarchy?.any { it.route == tabBarItem.title } == true,
onClick = {
navController.navigate(tabBarItem.title) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
}
}
) {
NavHost(
navController = navController,
startDestination = calculatorTab.title
)
{
composable(calculatorTab.title) {
CalculatorScreen()
}
composable(converterTab.title) {
ConverterScreen(dataSource, context)
}
}
}
}

@Composable
fun TabBarIconView(
isSelected: Boolean,
selectedIcon: ImageVector,
unselectedIcon: ImageVector,
title: String
) {
Icon(
imageVector = if (isSelected) {
selectedIcon
} else {
unselectedIcon
},
contentDescription = title
)
}


@Preview(showSystemUi = true)
@Composable
fun AppPre() {
CalculatorApp()
}
  1. Menampilkan dua tab: Kalkulator dan Konverter.

  2. Menampilkan ikon dan label di bottom navigation.

  3. Mengatur perpindahan antar tab saat diklik.

  4. Menampilkan konten yang sesuai berdasarkan tab yang aktif.

Sederhananya, CalculatorApp() = Navigasi + UI utama + Tab bar aplikasi

package com.zienzidan.calculator


import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Backspace
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.github.murzagalin.evaluator.Evaluator

val evaluator = Evaluator()

@Composable
fun CalculatorScreen() {
var expression by rememberSaveable {
mutableStateOf("")
}

val expressionColorNormal = MaterialTheme.colorScheme.onSurface
val expressionColorError = Color.Red
var expressionColor by remember { mutableStateOf(expressionColorNormal) }

Column (
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(8.dp, 8.dp, 8.dp, 85.dp)

) {
Column(
modifier = Modifier

) {
ElevatedCard {
Text(
text = expression,
modifier = Modifier
.fillMaxWidth(),
lineHeight = 90.sp,
textAlign = TextAlign.End,
fontSize = 80.sp,
maxLines = 4,
overflow = TextOverflow.Ellipsis,
color = expressionColor
)
}
}
Column(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.fillMaxWidth()
.fillMaxSize()
.weight(0.5f),
verticalArrangement = Arrangement.Bottom
) {
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression = ""
expressionColor = expressionColorNormal
}
) {
Icon(
imageVector = Icons.Default.Refresh,
contentDescription = stringResource(R.string.reset)
)
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "("
expressionColor = expressionColorNormal
}
) {
Text(text = "(")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += ")"
expressionColor = expressionColorNormal
}
) {
Text(text = ")")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression = addTheOperant(expression,"/")
expressionColor = expressionColorNormal
}
) {
Text(text = "/")
}
}
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "7"
expressionColor = expressionColorNormal
}
) {
Text(text = "7")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "8"
expressionColor = expressionColorNormal
}
) {
Text(text = "8")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "9"
expressionColor = expressionColorNormal
}
) {
Text(text = "9")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression = addTheOperant(expression,"*")
expressionColor = expressionColorNormal
}
) {
Text(text = "*")
}
}
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "4"
expressionColor = expressionColorNormal
}
) {
Text(text = "4")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "5"
expressionColor = expressionColorNormal
}
) {
Text(text = "5")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "6"
expressionColor = expressionColorNormal
}
) {
Text(text = "6")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression = addTheOperant(expression,"-")
expressionColor = expressionColorNormal
}
) {
Text(text = "-")
}
}
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "1"
expressionColor = expressionColorNormal
}
) {
Text(text = "1")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "2"
expressionColor = expressionColorNormal
}
) {
Text(text = "2")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "3"
expressionColor = expressionColorNormal
}
) {
Text(text = "3")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression = addTheOperant(expression,"+")
expressionColor = expressionColorNormal
}
) {
Text(text = "+")
}
}
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression += "0"
expressionColor = expressionColorNormal
}
) {
Text(text = "0")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp)
,
onClick = {
expression = addTheOperant(expression,".")
expressionColor = expressionColorNormal
}

) {
Text(text = ".")
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
expression = expression.dropLast(1)
expressionColor = expressionColorNormal
}
) {
Icon(
imageVector = Icons.AutoMirrored.Default.Backspace,
contentDescription = stringResource(R.string.backspace)
)
}
Button(
modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
try {
expression = evaluateExpression(expression)
expressionColor = expressionColorNormal
}catch (ex: Exception){
expressionColor = expressionColorError
}
}
) {
Text(text = "=")
}
}
}
}
}

//Checks if expression already ends with an operant before adding a new one
fun addTheOperant(expression: String, operant: String): String{
return if (expression.endsWith("+") ||
expression.endsWith("-") ||
expression.endsWith("*") ||
expression.endsWith("/") ||
expression.endsWith(".")){
expression
} else{
expression+operant
}
}

//Uses the Evaluator library to evaluate the expression into a result
fun evaluateExpression(expression: String): String {
return try {
val result = evaluator.evaluateDouble(expression).toString()
if (result.endsWith(".0")){
result.dropLast(2)
} else{
result
}
} catch (ex: Exception){
throw Exception("Wrong Expression Format")
}
}


@Preview(showSystemUi = true)
@Composable
fun CalculatorScreenPr(){
CalculatorScreen()
}

Kode tersebut membuat aplikasi kalkulator di Android menggunakan Jetpack Compose yang bisa:

  • Menampilkan dan mengedit ekspresi matematika.

  • Mendukung operasi dasar: +, -, *, /, (), dan ..

  • Menghapus karakter (Backspace) dan mereset ekspresi (Refresh).

  • Menghitung hasil saat tombol = ditekan menggunakan library Evaluator.

  • Menampilkan kesalahan dengan warna merah jika ekspresi salah.

Semuanya ditampilkan dalam antarmuka tombol interaktif

package com.zienzidan.calculator


import android.content.Context
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Backspace
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.zienzidan.calculator.data.ApiClient
import com.zienzidan.calculator.data.ConvertionResult
import com.zienzidan.calculator.data.DataSource
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConverterScreen(dataSource: DataSource, context: Context) {

val currencyList = dataSource.currencyList

var expandedFrom by remember { mutableStateOf(false) }
var selectedFromOption by rememberSaveable { mutableStateOf(currencyList[0].code) }
var symbolFrom by rememberSaveable { mutableStateOf(currencyList[0].symbol) }

var expandedTo by remember { mutableStateOf(false) }
var selectedToOption by rememberSaveable { mutableStateOf(currencyList[1].code) }
var symbolTo by rememberSaveable { mutableStateOf(currencyList[1].symbol) }

var amount by rememberSaveable { mutableStateOf("") }
var result by rememberSaveable { mutableStateOf("") }

var loadingIndication by remember { mutableStateOf(false) }


Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(8.dp, 8.dp, 8.dp, 85.dp)
) {
Column(
modifier = Modifier
) {
ElevatedCard {
Text(
text = symbolFrom + amount,
modifier = Modifier.fillMaxWidth(),
lineHeight = 90.sp,
textAlign = TextAlign.End,
fontSize = 80.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
Column(
modifier = Modifier.padding(2.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(2.dp)
) {
Text(
text = stringResource(R.string.from),
style = MaterialTheme.typography.displaySmall,
modifier = Modifier.weight(0.2f)
)
ExposedDropdownMenuBox(
expanded = expandedFrom,
onExpandedChange = {
expandedFrom = !expandedFrom
}) {
TextField(
value = selectedFromOption,
onValueChange = {},
readOnly = true,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedFrom) },
modifier = Modifier.menuAnchor(),
textStyle = TextStyle.Default
)

ExposedDropdownMenu(
expanded = expandedFrom,
onDismissRequest = { expandedFrom = false }) {
currencyList.forEach { item ->
DropdownMenuItem(
text = { Text(text = item.code + " : " + item.name) },
onClick = {
selectedFromOption = item.code
expandedFrom = false
symbolFrom = item.symbol
result = ""
})
}
}
}
}

Row(
modifier = Modifier
.fillMaxWidth()
.padding(2.dp)
) {
Text(
text = stringResource(R.string.to),
style = MaterialTheme.typography.displaySmall,
modifier = Modifier.weight(0.2f)
)
ExposedDropdownMenuBox(
expanded = expandedTo,
onExpandedChange = {
expandedTo = !expandedTo
}) {
TextField(
value = selectedToOption,
onValueChange = {},
readOnly = true,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedTo) },
modifier = Modifier.menuAnchor(),
textStyle = TextStyle.Default
)

ExposedDropdownMenu(
expanded = expandedTo,
onDismissRequest = { expandedTo = false }) {
currencyList.forEach { item ->
DropdownMenuItem(
text = { Text(text = item.code + " : " + item.name) },
onClick = {
selectedToOption = item.code
expandedTo = false
symbolTo = item.symbol
result = ""
})
}
}
}
}
}
Column(
modifier = Modifier
) {
ElevatedCard {
Text(
text = symbolTo + result,
modifier = Modifier.fillMaxWidth(),
lineHeight = 90.sp,
textAlign = TextAlign.End,
fontSize = 80.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
}
if (loadingIndication){
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth().padding(2.dp),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
}

Column(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.fillMaxWidth()
.weight(0.5f),
verticalArrangement = Arrangement.Bottom
) {
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "7"
}) {
Text(text = "7")
}
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "8"
}) {
Text(text = "8")
}
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "9"
}) {
Text(text = "9")
}
}
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "4"
}) {
Text(text = "4")
}
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "5"
}) {
Text(text = "5")
}
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "6"
}) {
Text(text = "6")
}
}
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "1"
}) {
Text(text = "1")
}
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "2"
}) {
Text(text = "2")
}
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "3"
}) {
Text(text = "3")
}
}
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "0"
}) {
Text(text = "0")
}
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp), onClick = {
amount += "."
}

) {
Text(text = ".")
}
Button(modifier = Modifier
.weight(1f)
.height(60.dp)
.padding(2.dp),
onClick = {
amount = amount.dropLast(1)
result = ""
}) {
Icon(
imageVector = Icons.AutoMirrored.Default.Backspace,
contentDescription = stringResource(R.string.backspace)
)
}
}
Row {
Button(
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
.padding(2.dp),
onClick = {
loadingIndication = true
if (selectedFromOption == selectedToOption || amount == "" || amount == "0") {
result = amount
} else {
val call = ApiClient.apiService.convertAmount(
amount,
selectedFromOption,
selectedToOption
)
//Calls the API to get the conversion
call.enqueue(object : Callback<ConvertionResult> {
override fun onResponse(
call: Call<ConvertionResult>,
response: Response<ConvertionResult>
) {
if (response.isSuccessful) {
val post = response.body()
result =
convertRateToValue(
post?.rates.toString(),
selectedToOption
)
} else {
Toast.makeText(
context,
context.getString(R.string.conversion_failed),
Toast.LENGTH_LONG
).show()
}
}

override fun onFailure(call: Call<ConvertionResult>, t: Throwable) {
Toast.makeText(
context,
context.getString(R.string.connection_error),
Toast.LENGTH_LONG
).show()
}
})
}
loadingIndication = false
}
) {
Text(text = stringResource(R.string.convert_currency))
}
}
}
}
}

//Converts the received rate from the api to only the value
private fun convertRateToValue(rate: String, selectedToOption: String): String {
val pattern = Regex("""$selectedToOption=(.*?)(?=,|$)""")
val match = pattern.find(rate)
return match?.groupValues?.get(1) ?: "0.0"
}


@Preview(showSystemUi = true)
@Composable
fun ConverterScreenPr() {
val dataSource = DataSource()
val context = LocalContext.current
ConverterScreen(dataSource, context)
}

kode di atas adalah layar konversi mata uang menggunakan Jetpack Compose di Android. Fitur utamanya:

  • Menampilkan nilai input dan hasil konversi dalam format mata uang.

  • Memilih mata uang asal dan tujuan dari dropdown.

  • Input angka lewat tombol seperti kalkulator.

  • Tombol "Convert Currency" menghubungi API untuk mengonversi jumlah.

  • Menampilkan progress bar saat loading.

  • Menangani error dengan Toast.

Strukturnya menggunakan Column, Row, Button, dan TextField dalam UI Compose

Video Presentasi


Source Code : Kalkulator Konversi Mata Uang

Referensi

https://kuliahppb.blogspot.com/2025/04/penggunaan-kotlin-1.html

https://www.idn.id/mengapa-kotlin-menjadi-masa-depan-bagi-pemrograman-android/

https://developer.android.com/kotlin/learn?hl=id#:~:text=Kotlin%20adalah%20bahasa%20pemrograman%20banyak,membantu%20Anda%20bekerja%20dengan%20cepat.

Komentar

Postingan populer dari blog ini

Tugas 8 (Aplikasi Woof)

EAS Pemograman Perangkat Bergerak

Tugas 7 (Halaman Login)