Tugas Pertemuan 12 Pemrograman Perangkat Bergerak B
Nama : Fadaukas Daffa Tajuddin
NRP : 5025231149
Kelas : PPB (B)
Membuat Login Sederhana
Aplikasi ini dirancang untuk mendemonstrasikan alur autentikasi sederhana (Login & Registrasi) dengan penyimpanan lokal. Menggunakan pola arsitektur MVVM untuk memisahkan logika bisnis dari antarmuka pengguna, serta Room Database sebagai Single Source of Truth untuk data pengguna.
Data Layer (Model & Room)
AppDatabase
package com.example.login.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
).build()
INSTANCE = instance
instance
}
}
}
}
User
package com.example.login.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val username: String,
val password: String
)
UserDao
package com.example.login.data
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE username = :username AND password = :password LIMIT 1")
suspend fun login(username: String, password: String): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun register(user: User)
}
UserRepository
package com.example.login.data
class UserRepository(private val userDao: UserDao) {
suspend fun login(username: String, password: String): User? {
return userDao.login(username, password)
}
suspend fun register(user: User) {
userDao.register(user)
}
}
Logic Layer
LoginViewModel
package com.example.login.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.example.login.data.User
import com.example.login.data.UserRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class LoginViewModel(private val repository: UserRepository) : ViewModel() {
private val _loginStatus = MutableStateFlow<String?>(null)
val loginStatus: StateFlow<String?> = _loginStatus
fun login(username: String, password: String) {
if (username.isBlank() || password.isBlank()) {
_loginStatus.value = "Username dan password tidak boleh kosong"
return
}
viewModelScope.launch {
val user = repository.login(username, password)
if (user != null) {
_loginStatus.value = "Login Berhasil! Selamat datang, ${user.username}"
} else {
_loginStatus.value = "Login Gagal! Username atau password salah"
}
}
}
fun register(username: String, password: String) {
if (username.isBlank() || password.isBlank()) {
_loginStatus.value = "Username dan password tidak boleh kosong"
return
}
viewModelScope.launch {
repository.register(User(username = username, password = password))
_loginStatus.value = "Registrasi Berhasil! Silakan Login"
}
}
fun resetStatus() {
_loginStatus.value = null
}
}
class LoginViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return LoginViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
UI Layer
LoginScreen
package com.example.login.ui
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
@Composable
fun LoginScreen(viewModel: LoginViewModel, modifier: Modifier = Modifier) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
val loginStatus by viewModel.loginStatus.collectAsState()
val isInputValid = username.isNotBlank() && password.isNotBlank()
Column(
modifier = modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Login", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(32.dp))
OutlinedTextField(
value = username,
onValueChange = {
username = it
viewModel.resetStatus()
},
label = { Text("Username") },
modifier = Modifier.fillMaxWidth(),
isError = username.isEmpty() && loginStatus != null && loginStatus!!.contains("kosong"),
singleLine = true
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = password,
onValueChange = {
password = it
viewModel.resetStatus()
},
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth(),
isError = password.isEmpty() && loginStatus != null && loginStatus!!.contains("kosong"),
singleLine = true
)
Spacer(modifier = Modifier.height(16.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
Button(
onClick = { viewModel.login(username, password) },
enabled = isInputValid
) {
Text("Login")
}
Button(
onClick = { viewModel.register(username, password) },
enabled = isInputValid
) {
Text("Register")
}
}
Spacer(modifier = Modifier.height(16.dp))
loginStatus?.let { status ->
Text(
text = status,
color = if (status.contains("Berhasil")) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
)
}
}
}
Fitur Utama
- Validasi Ketat: Sistem mencegah pengiriman data kosong (null/blank) baik melalui UI (tombol terkunci) maupun logika di ViewModel.
- Room Persistence: Data pengguna tersimpan secara permanen di database lokal smartphone.
- Feedback Reaktif: Menggunakan StateFlow untuk menampilkan pesan sukses/gagal secara instan tanpa perlu memuat ulang halaman.
- Injeksi ViewModel: Menggunakan ViewModelProvider.Factory untuk mengirimkan instance repository ke ViewModel di MainActivity.
Komentar
Posting Komentar