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

Postingan populer dari blog ini

Tugas Pertemuan 2 Pemrograman Perangkat Bergerak B

Tugas Pertemuan 3 Pemrograman Perangkat Bergerak B