π Catatan Syntax & Code Snippet untuk Ujian IF3210
File ini berisi semua contoh kode, method, dan syntax penting dari PPT 9-15. Cocok untuk soal βapa yang terjadi di snippet ini?β atau βsyntax yang benar/salahβ.
π’ BAB 9: Activity & Fragment Lifecycle
Activity Lifecycle Callbacks (Urutan WAJIB HAFAL)
Activity launched β onCreate() β onStart() β onResume() β [RUNNING]
β
onPause() β [kehilangan fokus]
onStop() β [tidak visible]
onDestroy() β [hancur]
Kembali dari onStop: onRestart() β onStart() β onResume()
Fragment Lifecycle (Bedanya dari Activity!)
onAttach() β onCreate() β onCreateView() β onViewCreated()
β onStart() β onResume() β [AKTIF]
β onPause() β onStop() β onDestroyView() β onDestroy() β onDetach()
Poin Penting: Fragment punya onAttach(), onCreateView(), onViewCreated(), onDestroyView(), onDetach() yang Activity TIDAK punya.
Simpan State saat Config Change (Rotasi Layar)
// SIMPAN
override fun onSaveInstanceState(outState: Bundle) {
outState.putString("KEY", value)
super.onSaveInstanceState(outState)
}
// RESTORE - di onCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val restoredValue = savedInstanceState?.getString("KEY")
}Fragment juga bisa restore di: onCreate(), onCreateView(), onViewCreated().
LifecycleObserver Pattern
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun connectListener() { ... }
}
// Tambah observer ke lifecycle owner:
myLifecycleOwner.getLifecycle().addObserver(MyObserver())Keyword: LifecycleOwner = punya lifecycle (Fragment, AppCompatActivity). LifecycleObserver = yang mengawasi.
πΎ BAB 10: Room Database & Coroutines
Entity (Representasi Tabel)
@Entity(tableName = "colors")
data class Color(
@PrimaryKey(autoGenerate = true) val _id: Int,
@ColumnInfo(name = "hex_color") val hex: String,
val name: String
)β οΈ Perhatikan: @ColumnInfo(name = "hex_color") mengubah nama kolom di DB.
DAO (Data Access Object)
@Dao
interface ColorDao {
@Query("SELECT * FROM colors")
suspend fun getAll(): Array<Color>
@Query("SELECT * FROM colors WHERE name = :name")
suspend fun getColorByName(name: String): LiveData<Color>
@Insert
suspend fun insert(vararg color: Color)
@Update
suspend fun update(color: Color)
@Delete
suspend fun delete(color: Color)
}β οΈ Perhatikan: :name di Query merujuk ke parameter fungsi. vararg = bisa masukkan banyak argumen.
Database Singleton Pattern (SERING KELUAR DI SOAL)
@Database(entities = [Color::class], version = 1)
abstract class ColorDatabase : RoomDatabase() {
abstract fun colorDao(): ColorDao
companion object {
@Volatile
private var INSTANCE: ColorDatabase? = null
fun getInstance(context: Context): ColorDatabase {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: Room.databaseBuilder(
context.applicationContext,
ColorDatabase::class.java, "color_database"
)
.fallbackToDestructiveMigration()
.build()
.also { INSTANCE = it }
}
}
}
}β οΈ Yang harus kamu tahu:
@Volatileβ perubahan INSTANCE langsung visible ke semua threadsynchronized(this)β cuma 1 thread yang boleh bikin DB?: ... ?: ...β Double-check locking pattern.also { INSTANCE = it }β simpan instance yang baru dibuatfallbackToDestructiveMigration()β hapus semua data lama kalau versi DB naik
Pakai DAO
val colorDao = ColorDatabase.getInstance(application).colorDao()
val newColor = Color(hex = "#6200EE", name = "purple")
colorDao.insert(newColor)Coroutine Dispatchers (WAJIB TAHU)
| Dispatcher | Deskripsi | Contoh |
|---|---|---|
Dispatchers.Main | UI & task ringan | Update LiveData, panggil suspend function |
Dispatchers.IO | Network & disk | Database, file IO, Retrofit call |
Dispatchers.Default | CPU intensive | Parsing JSON, sorting besar |
withContext (Pindah Dispatcher)
suspend fun get(url: String) {
// Mulai di Dispatchers.Main
withContext(Dispatchers.IO) {
// Pindah ke Dispatchers.IO
// Lakukan network request
}
// Kembali ke Dispatchers.Main
}CoroutineScope (launch vs async)
// launch - kalau TIDAK butuh hasil balik
viewModelScope.launch {
fetchDocs()
}
// async - kalau BUTUH hasil balik
val result = viewModelScope.async {
fetchData()
}.await()ViewModel + Coroutine
class ColorViewModel(val dao: ColorDao, application: Application)
: AndroidViewModel(application) {
fun save(color: Color) {
viewModelScope.launch {
colorDao.insert(color)
}
}
}viewModelScope otomatis cancel kalau ViewModel cleared.
Testing Room Database
@RunWith(AndroidJUnit4::class)
class DatabaseTest {
private lateinit val colorDao: ColorDao
private lateinit val db: ColorDatabase
@Before
fun createDb() {
val context = ApplicationProvider.getApplicationContext()
db = Room.inMemoryDatabaseBuilder(context, ColorDatabase::class.java)
.allowMainThreadQueries()
.build()
colorDao = db.colorDao()
}
@After
fun closeDb() = db.close()
@Test
fun insertAndRetrieve() {
colorDao.insert(red, green, blue)
val colors = colorDao.getAll()
assert(colors.size == 3)
}
}Keyword: inMemoryDatabaseBuilder = DB cuma di RAM, hilang setelah test selesai.
π οΈ BAB 11: Services, Broadcast, Notification, Content Provider
Broadcast - Jenis
sendBroadcast() β Asynchronous, semua receiver dapat
sendOrderedBroadcast() β Synchronous, satu per satu
LocalBroadcastManager.sendBroadcast() β Hanya dalam app sendiri (aman!)
Register BroadcastReceiver (Dynamic)
// Register di onCreate
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
receiver = NetworkReceiver()
this.registerReceiver(receiver, filter)
// Unregister di onDestroy
this.unregisterReceiver(receiver)β οΈ WAJIB unregister! Kalau tidak β memory leak.
BroadcastReceiver Implementation
class NetworkReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Punya waktu HANYA 5 DETIK untuk eksekusi!
val conn = context.getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager
val networkInfo: NetworkInfo? = conn.activeNetworkInfo
// ... proses network status
}
}β οΈ onReceive() punya batas waktu 5 DETIK, lalu receiver DIHANCURKAN.
Service Types (Method yang dipanggil)
| Jenis | Mulai dengan | Berhenti dengan |
|---|---|---|
| Started | startService() | stopSelf() atau stopService() |
| Bound | bindService() | Otomatis saat semua client unbind |
| IntentService | startService() | Otomatis setelah onHandleIntent() selesai |
Service berjalan di MAIN THREAD by default (kecuali IntentService).
Notification Builder
// Java-style dari PPT (mungkin keluar di soal!)
mNotifyManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
mNotifyBuilder = NotificationCompat.Builder(this)
.setContentTitle("You've been notified!")
.setContentText("This is your notification text.")
.setSmallIcon(R.drawable.ic_android_black_24dp)
val myNotification = mNotifyBuilder.build()
mNotifyManager.notify(NOTIFICATION_ID, myNotification)Keyword: PendingIntent = memberi app lain hak menjalankan Intent atas namamu (untuk notif tap action).
Content Provider - Arsitektur
Activity/Adapter β ContentResolver β ContentProvider β Data (SQLite)
- ContentResolver mengembalikan data sebagai Cursor (baris + kolom)
- Content Provider butuh permission di Manifest
- Tanpa permission eksplisit = semua app bisa baca/tulis (bahaya!)
π BAB 12: Connectivity & Retrofit
Permission di Manifest (WAJIB)
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />β οΈ INTERNET = Normal permission (auto-granted). Tapi network operation di main thread β NetworkOnMainThreadException!
Permission Types
| Level | Granted | Prompt? | Contoh |
|---|---|---|---|
| Normal | Install time | Tidak | INTERNET, BLUETOOTH, VIBRATE |
| Signature | Install time | Tidak | N/A |
| Dangerous | Runtime | Ya | CAMERA, CALL_PHONE, GET_ACCOUNTS |
Retrofit Interface Definition
interface UserService {
@GET("/users/{id}")
suspend fun getUser(@Path("id") id: String): User
}Retrofit + Repository Pattern
class UserRepository constructor(private val userService: UserService) {
suspend fun getUserById(id: String): User {
return userService.getUser(id)
}
}
// Setup Retrofit
var retrofit = Retrofit.Builder()
.baseUrl("https://...")
.build()
var userRepository = UserRepository(retrofit.create(UserService::class.java))
var john = userRepository.getUserById("john")Moshi + Retrofit (JSON Parsing)
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
object API {
val retrofitService: SimpleService by lazy {
retrofit.create(SimpleService::class.java)
}
}by lazy = instance baru dibuat saat pertama kali diakses.
ViewModel + Retrofit + LiveData
class MainViewModel(savedStateHandle: SavedStateHandle,
userRepository: UserRepository) : ViewModel() {
private val userId: String = savedStateHandle["uid"]
?: throw IllegalArgumentException("Missing user ID")
private val _user = MutableLiveData<User>()
val user = _user as LiveData<User>
init {
viewModelScope.launch {
try {
val user = userRepository.getUserById(userId)
_user.value = user
} catch (error: Exception) {
// show error message to user
}
}
}
}Check Network Connection
fun isOnline(): Boolean {
val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE)
as ConnectivityManager
val networkInfo: NetworkInfo? = connMgr.activeNetworkInfo
return networkInfo?.isConnected == true
}Network Callback (Modern API)
connectivityManager.registerDefaultNetworkCallback(object :
ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { ... }
override fun onLost(network: Network) { ... }
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities) { ... }
})Optimizing Network Usage
(getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).apply {
if (isActiveNetworkMetered) {
when (restrictBackgroundStatus) {
RESTRICT_BACKGROUND_STATUS_ENABLED -> { /* blocked */ }
RESTRICT_BACKGROUND_STATUS_WHITELISTED -> { /* allowed */ }
RESTRICT_BACKGROUND_STATUS_DISABLED -> { /* no restriction */ }
}
}
}π¦ BAB 13: Dart & Flutter Syntax
Variabel & Tipe Data
String firstName = 'John'; // Eksplisit
var lastName = 'Smith'; // Tipe diinfer
int age = 20;
double height = 5.9;
bool isMarried = false;
// String interpolation
print('Name is $firstName $lastName');
print('Age is ${age + 1}'); // Gunakan {} untuk expressionfinal vs const (SERING KELUAR)
final date = DateTime.now(); // β
OK, dihitung saat runtime
const date = DateTime.now(); // β Error! const harus compile-time
const pi = 3.14; // β
OK
final gravity; // β Error! final harus diinisialisasi
final gravity = Gravity(); // β
OK
gravity.setForce(...); // β
OK, object-nya masih mutable!β οΈ final = referensi-nya fixed, tapi isi objeknya masih bisa berubah!
β οΈ const = semuanya fixed + canonicalized (1 copy di memory).
const Canonicalization
const point1 = ImmutablePoint(2, 3);
const point2 = ImmutablePoint(2, 3);
print(identical(point1, point2)); // true β SATU OBJEK di memory
final point3 = ImmutablePoint(2, 3);
final point4 = ImmutablePoint(2, 3);
print(identical(point3, point4)); // false β DUA OBJEK berbedaNull Safety (Dart 3.0)
int a = 100; // Non-nullable, WAJIB ada nilai
int b; // β Compile-time error!
int? c; // β
Nullable, boleh null
late int d; // β
Non-nullable tapi diinisialisasi nanti
print(d); // β Runtime error: belum diinisialisasi!
d = 3;
print(d); // β
OKNull-aware Operators (WAJIB HAFAL)
String? str;
print(str.length); // β Compile-time error
print(str?.length); // β
Print null jika str null (safe call)
print(str ?? 'Default'); // β
Print 'Default' jika str null (elvis)
str ??= 'Default'; // β
Assign 'Default' HANYA jika str null
int length = str!.length; // β οΈ Paksa non-null (crash jika null!)Object vs dynamic
Object obj = 'Hello';
print(obj.length); // β Compile-time error (Object gak punya .length)
print((obj as String).length); // β
OK, cast dulu
dynamic dyn = 'Hello';
print(dyn.length); // β
OK (bypass compile-time check)
print(dyn.isEven); // β Runtime error! String gak punya isEvenType Checking & Conversion
// Type check
if (obj is String) { ... }
if (obj is! int) { ... }
print(obj.runtimeType);
// Conversion
int one = int.parse('1'); // String β int
String oneStr = one.toString(); // int β String
pi.toInt(); // double β int
String str = obj as String; // Parent β child typeFunctions: Parameters & Default Values
// Optional positional parameters
void sayMessage(String message, [String? author]) {
print("$message -- ${author ?? 'Anonymous'}");
}
// Named parameters & default values
void setDimensions({int width = 10, int height = 10}) {
print("Width: $width, Height: $height");
}
void main() {
sayMessage('Hello, Dart!');
setDimensions(height: 20, width: 30); // Urutan bebas!
}Arrow Functions
// Normal
int add(int a, int b) { return a + b; }
// Arrow (HANYA untuk 1 expression yang return value)
int add(int a, int b) => a + b;β οΈ Jangan pakai arrow kalau fungsi tidak return apa-apa!
Higher-Order Functions & Anonymous Functions
// Function sebagai parameter
void printOPResult(int a, int b, Function(int, int) op) {
var result = op(a, b);
print('Result: $result');
}
// Anonymous function & forEach
myList.forEach((e) { print(e); });
var myList2 = myList.map((e) => e * 2).toList();Closures
Function makeAdder(int base) {
return (int i) => base + i; // Captures 'base'
}
var addFrom2 = makeAdder(2);
print(addFrom2(10)); // 12β οΈ Dart: Closure di loop AMAN (tiap iterasi punya copy sendiri). JavaScript: TIDAK aman (bad closure).
Classes & Constructors
class Person {
String name;
int age;
// Constructor
Person({required this.name, required this.age});
// Named constructor
Person.fromMap(Map<String, dynamic> data)
: name = data['name'],
age = data['age'];
void displayInfo() {
print('Name: $name, Age: $age');
}
}Inheritance
class Student extends Person {
String university;
Student({
required super.name,
required super.age,
required this.university
});
@override
void displayInfo() {
super.displayInfo();
print('University: $university');
}
}
// Type check
Person alice = Student(...);
assert(alice is Person); // true
assert(alice is Student); // true
assert(alice is! Employee); // true
var studentAlice = alice as Student; // DowncastAbstract Class (Dart gak punya keyword interface)
abstract class GameCharacter {
String name;
GameCharacter(this.name);
void performAbility(); // Harus di-implement oleh subclass
}Mixin (Multiple Inheritance)
mixin Logger {
void log(String message) { /* persist message */ }
}
class OrderProcessor extends Processor with Logger, ... {
void createOrder(String details) {
log('Order created: $details'); // Method dari mixin!
}
}Extension Methods
extension EmailValidator on String {
bool get isValidEmail {
return RegExp(r'^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+')
.hasMatch(this);
}
}
var email = '[email protected]';
if (email.isValidEmail) { ... } // Pakai langsung pada String!Enums & Switch Expression
enum Status { loading, success, error }
Status status = .loading; // Shorthand Dart 3
// Switch EXPRESSION (mengembalikan value)
var token = switch (char) {
'+' || '-' || '*' || '/' => 'Operator',
',' || ';' => 'Punctuation',
_ => throw FormatException('Invalid character'),
};Spread, if, for in Collection Literals
bool includeOddNumbers = true;
var numbers = [1, 2, 3, 4, 5];
var evenNumbers = [2, 4];
var combinedList = [
if (includeOddNumbers) ...numbers.where((n) => n.isOdd),
for (var n in evenNumbers) n,
];
// Result: [1, 3, 5, 2, 4]StatefulWidget Pattern (HAFAL!)
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() { // β Ini yang trigger rebuild!
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Text('Counter: $_counter');
}
}β οΈ Tanpa setState(), UI TIDAK akan update walaupun variabel berubah!
Dart Coding Conventions (bisa keluar di soal pilihan ganda)
| Apa | Format | Contoh |
|---|---|---|
| Variable/function | lowerCamelCase | myVariable, getUserName() |
| Class/extension | UpperCamelCase | MyClass, EmailValidator |
| Package | snake_case | my_package |
| Private | Leading _ | _age, _helper() |
| Global variable | Leading k | kColorScheme |
| String | Single-quoted | 'hello' (kecuali ada single quote di dalamnya) |
π BAB 14: Mobile Security
Android Manifest Permission
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp">
<uses-permission android:name="android.permission.RECEIVE_SMS" />
...
</manifest>OWASP Top 10 Mobile 2024 (HAFAL URUTAN)
- M1: Improper Credential Usage
- M2: Inadequate Supply Chain Security
- M3: Insecure Authentication/Authorization
- M4: Insufficient Input/Output Validation
- M5: Insecure Communication
- M6: Inadequate Privacy Controls
- M7: Insufficient Binary Protections
- M8: Security Misconfiguration
- M9: Insecure Data Storage
- M10: Insufficient Cryptography
Security Best Practices Summary
- Encrypt: AES 256-bit, simpan key di KeyStore
- Communication: HTTPS, Certificate Pinning, HMAC
- Storage: Internal app-specific storage,
EncryptedSharedPreferences - Permission: Minta minimal, runtime kalau dangerous, jelaskan ke user
- Code: Input validation, hindari dynamic code loading, disable IPC by default
- Identifier: Jangan pakai IMEI β gunakan user-resettable ID
π BAB 15: Jetpack Compose
Basic Composable
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
// Dalam Activity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}Modifier Chaining (URUTAN PENTING!)
val shape = RoundedCornerShape(8.dp)
Text(
text = "Text 1",
modifier = Modifier.fillMaxWidth()
.padding(16.dp) // Padding LUAR
.border(2.dp, color, shape)
.padding(1.dp)
.background(color, shape)
.clickable(onClick = { })
.padding(16.dp) // Padding DALAM
)β οΈ padding sebelum background = space di LUAR background. Setelah = space di DALAM.
Layouts: Column, Row, Box
Box(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(16.dp).fillMaxWidth()) {
Text("Column Text 1")
Text("Column Text 2")
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(text = "Row Text 1")
Text(text = "Row Text 2")
}
}
Text("Stack Text", modifier = Modifier
.align(Alignment.TopEnd)
.padding(end = 16.dp, top = 16.dp)
)
}- Column = vertikal (atas-bawah)
- Row = horizontal (kiri-kanan)
- Box = stack/tumpuk (elemen terakhir di atas)
State Management (PALING PENTING)
// State yang survive recomposition
var nameState by remember { mutableStateOf("") }
TextField(
value = nameState,
label = { Text("Name") },
onValueChange = { s: String -> nameState = s }
)rememberSaveable (Survive rotation)
var state by rememberSaveable {
mutableStateOf("Compose!!!")
}Observable State in Class
class Score(team: String, score: Int) {
var team by mutableStateOf(team)
var score by mutableStateOf(score)
}
// Perubahan langsung trigger recomposition!
score.score += 1LiveData β Compose
@Composable
fun UserScreen(usersLiveData: LiveData<List<UserBinding>>) {
val users by usersLiveData.observeAsState()
// users sekarang bisa dipakai langsung
}Alternatives: Flow.collectAsState(), Observable.subscribeAsState()
ViewModel + Compose (Full Pattern)
@Composable
fun UserScreen(viewModel: UsersViewModel = viewModel()) {
val users by viewModel.allUsers.observeAsState()
Column(modifier = Modifier.fillMaxSize()) {
UserList(
users = users ?: emptyList(),
onDeleteUser = { user -> viewModel.deleteUser(user) }
)
}
}LazyColumn (RecyclerView replacement)
@Composable
fun UserListScreen(users: List<User>) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item {
Text("Header", Modifier.fillMaxWidth().padding(8.dp))
}
items(users) { user ->
Text("${user.name} - ${user.age}", Modifier.fillMaxWidth().padding(8.dp))
}
}
}Variant: itemsIndexed(users) { index, user -> ... } untuk akses index.
Scaffold (App Structure)
Scaffold(
topBar = {
TopAppBar(
title = { Text("Compose") },
actions = {
IconButton(onClick = {}) {
Icon(Icons.Default.Search, "Search")
}
}
)
},
floatingActionButton = {
FloatingActionButton(onClick = { }) {
Icon(Icons.Filled.Add, "Add")
}
},
bottomBar = { ... }
) { /* content */ }Side Effects
// LaunchedEffect: Jalankan coroutine saat composable pertama kali muncul
LaunchedEffect(someKey) {
val s = withContext(Dispatchers.IO) {
delay(5_000)
"Hello Compose!"
}
welcomeMsg.value = s
}
// DisposableEffect: Cleanup saat composable dihancurkan
DisposableEffect(someKey) {
// setup listener
onDispose {
// Cleanup!
}
}
// rememberCoroutineScope: Launch dari callback
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
// do async work
}
})Interop: Compose dalam Fragment
class MyFragment : Fragment() {
override fun onCreateView(...): View? {
return ComposeView(requireContext()).apply {
setContent {
AppTheme { YourComposable() }
}
}
}
}Interop: Old View dalam Compose
@Composable
fun MyCalendar(onDateUpdate: (Date) -> Unit) {
AndroidView(
factory = { context: Context ->
LayoutInflater.from(context)
.inflate(R.layout.my_layout, null, false)
},
update = { view ->
// Update view saat recomposition
}
)
}Compose Resource Access
stringResource(R.string.your_string)
dimensionResource(R.dimen.padding_small)
colorResource(R.color.blue)π― Quick Reference: βApa yang Salah dengan Snippet Ini?β
β Network di Main Thread
fun loadData() {
val data = URL("https://...").readText() // CRASH! NetworkOnMainThreadException
}Fix: Wrap dalam viewModelScope.launch { withContext(Dispatchers.IO) { ... } }
β Room query tanpa suspend
@Query("SELECT * FROM colors")
fun getAll(): List<Color> // β οΈ Kalau dipanggil dari Main Thread β CRASH!Fix: Tambahkan suspend β suspend fun getAll(): List<Color>
β Dart: Modifikasi const
const list = [1, 2, 3];
list.add(4); // β Runtime Error! const = immutableβ Flutter: Update state tanpa setState
void _increment() {
_counter++; // Variabel berubah, tapi UI TIDAK update
}Fix: Wrap dalam setState(() { _counter++; })
β Compose: State tanpa remember
@Composable
fun Counter() {
var count = mutableStateOf(0) // β Reset setiap recomposition!
Button(onClick = { count.value++ }) { Text("${count.value}") }
}Fix: var count by remember { mutableStateOf(0) }