Tugas PPB 10 Membuat Aplikasi Dessert Clicker
TUGAS PPB 10
Membuat Aplikasi Dessert Clicker
Nama: Fayyadh Hafizh
NRP : 5025201164
Kelas: PPB I
Link Github: Tugas 10
Halo temen-temen semuanya! Pada kesempatan kali ini, kita akan mencoba membuat sebuah aplikasi yang bernama Dessert Clicket. Aplikasi ini dibuat dengan tujuan untuk mempelajari lifecycle dari sebuah Activity. Activity sendiri merupakan proses yang dijalankan dalam bentuk aplikasi. Pada implementasinya, kita akan melihat bagaimana metode-metode dari lifecycle yang ada di dalam sebuah rangkaian proses jalannya aplikasi. Secara umum, terdapat 7 metode dari lifecycle yang ada, yaitu onCreate(), onStart(), onRestart(), onResume(), onPause(), onStop(), dan onDestroyed(). Masing-masing dari metode memiliki fungsinya sendiri. Detail dari penjelasan setiap metode akan dijelaskan dibawah ini.
- onCreate()onCreate() merupakan metode paling awal dimana metode ini akan dipanggil oleh sistem ketika sebuah aplikasi dijalankan oleh pengguna. Metode onCreate() akan menjalankan aplikasi di belakang layar dan mempersiapkan tampilan beserta logic dari aplikasi yang dijalankan.
- onStart()Metode onStart() merupakan metode lanjutan dari metode onCreate() dimana setelah pemanggilan onStart(), tampilan aplikasi akan diperlihatkan kepada pengguna dan menjadi interaktif. Proses ini berlangsung dengan cepat sehingga pengguna mungkin saja tidak sempat untuk berinteraksi dengan aplikasi. Alih-alih berhenti pada lifecycle ini, pemanggilan metode onResume() dilakukan setelah metode onStart() selesai dilakukan.
- onResume()Metode ini merupakan metode yang paling lama dibandingkan dengan metode lainnya karena metode ini merupakan proses yang memungkinkan pengguna untuk berinteraksi dengan aplikasi mulai dari menambahkan data, menampilkan data, dan lain-lain. Pada state ini, aplikasi akan dijadikan sebagai fokus sehingga interaksi antar aplikasi dan pengguna dapat berlangsung. Proses ini berlangsung selama tidak gangguan dari aplikasi lain atau pengguna melakukan perubahan state pada aplikasi.
- onPause()Ketika terdapat gangguan dari aplikasi lain seperti aplikasi telpon ataupun aplikasi yang dapat merubah fokus perangkat ke aplikasi lain dalam mode multi-aplikasi, maka pemanggilan metode onPause() akan dilakukan. Pada state ini, aplikasi akan masuk ke mode tidak fokus walaupun tampilan aplikasi masih berjalan.
- onStop()Jika aplikasi ditutup dan berjalan pada pada background, maka metode onStop() dipanggil untuk menutup tampilan dari aplikasi tersebut. Urutan dari pemanggilan metode lifecycle ketika aplikasi dikeluarkan dimulai dari onPause() kemudian dilanjutkan dengan onStop(). Pada state ini, pengguna tidak dapat melihat tampilan maupun berinteraksi dengan aplikasi.
- onDestroyed()Jika aplikasi dihapus dari background proses, maka pemanggilan metode onDestroyed() dilakukan agar aplikasi dihapus dari memori untuk mengurangi beban pada memori. Metode onDestroyed() juga dapat dipanggil ketika konfigurasi dari perangkat maupun aplikasi berubah seperti rotasi terhadap orientasi layar yang menyebabkan pemanggilan metode onPause(), onStop(), dan onDestroyed() secara berurutan diikuti oleh metode onCreate(), onStart(), dan onResume(). Jika dilihat dari pemanggilan metode onDestroyed(), maka seharusnya semua data yang ditampilkan pada aplikasi akan menghilang. Tetapi, dengan menggunakan fungsi rememberSaveable memungkinan setiap Composable atau Component menyimpan state dari setiap data yang ada agar dapat ditampilkan metode onDestroyed() dipanggilan.
- onRestart()
Metode onRestart() mirip dengan metode onStart() dimana aplikasi akan diperlihatkan sekaligus inisialisasi UI lainnya. Perbedaan dari keduanya adalah pemanggilan dengan melihat state sebelumnya. Jika state sebelumnya adalah ON_CREATE, maka yang dipanggil adalah metode onStart() sedangkan pemanggilan metode onRestart() dilakukan jika state sebelumnya adalah ON_STOP.
Lifecycle pada aplikasi android meliputi onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroyed(), dan onRestart(). Setiap metode dipanggil sesuai dengan perubahan yang dilakukan oleh pengguna maupun perangkat sehingga manajemen terhadap state aplikasi dapat dilakukan dengan baik. Kode dan hasil implementasi aplikasi dapat dilihat dibawah ini.
- Kode Implementasi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2023 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.example.dessertclicker | |
import android.content.ActivityNotFoundException | |
import android.content.Context | |
import android.content.Intent | |
import android.os.Bundle | |
import android.util.Log | |
import android.widget.Toast | |
import androidx.activity.ComponentActivity | |
import androidx.activity.compose.setContent | |
import androidx.activity.enableEdgeToEdge | |
import androidx.annotation.DrawableRes | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.clickable | |
import androidx.compose.foundation.layout.Arrangement | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.Row | |
import androidx.compose.foundation.layout.WindowInsets | |
import androidx.compose.foundation.layout.asPaddingValues | |
import androidx.compose.foundation.layout.calculateEndPadding | |
import androidx.compose.foundation.layout.calculateStartPadding | |
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.layout.safeDrawing | |
import androidx.compose.foundation.layout.statusBarsPadding | |
import androidx.compose.foundation.layout.width | |
import androidx.compose.material.icons.Icons | |
import androidx.compose.material.icons.filled.Share | |
import androidx.compose.material3.Scaffold | |
import androidx.compose.material3.Icon | |
import androidx.compose.material3.IconButton | |
import androidx.compose.material3.MaterialTheme | |
import androidx.compose.material3.Surface | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableStateOf | |
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.layout.ContentScale | |
import androidx.compose.ui.platform.LocalContext | |
import androidx.compose.ui.platform.LocalLayoutDirection | |
import androidx.compose.ui.res.dimensionResource | |
import androidx.compose.ui.res.painterResource | |
import androidx.compose.ui.res.stringResource | |
import androidx.compose.ui.text.style.TextAlign | |
import androidx.compose.ui.tooling.preview.Preview | |
import androidx.core.content.ContextCompat | |
import com.example.dessertclicker.data.Datasource | |
import com.example.dessertclicker.model.Dessert | |
import com.example.dessertclicker.ui.theme.DessertClickerTheme | |
// Tag for logging | |
private const val TAG = "MainActivity" | |
class MainActivity : ComponentActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
enableEdgeToEdge() | |
super.onCreate(savedInstanceState) | |
Log.d(TAG, "onCreate Called") | |
setContent { | |
DessertClickerTheme { | |
// A surface container using the 'background' color from the theme | |
Surface( | |
modifier = Modifier | |
.fillMaxSize() | |
.statusBarsPadding(), | |
) { | |
DessertClickerApp(desserts = Datasource.dessertList) | |
} | |
} | |
} | |
} | |
override fun onStart() { | |
super.onStart() | |
Log.d(TAG, "onStart Called") | |
} | |
override fun onResume() { | |
super.onResume() | |
Log.d(TAG, "onResume Called") | |
} | |
override fun onRestart() { | |
super.onRestart() | |
Log.d(TAG, "onRestart Called") | |
} | |
override fun onPause() { | |
super.onPause() | |
Log.d(TAG, "onPause Called") | |
} | |
override fun onStop() { | |
super.onStop() | |
Log.d(TAG, "onStop Called") | |
} | |
override fun onDestroy() { | |
super.onDestroy() | |
Log.d(TAG, "onDestroy Called") | |
} | |
} | |
/** | |
* Determine which dessert to show. | |
*/ | |
fun determineDessertToShow( | |
desserts: List<Dessert>, | |
dessertsSold: Int | |
): Dessert { | |
var dessertToShow = desserts.first() | |
for (dessert in desserts) { | |
if (dessertsSold >= dessert.startProductionAmount) { | |
dessertToShow = dessert | |
} else { | |
// The list of desserts is sorted by startProductionAmount. As you sell more desserts, | |
// you'll start producing more expensive desserts as determined by startProductionAmount | |
// We know to break as soon as we see a dessert who's "startProductionAmount" is greater | |
// than the amount sold. | |
break | |
} | |
} | |
return dessertToShow | |
} | |
/** | |
* Share desserts sold information using ACTION_SEND intent | |
*/ | |
private fun shareSoldDessertsInformation(intentContext: Context, dessertsSold: Int, revenue: Int) { | |
val sendIntent = Intent().apply { | |
action = Intent.ACTION_SEND | |
putExtra( | |
Intent.EXTRA_TEXT, | |
intentContext.getString(R.string.share_text, dessertsSold, revenue) | |
) | |
type = "text/plain" | |
} | |
val shareIntent = Intent.createChooser(sendIntent, null) | |
try { | |
ContextCompat.startActivity(intentContext, shareIntent, null) | |
} catch (e: ActivityNotFoundException) { | |
Toast.makeText( | |
intentContext, | |
intentContext.getString(R.string.sharing_not_available), | |
Toast.LENGTH_LONG | |
).show() | |
} | |
} | |
@Composable | |
private fun DessertClickerApp( | |
desserts: List<Dessert> | |
) { | |
var revenue by rememberSaveable { mutableStateOf(0) } | |
var dessertsSold by rememberSaveable { mutableStateOf(0) } | |
val currentDessertIndex by rememberSaveable { mutableStateOf(0) } | |
var currentDessertPrice by rememberSaveable { | |
mutableStateOf(desserts[currentDessertIndex].price) | |
} | |
var currentDessertImageId by rememberSaveable { | |
mutableStateOf(desserts[currentDessertIndex].imageId) | |
} | |
Scaffold( | |
topBar = { | |
val intentContext = LocalContext.current | |
val layoutDirection = LocalLayoutDirection.current | |
DessertClickerAppBar( | |
onShareButtonClicked = { | |
shareSoldDessertsInformation( | |
intentContext = intentContext, | |
dessertsSold = dessertsSold, | |
revenue = revenue | |
) | |
}, | |
modifier = Modifier | |
.fillMaxWidth() | |
.padding( | |
start = WindowInsets.safeDrawing.asPaddingValues() | |
.calculateStartPadding(layoutDirection), | |
end = WindowInsets.safeDrawing.asPaddingValues() | |
.calculateEndPadding(layoutDirection), | |
) | |
.background(MaterialTheme.colorScheme.primary) | |
) | |
} | |
) { contentPadding -> | |
DessertClickerScreen( | |
revenue = revenue, | |
dessertsSold = dessertsSold, | |
dessertImageId = currentDessertImageId, | |
onDessertClicked = { | |
// Update the revenue | |
revenue += currentDessertPrice | |
dessertsSold++ | |
// Show the next dessert | |
val dessertToShow = determineDessertToShow(desserts, dessertsSold) | |
currentDessertImageId = dessertToShow.imageId | |
currentDessertPrice = dessertToShow.price | |
}, | |
modifier = Modifier.padding(contentPadding) | |
) | |
} | |
} | |
@Composable | |
private fun DessertClickerAppBar( | |
onShareButtonClicked: () -> Unit, | |
modifier: Modifier = Modifier | |
) { | |
Row( | |
modifier = modifier, | |
horizontalArrangement = Arrangement.SpaceBetween, | |
verticalAlignment = Alignment.CenterVertically, | |
) { | |
Text( | |
text = stringResource(R.string.app_name), | |
modifier = Modifier.padding(start = dimensionResource(R.dimen.padding_medium)), | |
color = MaterialTheme.colorScheme.onPrimary, | |
style = MaterialTheme.typography.titleLarge, | |
) | |
IconButton( | |
onClick = onShareButtonClicked, | |
modifier = Modifier.padding(end = dimensionResource(R.dimen.padding_medium)), | |
) { | |
Icon( | |
imageVector = Icons.Filled.Share, | |
contentDescription = stringResource(R.string.share), | |
tint = MaterialTheme.colorScheme.onPrimary | |
) | |
} | |
} | |
} | |
@Composable | |
fun DessertClickerScreen( | |
revenue: Int, | |
dessertsSold: Int, | |
@DrawableRes dessertImageId: Int, | |
onDessertClicked: () -> Unit, | |
modifier: Modifier = Modifier | |
) { | |
Box(modifier = modifier) { | |
Image( | |
painter = painterResource(R.drawable.bakery_back), | |
contentDescription = null, | |
contentScale = ContentScale.Crop | |
) | |
Column { | |
Box( | |
modifier = Modifier | |
.weight(1f) | |
.fillMaxWidth(), | |
) { | |
Image( | |
painter = painterResource(dessertImageId), | |
contentDescription = null, | |
modifier = Modifier | |
.width(dimensionResource(R.dimen.image_size)) | |
.height(dimensionResource(R.dimen.image_size)) | |
.align(Alignment.Center) | |
.clickable { onDessertClicked() }, | |
contentScale = ContentScale.Crop, | |
) | |
} | |
TransactionInfo( | |
revenue = revenue, | |
dessertsSold = dessertsSold, | |
modifier = Modifier.background(MaterialTheme.colorScheme.secondaryContainer) | |
) | |
} | |
} | |
} | |
@Composable | |
private fun TransactionInfo( | |
revenue: Int, | |
dessertsSold: Int, | |
modifier: Modifier = Modifier | |
) { | |
Column(modifier = modifier) { | |
DessertsSoldInfo( | |
dessertsSold = dessertsSold, | |
modifier = Modifier | |
.fillMaxWidth() | |
.padding(dimensionResource(R.dimen.padding_medium)) | |
) | |
RevenueInfo( | |
revenue = revenue, | |
modifier = Modifier | |
.fillMaxWidth() | |
.padding(dimensionResource(R.dimen.padding_medium)) | |
) | |
} | |
} | |
@Composable | |
private fun RevenueInfo(revenue: Int, modifier: Modifier = Modifier) { | |
Row( | |
modifier = modifier, | |
horizontalArrangement = Arrangement.SpaceBetween, | |
) { | |
Text( | |
text = stringResource(R.string.total_revenue), | |
style = MaterialTheme.typography.headlineMedium, | |
color = MaterialTheme.colorScheme.onSecondaryContainer | |
) | |
Text( | |
text = "$${revenue}", | |
textAlign = TextAlign.Right, | |
style = MaterialTheme.typography.headlineMedium, | |
color = MaterialTheme.colorScheme.onSecondaryContainer | |
) | |
} | |
} | |
@Composable | |
private fun DessertsSoldInfo(dessertsSold: Int, modifier: Modifier = Modifier) { | |
Row( | |
modifier = modifier, | |
horizontalArrangement = Arrangement.SpaceBetween, | |
) { | |
Text( | |
text = stringResource(R.string.dessert_sold), | |
style = MaterialTheme.typography.titleLarge, | |
color = MaterialTheme.colorScheme.onSecondaryContainer | |
) | |
Text( | |
text = dessertsSold.toString(), | |
style = MaterialTheme.typography.titleLarge, | |
color = MaterialTheme.colorScheme.onSecondaryContainer | |
) | |
} | |
} | |
@Preview | |
@Composable | |
fun MyDessertClickerAppPreview() { | |
DessertClickerTheme { | |
DessertClickerApp(listOf(Dessert(R.drawable.cupcake, 5, 0))) | |
} | |
} |
- Hasil Implementasi
Komentar
Posting Komentar