Pertemuan 12
Pada latihan ini, kami membuat aplikasi Login App menggunakan Kotlin dan View Binding di Android Studio. Aplikasi ini merupakan sistem autentikasi sederhana yang menyimpan data pengguna secara lokal menggunakan Room Database. Konsep utama yang diterapkan meliputi pembuatan Entity dan DAO untuk operasi database, pengelolaan navigasi antar halaman dengan Intent, validasi input form, serta operasi database asinkron menggunakan Coroutines dan lifecycleScope.
com.example.loginapp, bahasa Kotlin, dan Minimum SDK API 24.gradle/libs.versions.toml yaitu room = "2.7.1", ksp = "2.0.21-1.0.28", dan coroutines = "1.9.0", lalu mendaftarkan library dan plugin terkait pada bagian [libraries] dan [plugins].ksp dan fitur viewBinding = true di app/build.gradle.kts, serta menambahkan dependency room-runtime, room-ktx, room-compiler (via ksp), dan kotlinx-coroutines-android. Menambahkan android.disallowKotlinSourceSets=false di gradle.properties agar KSP kompatibel dengan AGP 9.x.data/ dengan anotasi @Entity(tableName = "users"), properti id sebagai @PrimaryKey(autoGenerate = true), serta properti username dan password.@Dao berisi tiga fungsi suspend: insertUser() dengan @Insert, login() dengan query SELECT berdasarkan username dan password, serta getUserByUsername() untuk mengecek duplikasi username saat registrasi.@Database yang mengextend RoomDatabase, mengimplementasikan pola Singleton menggunakan companion object dan blok synchronized agar hanya satu instance database yang dibuat selama siklus hidup aplikasi.activity_main.xml sebagai halaman login menggunakan ScrollView dan ConstraintLayout, berisi logo aplikasi, judul, TextInputLayout untuk username dan password (dengan toggle visibilitas), TextView untuk pesan hasil login, tombol Masuk, dan link teks Daftar.activity_register.xml dengan tiga field input (username, password, konfirmasi password) dan activity_home.xml dengan CardView bergradien ungu yang menampilkan nama pengguna yang berhasil login beserta tombol Keluar.lifecycleScope.launch untuk memanggil userDao().login() secara asinkron. Jika berhasil, aplikasi berpindah ke HomeActivity dengan membawa data username melalui Intent.putExtra(); jika gagal, menampilkan pesan error berwarna merah.OnBackPressedCallback (API modern, non-deprecated).ORM resmi Android untuk penyimpanan data lokal — menyediakan abstraksi di atas SQLite dengan type-safety dan integrasi Kotlin Coroutines.
Entity mendefinisikan struktur tabel database, sedangkan DAO menyediakan antarmuka fungsi untuk operasi insert, query, update, dan delete.
Fitur AGP yang menghasilkan class binding untuk setiap layout — menggantikan findViewById dengan akses view yang type-safe dan null-safe.
Coroutine scope yang terikat pada siklus hidup Activity — secara otomatis membatalkan coroutine ketika Activity dihancurkan, mencegah memory leak.
Pola desain yang memastikan hanya satu instance AppDatabase yang ada, diimplementasikan dengan @Volatile dan synchronized.
Mekanisme perpindahan antar Activity menggunakan Intent dengan putExtra() untuk mengirim data, dan flag FLAG_ACTIVITY_CLEAR_TASK untuk membersihkan back stack saat logout.
package com.example.loginapp.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 )
package com.example.loginapp.data import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query @Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.ABORT) suspend fun insertUser(user: User) @Query("SELECT * FROM users WHERE username = :username AND password = :password LIMIT 1") suspend fun login(username: String, password: String): User? @Query("SELECT * FROM users WHERE username = :username LIMIT 1") suspend fun getUserByUsername(username: String): User? }
package com.example.loginapp.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, "login_database" ).build() INSTANCE = instance instance } } } }
package com.example.loginapp import android.content.Intent import android.graphics.Color import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.example.loginapp.data.AppDatabase import com.example.loginapp.databinding.ActivityMainBinding import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var db: AppDatabase override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) db = AppDatabase.getDatabase(this) binding.btnLogin.setOnClickListener { doLogin() } binding.tvRegister.setOnClickListener { startActivity(Intent(this, RegisterActivity::class.java)) } } private fun doLogin() { val username = binding.etUsername.text.toString().trim() val password = binding.etPassword.text.toString().trim() if (username.isEmpty()) { binding.tilUsername.error = "Username tidak boleh kosong"; return } else { binding.tilUsername.error = null } if (password.isEmpty()) { binding.tilPassword.error = "Password tidak boleh kosong"; return } else { binding.tilPassword.error = null } binding.btnLogin.isEnabled = false lifecycleScope.launch { val user = db.userDao().login(username, password) if (user != null) { showMessage("Login berhasil! Selamat datang, ${"{"}user.username{"}"}", isSuccess = true) val intent = Intent(this@MainActivity, HomeActivity::class.java) intent.putExtra("USERNAME", user.username) startActivity(intent) finish() } else { showMessage("Username atau password salah", isSuccess = false) binding.btnLogin.isEnabled = true } } } private fun showMessage(message: String, isSuccess: Boolean) { binding.tvMessage.apply { text = message setTextColor(if (isSuccess) Color.parseColor("#FF4CAF50") else Color.parseColor("#FFB00020")) visibility = View.VISIBLE } } }
Komentar
Posting Komentar