577 lines
13 KiB
Markdown
577 lines
13 KiB
Markdown
# 💾 CODE SNIPPETS REFERENCE - Cepat Copy-Paste
|
|
|
|
## 📌 Useful Code Blocks untuk Development
|
|
|
|
---
|
|
|
|
## 🔐 PASSWORD HASHING (Future Enhancement)
|
|
|
|
Jika ingin menambahkan password hashing di Phase 2:
|
|
|
|
### Method Hashing
|
|
```kotlin
|
|
import java.security.MessageDigest
|
|
import android.util.Base64
|
|
|
|
fun hashPassword(password: String): String {
|
|
val md = MessageDigest.getInstance("SHA-256")
|
|
val hashedBytes = md.digest(password.toByteArray())
|
|
return Base64.encodeToString(hashedBytes, Base64.NO_WRAP)
|
|
}
|
|
|
|
fun verifyPassword(password: String, hash: String): Boolean {
|
|
return hashPassword(password) == hash
|
|
}
|
|
```
|
|
|
|
### Usage di DatabaseHelper
|
|
```kotlin
|
|
fun addUser(username: String, npm: String, pass: String): Boolean {
|
|
return try {
|
|
val db = this.writableDatabase
|
|
val values = ContentValues()
|
|
values.put(COLUMN_USERNAME, username)
|
|
values.put(COLUMN_NPM, npm)
|
|
values.put(COLUMN_PASSWORD, hashPassword(pass)) // ← Hash sebelum simpan
|
|
val result = db.insert(TABLE_USERS, null, values)
|
|
result != -1L
|
|
} catch (e: Exception) {
|
|
android.util.Log.e("DatabaseHelper", "Error adding user: ${e.message}")
|
|
false
|
|
}
|
|
}
|
|
|
|
fun checkUser(npm: String, pass: String): Boolean {
|
|
return try {
|
|
val db = this.readableDatabase
|
|
val cursor = db.rawQuery(
|
|
"SELECT $COLUMN_PASSWORD FROM $TABLE_USERS WHERE $COLUMN_NPM=?",
|
|
arrayOf(npm)
|
|
)
|
|
var matches = false
|
|
if (cursor.moveToFirst()) {
|
|
val storedHash = cursor.getString(0)
|
|
matches = hashPassword(pass) == storedHash // ← Verify hash
|
|
}
|
|
cursor.close()
|
|
matches
|
|
} catch (e: Exception) {
|
|
android.util.Log.e("DatabaseHelper", "Error checking user: ${e.message}")
|
|
false
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 DATABASE INSPECTION
|
|
|
|
### Via ADB Terminal
|
|
```bash
|
|
# Connect device/emulator
|
|
adb shell
|
|
|
|
# Navigate to database
|
|
cd /data/data/id.ac.ubharajaya.sistemakademik/databases/
|
|
|
|
# Open SQLite
|
|
sqlite3 Akademik.db
|
|
|
|
# View all users
|
|
SELECT * FROM users;
|
|
|
|
# View specific user
|
|
SELECT * FROM users WHERE npm='20231071513';
|
|
|
|
# View attendance records
|
|
SELECT * FROM attendance;
|
|
|
|
# Count records
|
|
SELECT COUNT(*) FROM users;
|
|
SELECT COUNT(*) FROM attendance;
|
|
|
|
# Exit
|
|
.exit
|
|
```
|
|
|
|
### Via Android Studio (Built-in)
|
|
```
|
|
1. View → Tool Windows → App Inspection
|
|
2. Select Device
|
|
3. Navigate to Database section
|
|
4. Browse tables visually
|
|
```
|
|
|
|
---
|
|
|
|
## 🔍 LOGGING EXAMPLES
|
|
|
|
### Current Logging (Already Added)
|
|
```kotlin
|
|
android.util.Log.e("DatabaseHelper", "Error adding user: ${e.message}")
|
|
android.util.Log.e("DatabaseHelper", "Error checking user exists: ${e.message}")
|
|
android.util.Log.i("RegisterScreen", "Registration started")
|
|
```
|
|
|
|
### Advanced Logging (Optional Enhancement)
|
|
```kotlin
|
|
// Add different log levels
|
|
android.util.Log.v("TAG", "Verbose message") // Least important
|
|
android.util.Log.d("TAG", "Debug message") // Debug info
|
|
android.util.Log.i("TAG", "Info message") // General info
|
|
android.util.Log.w("TAG", "Warning message") // Warnings
|
|
android.util.Log.e("TAG", "Error message") // Errors (Most important)
|
|
|
|
// Example in context
|
|
fun addUser(username: String, npm: String, pass: String): Boolean {
|
|
android.util.Log.i("DatabaseHelper", "Starting user registration: $npm")
|
|
return try {
|
|
val db = this.writableDatabase
|
|
// ... code ...
|
|
android.util.Log.d("DatabaseHelper", "User added successfully: $npm")
|
|
result != -1L
|
|
} catch (e: SQLiteIntegrityConstraintException) {
|
|
android.util.Log.w("DatabaseHelper", "Duplicate NPM: $npm")
|
|
false
|
|
} catch (e: Exception) {
|
|
android.util.Log.e("DatabaseHelper", "Error adding user: ${e.message}", e)
|
|
false
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 TEST DATA
|
|
|
|
### Valid Test Data
|
|
```kotlin
|
|
// Success case
|
|
val testData1 = mapOf(
|
|
"nama" to "Febby Dwiss",
|
|
"npm" to "20231071513",
|
|
"password" to "password123"
|
|
)
|
|
|
|
// Another valid case
|
|
val testData2 = mapOf(
|
|
"nama" to "Test User",
|
|
"npm" to "20231071234",
|
|
"password" to "test1234"
|
|
)
|
|
|
|
// Edge case - minimum valid
|
|
val testData3 = mapOf(
|
|
"nama" to "A",
|
|
"npm" to "10000000",
|
|
"password" to "123456"
|
|
)
|
|
```
|
|
|
|
### Invalid Test Data
|
|
```kotlin
|
|
// NPM too short
|
|
val invalidData1 = mapOf(
|
|
"nama" to "Test",
|
|
"npm" to "2023107", // 7 digits
|
|
"password" to "pass123"
|
|
)
|
|
|
|
// NPM with letters
|
|
val invalidData2 = mapOf(
|
|
"nama" to "Test",
|
|
"npm" to "2023ABC78", // Contains letters
|
|
"password" to "pass123"
|
|
)
|
|
|
|
// Password too short
|
|
val invalidData3 = mapOf(
|
|
"nama" to "Test",
|
|
"npm" to "20231071513",
|
|
"password" to "pass1" // 5 chars
|
|
)
|
|
|
|
// Empty field
|
|
val invalidData4 = mapOf(
|
|
"nama" to "", // Empty
|
|
"npm" to "20231071513",
|
|
"password" to "pass123"
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 UI IMPROVEMENTS (Optional)
|
|
|
|
### Add Loading State
|
|
```kotlin
|
|
var isLoading by remember { mutableStateOf(false) }
|
|
|
|
Button(
|
|
onClick = {
|
|
if (/* all validations pass */) {
|
|
isLoading = true
|
|
try {
|
|
if (db.addUser(username, npm, password)) {
|
|
// Success
|
|
isLoading = false
|
|
}
|
|
} catch (e: Exception) {
|
|
isLoading = false
|
|
}
|
|
}
|
|
},
|
|
enabled = !isLoading, // Disable button during loading
|
|
modifier = Modifier.fillMaxWidth(),
|
|
) {
|
|
if (isLoading) {
|
|
CircularProgressIndicator(modifier = Modifier.size(20.dp))
|
|
} else {
|
|
Text("Daftar")
|
|
}
|
|
}
|
|
```
|
|
|
|
### Add Input Error States
|
|
```kotlin
|
|
var usernameError by remember { mutableStateOf("") }
|
|
var npmError by remember { mutableStateOf("") }
|
|
var passwordError by remember { mutableStateOf("") }
|
|
|
|
OutlinedTextField(
|
|
value = username,
|
|
onValueChange = {
|
|
username = it
|
|
usernameError = if (it.isBlank()) "Nama tidak boleh kosong" else ""
|
|
},
|
|
label = { Text("Nama Lengkap") },
|
|
isError = usernameError.isNotEmpty(), // Show error state
|
|
supportingText = {
|
|
if (usernameError.isNotEmpty()) {
|
|
Text(usernameError, color = Color.Red)
|
|
}
|
|
},
|
|
// ... other properties
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 🔄 API INTEGRATION (Future)
|
|
|
|
### Send to Server
|
|
```kotlin
|
|
fun sendRegistrationToServer(
|
|
username: String,
|
|
npm: String,
|
|
password: String,
|
|
onSuccess: () -> Unit,
|
|
onError: (String) -> Unit
|
|
) {
|
|
thread {
|
|
try {
|
|
val url = URL("https://your-api.com/register")
|
|
val conn = url.openConnection() as HttpURLConnection
|
|
|
|
conn.requestMethod = "POST"
|
|
conn.setRequestProperty("Content-Type", "application/json")
|
|
conn.doOutput = true
|
|
|
|
val json = JSONObject().apply {
|
|
put("nama", username)
|
|
put("npm", npm)
|
|
put("password", password)
|
|
}
|
|
|
|
conn.outputStream.use {
|
|
it.write(json.toString().toByteArray())
|
|
}
|
|
|
|
val responseCode = conn.responseCode
|
|
|
|
if (responseCode == 200 || responseCode == 201) {
|
|
onSuccess()
|
|
} else {
|
|
onError("Server error: $responseCode")
|
|
}
|
|
|
|
conn.disconnect()
|
|
} catch (e: Exception) {
|
|
onError("Network error: ${e.message}")
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📱 PERMISSION HANDLING
|
|
|
|
### For Absensi Feature (GPS + Camera)
|
|
```kotlin
|
|
// In AndroidManifest.xml
|
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
<uses-permission android:name="android.permission.CAMERA" />
|
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
|
|
// In Compose
|
|
val locationPermissionLauncher = rememberLauncherForActivityResult(
|
|
ActivityResultContracts.RequestPermission()
|
|
) { granted ->
|
|
if (granted) {
|
|
// Request location
|
|
} else {
|
|
Toast.makeText(context, "Location permission denied", Toast.LENGTH_SHORT).show()
|
|
}
|
|
}
|
|
|
|
Button(onClick = {
|
|
locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
|
|
}) {
|
|
Text("Enable Location")
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 VALIDATION HELPERS
|
|
|
|
### Reusable Validation Functions
|
|
```kotlin
|
|
// NPM validation
|
|
fun isValidNPM(npm: String): Boolean {
|
|
return npm.length >= 8 && npm.all { it.isDigit() }
|
|
}
|
|
|
|
// Password validation
|
|
fun isValidPassword(password: String): Boolean {
|
|
return password.length >= 6
|
|
}
|
|
|
|
// Email validation (if needed)
|
|
fun isValidEmail(email: String): Boolean {
|
|
return email.matches(Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"))
|
|
}
|
|
|
|
// Usage
|
|
if (!isValidNPM(npm)) {
|
|
Toast.makeText(context, "NPM tidak valid", Toast.LENGTH_SHORT).show()
|
|
return
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔐 SHARED PREFERENCES
|
|
|
|
### Save User Session (Optional)
|
|
```kotlin
|
|
// Save session
|
|
val sharedPref = context.getSharedPreferences("user_session", Context.MODE_PRIVATE)
|
|
sharedPref.edit().apply {
|
|
putString("npm", npm)
|
|
putString("nama", nama)
|
|
putBoolean("is_logged_in", true)
|
|
apply()
|
|
}
|
|
|
|
// Retrieve session
|
|
val npm = sharedPref.getString("npm", null)
|
|
val nama = sharedPref.getString("nama", null)
|
|
val isLoggedIn = sharedPref.getBoolean("is_logged_in", false)
|
|
|
|
// Clear session on logout
|
|
sharedPref.edit().clear().apply()
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 UNIT TESTS
|
|
|
|
### Example Test Cases (For advanced users)
|
|
```kotlin
|
|
// Using JUnit
|
|
import org.junit.Test
|
|
import org.junit.Before
|
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
import org.junit.runner.RunWith
|
|
|
|
@RunWith(AndroidJUnit4::class)
|
|
class DatabaseHelperTest {
|
|
|
|
private lateinit var db: DatabaseHelper
|
|
|
|
@Before
|
|
fun setUp() {
|
|
val context = InstrumentationRegistry.getInstrumentation().context
|
|
db = DatabaseHelper(context)
|
|
}
|
|
|
|
@Test
|
|
fun testAddUser_ValidData_ReturnTrue() {
|
|
val result = db.addUser("Test User", "20231071513", "password123")
|
|
assertTrue(result)
|
|
}
|
|
|
|
@Test
|
|
fun testAddUser_DuplicateNPM_ReturnFalse() {
|
|
db.addUser("User 1", "20231071513", "pass123")
|
|
val result = db.addUser("User 2", "20231071513", "pass456")
|
|
assertFalse(result)
|
|
}
|
|
|
|
@Test
|
|
fun testCheckUser_ValidCredentials_ReturnTrue() {
|
|
db.addUser("Test User", "20231071513", "password123")
|
|
val result = db.checkUser("20231071513", "password123")
|
|
assertTrue(result)
|
|
}
|
|
|
|
@Test
|
|
fun testCheckUser_InvalidPassword_ReturnFalse() {
|
|
db.addUser("Test User", "20231071513", "password123")
|
|
val result = db.checkUser("20231071513", "wrongpassword")
|
|
assertFalse(result)
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 PERFORMANCE OPTIMIZATION
|
|
|
|
### Database Query Optimization
|
|
```kotlin
|
|
// INSTEAD OF (slow):
|
|
fun getAllUsers(): List<User> {
|
|
val users = mutableListOf<User>()
|
|
val cursor = db.rawQuery("SELECT * FROM users", null)
|
|
if (cursor.moveToFirst()) {
|
|
do {
|
|
users.add(User(...))
|
|
} while (cursor.moveToNext())
|
|
}
|
|
cursor.close()
|
|
return users
|
|
}
|
|
|
|
// USE (faster):
|
|
fun getAllUsers(): List<User> {
|
|
val users = mutableListOf<User>()
|
|
val cursor = db.query(
|
|
"users",
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null
|
|
)
|
|
cursor?.use {
|
|
if (it.moveToFirst()) {
|
|
do {
|
|
users.add(User(...))
|
|
} while (it.moveToNext())
|
|
}
|
|
}
|
|
return users
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 COLOR & THEME CUSTOMIZATION
|
|
|
|
### In themes/colors
|
|
```kotlin
|
|
// colors.kt
|
|
val PrimaryPink = Color(0xFFE91E63)
|
|
val SecondaryPink = Color(0xFFF48FB1)
|
|
val ErrorRed = Color(0xFFE53935)
|
|
val SuccessGreen = Color(0xFF43A047)
|
|
|
|
// Usage in UI
|
|
OutlinedTextField(
|
|
colors = OutlinedTextFieldDefaults.colors(
|
|
focusedBorderColor = PrimaryPink,
|
|
unfocusedBorderColor = SecondaryPink,
|
|
focusedLabelColor = PrimaryPink,
|
|
unfocusedLabelColor = SecondaryPink
|
|
)
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 💡 COMMON PATTERNS
|
|
|
|
### Safe Database Access
|
|
```kotlin
|
|
inline fun <T> withDatabase(block: (DatabaseHelper) -> T): T? {
|
|
return try {
|
|
block(db)
|
|
} catch (e: Exception) {
|
|
android.util.Log.e("Database", "Error: ${e.message}")
|
|
null
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
withDatabase { db ->
|
|
db.addUser("Name", "npm", "pass")
|
|
} ?: run {
|
|
Toast.makeText(context, "Database error", Toast.LENGTH_SHORT).show()
|
|
}
|
|
```
|
|
|
|
### Result Wrapper
|
|
```kotlin
|
|
sealed class Result<out T> {
|
|
data class Success<T>(val data: T) : Result<T>()
|
|
data class Error(val exception: Exception) : Result<Nothing>()
|
|
object Loading : Result<Nothing>()
|
|
}
|
|
|
|
fun addUserWithResult(username: String, npm: String, password: String): Result<Boolean> {
|
|
return try {
|
|
val result = db.addUser(username, npm, password)
|
|
Result.Success(result)
|
|
} catch (e: Exception) {
|
|
Result.Error(e)
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
when (val result = addUserWithResult(name, npm, pass)) {
|
|
is Result.Success -> {
|
|
Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show()
|
|
}
|
|
is Result.Error -> {
|
|
Toast.makeText(context, result.exception.message, Toast.LENGTH_SHORT).show()
|
|
}
|
|
is Result.Loading -> {
|
|
// Show loading
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 NOTES
|
|
|
|
- **Gunakan snippet ini dengan bijak** - Pastikan memahami apa yang dilakukan
|
|
- **Test setiap perubahan** - Jangan langsung ke production
|
|
- **Keep backups** - Simpan versi working
|
|
- **Follow patterns** - Konsisten dengan codebase yang ada
|
|
- **Add comments** - Document why, not what
|
|
|
|
---
|
|
|
|
**Happy Coding!** 💻✨
|
|
|
|
Untuk lebih lanjut, lihat:
|
|
- [Kotlin Documentation](https://kotlinlang.org/docs/)
|
|
- [Android Developers Guide](https://developer.android.com/)
|
|
- [Jetpack Compose Tutorial](https://developer.android.com/jetpack/compose)
|
|
|