TKK_E32220671/lib/background_service.dart

436 lines
15 KiB
Dart

// lib/services/background_service.dart
import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:flutter/foundation.dart' show kIsWeb, kDebugMode;
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_background_service_android/flutter_background_service_android.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:workmanager/workmanager.dart';
/// Inisialisasi plugin notifikasi
final FlutterLocalNotificationsPlugin _notifPlugin =
FlutterLocalNotificationsPlugin();
/// ID channel notifikasi
const String _channelId = 'rain_notification_channel';
/// Nama channel notifikasi
const String _channelName = 'Rain Notifications';
/// Deskripsi channel notifikasi
const String _channelDesc = 'Notifications for rain sensor updates';
/// Channel Android
const _channelIdAndroid = 'monitoring_hujan_channel';
const _channelNameAndroid = 'Monitoring Hujan';
const _channelDescAndroid = 'Notifikasi status hujan';
Future<void> saveToFirestoreIfNotDuplicate({
required double suhu,
required double kelembaban,
required double cahaya,
required double rainValue,
required String statusHujan,
required String statusCahaya,
}) async {
final now = DateTime.now();
final thirtySecondsAgo = now.subtract(const Duration(seconds: 30));
final query = await FirebaseFirestore.instance
.collection('sensor_history')
.where('timestamp', isGreaterThan: Timestamp.fromDate(thirtySecondsAgo))
.get();
if (query.docs.isEmpty) {
await FirebaseFirestore.instance.collection('sensor_history').add({
'temperature': suhu,
'humidity': kelembaban,
'light': cahaya,
'rain': rainValue,
'rain_status': statusHujan,
'weather_status': statusCahaya,
'timestamp': FieldValue.serverTimestamp(),
});
print('✅ Data tersimpan ke Firestore (background)');
} else {
print('⏩ Data sudah ada, tidak disimpan ulang (anti duplikat, background)');
}
}
@pragma('vm:entry-point')
Future<void> initializeService() async {
if (kIsWeb) return;
// 1) Setup channel & plugin notifikasi
const androidSetup = AndroidInitializationSettings('@mipmap/ic_launcher');
final initSettings = InitializationSettings(android: androidSetup);
await _notifPlugin.initialize(initSettings);
// Hapus channel lama jika ada
await _notifPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.deleteNotificationChannel(_channelIdAndroid);
// Buat channel baru dengan deskripsi yang diperbarui
await _notifPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(
const AndroidNotificationChannel(
_channelIdAndroid,
_channelNameAndroid,
description: _channelDescAndroid,
importance: Importance.max,
enableVibration: true,
enableLights: true,
showBadge: true,
),
);
// 2) Konfigurasikan service
final service = FlutterBackgroundService();
await service.configure(
androidConfiguration: AndroidConfiguration(
onStart: onStart,
autoStart: true,
isForegroundMode: true,
notificationChannelId: _channelIdAndroid,
initialNotificationTitle: 'Monitoring Hujan',
initialNotificationContent: 'Memulai monitoring...',
foregroundServiceNotificationId: 888,
),
iosConfiguration: IosConfiguration(
autoStart: true,
onForeground: onStart,
onBackground: onIosBackground,
),
);
// Mulai service
await service.startService();
}
@pragma('vm:entry-point')
Future<bool> onIosBackground(ServiceInstance service) async {
return true;
}
@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
if (kIsWeb) return;
print('🚀 Background service dimulai...');
// Inisialisasi WorkManager
await Workmanager().initialize(callbackDispatcher);
// Inisialisasi SharedPreferences
final prefs = await SharedPreferences.getInstance();
int saveInterval = prefs.getInt('saveInterval') ?? 1; // menit
int remainingSeconds =
prefs.getInt('remainingSeconds') ?? (saveInterval * 60);
DateTime? lastSaveTime = prefs.getString('lastSaveTime') != null
? DateTime.parse(prefs.getString('lastSaveTime')!)
: null;
// --- Tambahkan variabel untuk menyimpan rain sebelumnya ---
double? previousRainValue;
// Inisialisasi Firebase jika belum ada
try {
if (Firebase.apps.isEmpty) {
await Firebase.initializeApp(
options: const FirebaseOptions(
apiKey: 'AIzaSyBoYp4GpkwF-aMPHVW1gs7PmOj5ucF4xZs',
appId: '1:289397695071:android:f6e034034faf366003b1ea',
messagingSenderId: '289397695071',
projectId: 'monitoring-hujan',
databaseURL: 'https://monitoring-hujan-default-rtdb.firebaseio.com',
),
);
print('✅ Firebase berhasil diinisialisasi di background service');
// Ambil interval dari Firestore
try {
final settingsDoc = await FirebaseFirestore.instance
.collection('settings')
.doc('save_interval')
.get();
if (settingsDoc.exists) {
saveInterval = settingsDoc.data()?['interval'] ?? 1;
await prefs.setInt('saveInterval', saveInterval);
print(
'✅ Interval berhasil diambil dari Firestore: $saveInterval menit');
} else {
// Buat dokumen settings jika belum ada
await FirebaseFirestore.instance
.collection('settings')
.doc('save_interval')
.set({
'interval': saveInterval,
'last_updated': FieldValue.serverTimestamp(),
});
print(
'✅ Dokumen settings dibuat dengan interval default: $saveInterval menit');
}
} catch (e) {
print('⚠️ Gagal mengambil interval dari Firestore: $e');
}
}
} catch (e) {
print('❌ Error inisialisasi Firebase: $e');
if (service is AndroidServiceInstance) {
await service.setForegroundNotificationInfo(
title: 'Monitoring Hujan',
content: 'Error inisialisasi aplikasi',
);
}
return;
}
// Hitung sisa waktu jika ada lastSaveTime
if (lastSaveTime != null) {
final now = DateTime.now();
final difference = now.difference(lastSaveTime).inSeconds;
remainingSeconds = (saveInterval * 60) - difference;
if (remainingSeconds < 0) remainingSeconds = 0;
}
// Pastikan ini tetap demi foreground service
if (service is AndroidServiceInstance) {
await service.setAsForegroundService();
print('✅ Service diatur sebagai foreground service');
}
// Update notifikasi awal
if (service is AndroidServiceInstance) {
final minutes = (remainingSeconds ~/ 60).toString().padLeft(2, '0');
final seconds = (remainingSeconds % 60).toString().padLeft(2, '0');
await service.setForegroundNotificationInfo(
title: 'Monitoring Hujan',
content: 'Mengambil data sensor... Next save: $minutes:$seconds',
);
print('📱 Notifikasi awal diperbarui');
}
// Timer untuk menyimpan data ke history
Timer? saveTimer;
// Fungsi untuk mencoba koneksi ulang
Future<void> retryConnection() async {
try {
final dbRef = FirebaseDatabase.instance.ref();
await dbRef.get();
print('✅ Koneksi Firebase berhasil');
return;
} catch (e) {
print('❌ Gagal koneksi: $e');
if (service is AndroidServiceInstance) {
await service.setForegroundNotificationInfo(
title: 'Monitoring Hujan',
content: 'Mencoba menghubungkan kembali...',
);
}
// Tunggu 5 detik sebelum mencoba lagi
await Future.delayed(const Duration(seconds: 5));
return retryConnection();
}
}
// Fungsi untuk setup listener
void setupListener(DatabaseReference dbRef) {
dbRef.onValue.listen(
(event) async {
try {
if (event.snapshot.value != null) {
final data = event.snapshot.value as Map<dynamic, dynamic>;
final suhu = double.parse(data['temperature']?.toString() ?? '0');
final kelembaban =
double.parse(data['humidity']?.toString() ?? '0');
final cahaya = double.parse(data['ldr']?.toString() ?? '0');
final rainValue = double.parse(data['rain']?.toString() ?? '0');
final statusHujan =
data['statusHujan']?.toString() ?? _getStatusHujan(rainValue);
final statusCahaya =
data['statusCahaya']?.toString() ?? _getStatusCuaca(cahaya);
print('📊 Data diperbarui:');
print(' - Suhu: $suhu°C');
print(' - Kelembaban: $kelembaban%');
print(' - Cahaya: $cahaya');
print(' - Rain value: $rainValue');
print(' - Status Hujan: $statusHujan');
print(' - Status Cahaya: $statusCahaya');
// Simpan ke Firestore setiap interval
if (remainingSeconds <= 0) {
await saveToFirestoreIfNotDuplicate(
suhu: suhu,
kelembaban: kelembaban,
cahaya: cahaya,
rainValue: rainValue,
statusHujan: statusHujan,
statusCahaya: statusCahaya,
);
}
// --- Update notifikasi hanya jika rain berubah ---
if (previousRainValue == null || previousRainValue != rainValue) {
previousRainValue = rainValue;
if (service is AndroidServiceInstance) {
final minutes =
(remainingSeconds ~/ 60).toString().padLeft(2, '0');
final seconds =
(remainingSeconds % 60).toString().padLeft(2, '0');
await service.setForegroundNotificationInfo(
title: 'Status Hujan: ${_getStatusHujan(rainValue)}',
content: 'Curah Hujan: ${rainValue.toStringAsFixed(0)}',
);
print('🔔 Notifikasi diperbarui karena rain berubah');
}
}
// --- END ---
}
} catch (e) {
print('❌ Error memproses data: $e');
if (service is AndroidServiceInstance) {
await service.setForegroundNotificationInfo(
title: 'Monitoring Hujan',
content: 'Error memproses data sensor',
);
}
}
},
onError: (error) {
print('❌ Error pada listener: $error');
if (service is AndroidServiceInstance) {
service.setForegroundNotificationInfo(
title: 'Monitoring Hujan',
content: 'Mencoba menghubungkan kembali...',
);
}
// Coba setup ulang listener setelah error
Future.delayed(const Duration(seconds: 5), () {
setupListener(FirebaseDatabase.instance.ref());
});
},
cancelOnError: false,
);
}
// Mulai proses koneksi
try {
print('🔄 Mencoba menghubungkan ke Firebase...');
await retryConnection();
// Setup listener setelah koneksi berhasil
final dbRef = FirebaseDatabase.instance.ref();
setupListener(dbRef);
// Setup timer untuk countdown
saveTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
if (remainingSeconds > 0) {
remainingSeconds--;
await prefs.setInt('remainingSeconds', remainingSeconds);
}
});
} catch (e) {
print('❌ Error fatal: $e');
if (service is AndroidServiceInstance) {
await service.setForegroundNotificationInfo(
title: 'Monitoring Hujan',
content: 'Error koneksi ke server',
);
}
// Coba koneksi ulang setelah error fatal
Future.delayed(const Duration(seconds: 5), () {
onStart(service);
});
}
// Cleanup saat service dihentikan
service.on('stopService').listen((event) {
saveTimer?.cancel();
print('🛑 Service dihentikan');
});
}
/// Helper menentukan status hujan
String _getStatusHujan(double rainValue) {
// Status hujan berdasarkan nilai sensor
return (rainValue < 500) ? "Hujan" : "Tidak Hujan";
}
/// Helper menentukan status cuaca
String _getStatusCuaca(double cahaya) {
// Status cahaya berdasarkan nilai LDR
return (cahaya < 700) ? "Terang" : "Gelap";
}
Future<void> _updateNotification(
String title, String content, String status) async {
// Implementasi untuk mengupdate notifikasi
}
@pragma('vm:entry-point')
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
print('🔄 Background task dijalankan: $task');
if (task == 'saveData') {
try {
// Inisialisasi Firebase jika belum ada
if (Firebase.apps.isEmpty) {
await Firebase.initializeApp(
options: const FirebaseOptions(
apiKey: 'AIzaSyBoYp4GpkwF-aMPHVW1gs7PmOj5ucF4xZs',
appId: '1:289397695071:android:f6e034034faf366003b1ea',
messagingSenderId: '289397695071',
projectId: 'monitoring-hujan',
databaseURL:
'https://monitoring-hujan-default-rtdb.firebaseio.com',
),
);
}
// Ambil data dari Firebase
final dbRef = FirebaseDatabase.instance.ref();
final snapshot = await dbRef.get();
if (snapshot.value != null) {
final data = snapshot.value as Map<dynamic, dynamic>;
final suhu = double.parse(data['temperature']?.toString() ?? '0');
final kelembaban = double.parse(data['humidity']?.toString() ?? '0');
final cahaya = double.parse(data['ldr']?.toString() ?? '0');
final rainValue = double.parse(data['rain']?.toString() ?? '0');
final statusHujan =
data['statusHujan']?.toString() ?? _getStatusHujan(rainValue);
final statusCahaya =
data['statusCahaya']?.toString() ?? _getStatusCuaca(cahaya);
// Simpan ke Firestore dengan anti-duplikat
await saveToFirestoreIfNotDuplicate(
suhu: suhu,
kelembaban: kelembaban,
cahaya: cahaya,
rainValue: rainValue,
statusHujan: statusHujan,
statusCahaya: statusCahaya,
);
print('✅ Data berhasil disimpan dari background task');
}
} catch (e) {
print('❌ Error dalam background task: $e');
}
}
return Future.value(true);
});
}