13 KiB
13 KiB
📖 Panduan Implementasi Aplikasi Absensi Akademik
🎯 Gambaran Umum Implementasi
Aplikasi ini telah diimplementasikan dengan arsitektur yang modular dan clean, memisahkan concerns menjadi beberapa layers:
- UI Layer (Compose Components)
- Network Layer (API Communication)
- Utils Layer (Business Logic)
- Config Layer (Configuration Management)
- Models Layer (Data Classes)
📂 File Structure Lengkap
Core Application
MainActivity.kt # Entry point & main UI screen
Configuration
config/
└─ AttendanceConfig.kt # Centralized configuration
Data Models
models/
└─ AttendanceRecord.kt # Data classes:
# - AttendanceRecord
# - LocationData
# - ValidationResult
# - AttendanceState
# - ValidationStatus enum
Network Communication
network/
└─ N8nService.kt # API service untuk N8n webhook
Utilities
utils/
├─ LocationValidator.kt # Location validation logic:
# - calculateDistance (Haversine)
# - isLocationValid
# - getValidationMessage
# - adjustCoordinates
└─ ErrorHandler.kt # Error handling & messages
UI Components
ui/
├─ components/
│ └─ AttendanceComponents.kt # Reusable components:
│ # - PhotoPreviewCard
│ # - LocationStatusCard
│ # - ErrorAlertCard
│ # - SubmitButtonWithLoader
└─ theme/
├─ Theme.kt # Material 3 theme
├─ Color.kt # Color definitions
└─ Type.kt # Typography definitions
Tests
test/
└─ java/id/ac/ubharajaya/sistemakademik/
└─ utils/
└─ LocationValidatorTest.kt # Unit tests
🔄 Flow Diagram
┌─────────────────────────────────────────────────────────────┐
│ MainActivity │
│ (Jetpack Compose UI + State Management) │
└────────────────────┬────────────────────────────────────────┘
│
┌───────────┴───────────┐
│ │
┌────▼─────────┐ ┌──────▼──────────┐
│ Location │ │ Camera │
│ Permissions │ │ Permissions │
└────┬─────────┘ └──────┬──────────┘
│ │
┌────▼──────────────────────▼────┐
│ LocationValidator │
│ (Haversine distance calc) │
└────┬─────────────────────────────┘
│
┌────▼──────────────────────────┐
│ Validation Result │
│ (Valid/Invalid) │
└────┬─────────────────────────┘
│
┌────▼────────────────────────────────┐
│ N8nService │
│ (HTTP POST to Webhook) │
└────┬────────────────────────────────┘
│
┌────▼────────────────────┐
│ N8n Server │
│ (Webhook Processing) │
└─────────────────────────┘
🚀 Penggunaan Aplikasi
Alur Pengguna
- Buka Aplikasi → MainActivity dimuat
- Minta Izin Lokasi → LocationPermissionLauncher diaktifkan otomatis
- Ambil Lokasi → Fused Location Provider mengambil GPS
- Validasi Lokasi → LocationValidator mengecek jarak
- Tampilkan Status → LocationStatusCard menampilkan hasil
- Ambil Foto → CameraPermissionLauncher + Intent Camera
- Preview Foto → PhotoPreviewCard menampilkan hasil
- Validasi Data → Cek semua field terpenuhi
- Kirim Data → N8nService POST ke webhook
- Tampilkan Hasil → Success/Error message
⚙️ State Management
AttendanceState Data Class
data class AttendanceState(
val location: LocationData? = null, // GPS coordinates
val foto: Bitmap? = null, // Camera image
val isLoadingLocation: Boolean = false, // GPS acquiring
val isLoadingSubmit: Boolean = false, // API submitting
val validationResult: ValidationResult = ..., // Location validation
val errorMessage: String? = null, // Error feedback
val isLocationPermissionGranted: Boolean = false,
val isCameraPermissionGranted: Boolean = false
)
State Updates
State diupdate secara reactive menggunakan:
var state by remember { mutableStateOf(AttendanceState()) }
state = state.copy(location = newLocation) // Immutable update
📍 Validasi Lokasi Detil
Haversine Formula
Untuk menghitung jarak akurat antara dua koordinat:
Formula:
a = sin²(Δφ/2) + cos(φ1) × cos(φ2) × sin²(Δλ/2)
c = 2 × atan2(√a, √(1−a))
d = R × c
Di mana:
- φ adalah latitude (dalam radian)
- λ adalah longitude (dalam radian)
- R adalah radius bumi (6,371 km)
Contoh Validasi
// Referensi lokasi kampus
const val REFERENCE_LATITUDE = -7.0
const val REFERENCE_LONGITUDE = 110.4
// Lokasi mahasiswa (dari GPS)
val studentLatitude = -7.0035
val studentLongitude = 110.4042
// Hitung jarak
val distance = LocationValidator.calculateDistance(
REFERENCE_LATITUDE, REFERENCE_LONGITUDE,
studentLatitude, studentLongitude
)
// Result: ~500 meter
// Validasi terhadap radius (100m)
val isValid = distance <= 100.0 // false
📡 API Integration Detail
Request Format
{
"npm": "202310715082",
"nama": "Fazri Abdurrahman",
"latitude": -7.0035,
"longitude": 110.4042,
"timestamp": 1705250400000,
"foto_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEA..."
}
Response Handling
HttpURLConnection.HTTP_OK (200) // Success - absensi diterima
HttpURLConnection.HTTP_BAD_REQUEST (400) // Error - data invalid
HttpURLConnection.HTTP_UNAUTHORIZED (401) // Error - auth failed
HttpURLConnection.HTTP_INTERNAL_ERROR (500) // Error - server error
Error Callback
interface SubmitCallback {
fun onSuccess(responseCode: Int, message: String)
fun onError(error: Throwable, message: String)
}
🎨 UI Components Detail
1. PhotoPreviewCard
PhotoPreviewCard(
bitmap = state.foto, // Bitmap dari camera
onRetake = { /* reset foto */ }
)
- Menampilkan preview foto
- Button "Ambil Ulang" untuk mengganti foto
- Placeholder jika belum ada foto
2. LocationStatusCard
LocationStatusCard(
latitude = state.location?.latitude,
longitude = state.location?.longitude,
validationMessage = state.validationResult.message,
isLoading = state.isLoadingLocation
)
- Menampilkan koordinat GPS
- Pesan validasi (✓ valid atau ✗ invalid)
- Loading spinner saat mengambil lokasi
3. ErrorAlertCard
ErrorAlertCard(
message = state.errorMessage,
onDismiss = { /* hide error */ }
)
- Card merah untuk error messages
- Dismissable dengan tombol X
- Auto-hide saat tidak ada error
4. SubmitButtonWithLoader
SubmitButtonWithLoader(
text = "Kirim Absensi",
onClick = { /* submit */ },
isLoading = state.isLoadingSubmit,
isEnabled = canSubmit
)
- Button dengan loading indicator
- Disabled saat proses
- Spinner bertekstur saat loading
🔐 Permission Handling
Automatic Permission Request
LaunchedEffect(Unit) {
locationPermissionLauncher.launch(
Manifest.permission.ACCESS_FINE_LOCATION
)
}
- Dipanggil otomatis saat app launch
- Request CAMERA saat user klik "Ambil Foto"
Permission Check
if (ContextCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED)
🧪 Testing
Unit Test LocationValidator
./gradlew test
Test cases:
- ✓ Distance calculation accuracy
- ✓ Validation logic (within/outside radius)
- ✓ Message generation
- ✓ Coordinate adjustment
- ✓ Mathematical properties (symmetry, triangle inequality)
Manual Testing Checklist
[ ] Permissions diminta dengan benar
[ ] Lokasi GPS terakses saat connected
[ ] Card lokasi menampilkan koordinat
[ ] Validasi menunjukkan jarak akurat
[ ] Camera intent terbuka saat "Ambil Foto" diklik
[ ] Foto preview ditampilkan setelah capture
[ ] Form disabled saat lokasi invalid
[ ] Submit button hanya enable jika semua valid
[ ] Loading spinner muncul saat submit
[ ] Success message muncul setelah 200 response
[ ] Error message muncul setelah gagal
[ ] Form reset setelah 2 detik sukses
🛠️ Customization Guide
Ubah Koordinat Referensi
File: AttendanceConfig.kt
const val REFERENCE_LATITUDE = -7.025 // Ubah ke lokasi kampus
const val REFERENCE_LONGITUDE = 110.415
Ubah Radius Area
const val ALLOWED_RADIUS_METERS = 150.0 // Ubah ke radius yang diinginkan
Ubah Data Mahasiswa
const val STUDENT_NPM = "202310715082"
const val STUDENT_NAMA = "Fazri Abdurrahman"
Ubah Webhook URL
const val WEBHOOK_PRODUCTION = "https://your-webhook-url/..."
const val WEBHOOK_TEST = "https://your-test-webhook-url/..."
Ubah Photo Quality
const val PHOTO_QUALITY = 80 // 0-100, lebih tinggi = lebih besar file
📊 Monitoring & Debugging
Enable Logging
// Di N8nService, tambahkan:
Log.d("N8nService", "Request: $json")
Log.d("N8nService", "Response Code: $responseCode")
Check Permission Status
val hasLocationPermission = ContextCompat.checkSelfPermission(
context, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
Validate GPS
- Buka Google Maps untuk confirm GPS aktif
- Verifikasi koordinat akurat di Maps
- Test di lokasi berbeda untuk validate radius
Test Webhook
- Gunakan
WEBHOOK_TESTterlebih dahulu - Cek response di N8n dashboard
- Verify data received dengan benar
- Switch ke
WEBHOOK_PRODUCTIONsaat siap
🚨 Common Issues & Solutions
| Issue | Cause | Solution |
|---|---|---|
| GPS tidak berfungsi | Location permission ditolak | Buka Settings > Permissions > Location |
| Lokasi selalu invalid | Koordinat referensi salah | Update REFERENCE_LATITUDE/LONGITUDE |
| Foto tidak terakses | Camera permission ditolak | Buka Settings > Permissions > Camera |
| Submit gagal | Network issue | Check internet connection |
| Webhook 404 | URL salah | Verify webhook URL di AttendanceConfig |
📚 Dependencies
// Sudah terinclude di build.gradle.kts:
- androidx.core:core-ktx
- androidx.lifecycle:lifecycle-runtime-ktx
- androidx.compose.* (UI framework)
- com.google.android.gms:play-services-location (GPS)
- Material 3 (Design system)
🔄 Next Steps / Future Features
Phase 2 (Future)
- Attendance history dengan Room Database
- User login screen dengan authentication
- Support multiple courses/classes
- Attendance statistics & reports
- Push notifications untuk deadline
- Offline mode dengan sync
Phase 3 (Advanced)
- Biometric verification (fingerprint)
- QR code verification
- Face recognition
- Real-time attendance dashboard
- Mobile app backend server
📞 Support & Contact
Untuk pertanyaan atau issues:
- Check logs di Android Studio Logcat
- Review error messages di app
- Test dengan webhook test terlebih dahulu
- Verify configurations di
AttendanceConfig.kt
Last Updated: January 14, 2026
Version: 1.0
Status: ✅ Production Ready