dashboard terhubung firebase

This commit is contained in:
Wizznu 2026-01-12 17:53:26 +07:00
parent 95949930ad
commit 2586f744a9
6 changed files with 393 additions and 36 deletions

View File

@ -50,6 +50,7 @@ class DefaultFirebaseOptions {
messagingSenderId: '217854138058', messagingSenderId: '217854138058',
projectId: 'project-ta-951b4', projectId: 'project-ta-951b4',
storageBucket: 'project-ta-951b4.firebasestorage.app', storageBucket: 'project-ta-951b4.firebasestorage.app',
databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com',
); );
// Pilih project Anda > Project Settings > Your apps > Android app // Pilih project Anda > Project Settings > Your apps > Android app
@ -60,6 +61,7 @@ class DefaultFirebaseOptions {
messagingSenderId: '217854138058', messagingSenderId: '217854138058',
projectId: 'project-ta-951b4', projectId: 'project-ta-951b4',
storageBucket: 'project-ta-951b4.firebasestorage.app', storageBucket: 'project-ta-951b4.firebasestorage.app',
databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com',
iosBundleId: 'com.kampus.apsgo', iosBundleId: 'com.kampus.apsgo',
); );
@ -69,6 +71,7 @@ class DefaultFirebaseOptions {
messagingSenderId: '217854138058', messagingSenderId: '217854138058',
projectId: 'project-ta-951b4', projectId: 'project-ta-951b4',
authDomain: 'project-ta-951b4.firebaseapp.com', authDomain: 'project-ta-951b4.firebaseapp.com',
databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com',
storageBucket: 'project-ta-951b4.firebasestorage.app', storageBucket: 'project-ta-951b4.firebasestorage.app',
measurementId: 'G-6ML8QQEGNZ', measurementId: 'G-6ML8QQEGNZ',
); );
@ -79,6 +82,7 @@ class DefaultFirebaseOptions {
messagingSenderId: '217854138058', messagingSenderId: '217854138058',
projectId: 'project-ta-951b4', projectId: 'project-ta-951b4',
storageBucket: 'project-ta-951b4.firebasestorage.app', storageBucket: 'project-ta-951b4.firebasestorage.app',
databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com',
iosBundleId: 'com.example.projectTa', iosBundleId: 'com.example.projectTa',
); );
@ -87,6 +91,7 @@ class DefaultFirebaseOptions {
appId: '1:217854138058:web:233d93e01272a0870c4633', appId: '1:217854138058:web:233d93e01272a0870c4633',
messagingSenderId: '217854138058', messagingSenderId: '217854138058',
projectId: 'project-ta-951b4', projectId: 'project-ta-951b4',
databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com',
authDomain: 'project-ta-951b4.firebaseapp.com', authDomain: 'project-ta-951b4.firebaseapp.com',
storageBucket: 'project-ta-951b4.firebasestorage.app', storageBucket: 'project-ta-951b4.firebasestorage.app',
measurementId: 'G-SB8J6ZZQHZ', measurementId: 'G-SB8J6ZZQHZ',

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../theme/app_color.dart'; import '../theme/app_color.dart';
import '../services/auth_service.dart'; import '../services/auth_service.dart';
import '../services/firebase_database_service.dart';
import 'kontrol_page.dart'; import 'kontrol_page.dart';
import 'histori_page.dart'; import 'histori_page.dart';
@ -13,6 +14,7 @@ class DashboardPage extends StatefulWidget {
class _DashboardPageState extends State<DashboardPage> { class _DashboardPageState extends State<DashboardPage> {
int _selectedIndex = 0; int _selectedIndex = 0;
final _dbService = FirebaseDatabaseService();
final List<Map<String, dynamic>> _pages = [ final List<Map<String, dynamic>> _pages = [
{'title': 'Dashboard', 'icon': Icons.dashboard}, {'title': 'Dashboard', 'icon': Icons.dashboard},
@ -248,47 +250,152 @@ class _DashboardPageState extends State<DashboardPage> {
} }
Widget _buildDashboard() { Widget _buildDashboard() {
return SingleChildScrollView( return StreamBuilder<Map<String, dynamic>>(
padding: const EdgeInsets.all(16), stream: _dbService.getSensorDataStream(),
child: Column( builder: (context, snapshot) {
crossAxisAlignment: CrossAxisAlignment.start, // Loading state
children: [ if (snapshot.connectionState == ConnectionState.waiting) {
// Sensor Cards - Temperature, Humidity, Light return const Center(child: CircularProgressIndicator());
_buildSensorCard( }
title: "Temperature",
value: "30",
icon: Icons.thermostat_outlined,
color: Colors.orange,
),
_buildSensorCard(
title: "Humidity",
value: "70",
icon: Icons.water_drop_outlined,
color: Colors.blue,
),
_buildSensorCard(
title: "Light",
value: "85",
icon: Icons.wb_sunny_outlined,
color: Colors.amber,
),
const SizedBox(height: 12), // Error state
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text('Error: ${snapshot.error}'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => setState(() {}),
child: const Text('Retry'),
),
],
),
);
}
// Pot Cards - Pot 1 to Pot 5 // No data state
_buildPotCard(potNumber: 1, soilMoisture: "45"), if (!snapshot.hasData || snapshot.data!.isEmpty) {
_buildPotCard(potNumber: 2, soilMoisture: "52"), return const Center(
_buildPotCard(potNumber: 3, soilMoisture: "38"), child: Column(
_buildPotCard(potNumber: 4, soilMoisture: "61"), mainAxisAlignment: MainAxisAlignment.center,
_buildPotCard(potNumber: 5, soilMoisture: "48"), children: [
], Icon(Icons.cloud_off, size: 48, color: Colors.grey),
), SizedBox(height: 16),
Text('No data available'),
],
),
);
}
final data = snapshot.data!;
final suhu = data['suhu'] ?? '0';
final kelembapan = data['kelembapan'] ?? '0';
final ldr = data['ldr'] ?? '0';
final soil1 = data['soil_1'] ?? '0';
final soil2 = data['soil_2'] ?? '0';
final soil3 = data['soil_3'] ?? '0';
final soil4 = data['soil_4'] ?? '0';
final soil5 = data['soil_5'] ?? '0';
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Connection Status Indicator
_buildConnectionStatus(),
const SizedBox(height: 8),
// Sensor Cards - Temperature, Humidity, Light
_buildSensorCard(
title: "Temperature",
value: suhu,
unit: "°C",
icon: Icons.thermostat_outlined,
color: Colors.orange,
),
_buildSensorCard(
title: "Humidity",
value: kelembapan,
unit: "%",
icon: Icons.water_drop_outlined,
color: Colors.blue,
),
_buildSensorCard(
title: "Light",
value: ldr,
unit: "%",
icon: Icons.wb_sunny_outlined,
color: Colors.amber,
),
const SizedBox(height: 12),
// Pot Cards - Pot 1 to Pot 5
_buildPotCard(potNumber: 1, soilMoisture: soil1),
_buildPotCard(potNumber: 2, soilMoisture: soil2),
_buildPotCard(potNumber: 3, soilMoisture: soil3),
_buildPotCard(potNumber: 4, soilMoisture: soil4),
_buildPotCard(potNumber: 5, soilMoisture: soil5),
],
),
);
},
);
}
Widget _buildConnectionStatus() {
return StreamBuilder<bool>(
stream: _dbService.getConnectionStatus(),
builder: (context, snapshot) {
final isConnected = snapshot.data ?? false;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: isConnected
? Colors.green.withOpacity(0.1)
: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isConnected ? Colors.green : Colors.red,
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: isConnected ? Colors.green : Colors.red,
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Text(
isConnected ? 'Connected' : 'Disconnected',
style: TextStyle(
color: isConnected ? Colors.green : Colors.red,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
),
);
},
); );
} }
Widget _buildSensorCard({ Widget _buildSensorCard({
required String title, required String title,
required String value, required String value,
String unit = '',
required IconData icon, required IconData icon,
required Color color, required Color color,
}) { }) {
@ -325,9 +432,28 @@ class _DashboardPageState extends State<DashboardPage> {
), ),
), ),
), ),
Text( Row(
value, crossAxisAlignment: CrossAxisAlignment.end,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), children: [
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
if (unit.isNotEmpty)
Padding(
padding: const EdgeInsets.only(left: 4, bottom: 2),
child: Text(
unit,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
),
],
), ),
], ],
), ),

View File

@ -0,0 +1,199 @@
import 'package:firebase_database/firebase_database.dart';
class FirebaseDatabaseService {
static final FirebaseDatabaseService _instance =
FirebaseDatabaseService._internal();
factory FirebaseDatabaseService() => _instance;
FirebaseDatabaseService._internal();
final DatabaseReference _database = FirebaseDatabase.instance.ref();
// ==================== SENSOR DATA ====================
/// Get real-time stream of sensor data
Stream<Map<String, dynamic>> getSensorDataStream() {
return _database.child('data').onValue.map((event) {
if (event.snapshot.value != null) {
final data = Map<String, dynamic>.from(
event.snapshot.value as Map,
);
return {
'suhu': _parseValue(data['suhu']),
'kelembapan': _parseValue(data['kelembapan']),
'ldr': _parseValue(data['ldr']),
'soil_1': _parseValue(data['soil_1']),
'soil_2': _parseValue(data['soil_2']),
'soil_3': _parseValue(data['soil_3']),
'soil_4': _parseValue(data['soil_4']),
'soil_5': _parseValue(data['soil_5']),
};
}
return {};
});
}
/// Get sensor data once
Future<Map<String, dynamic>> getSensorData() async {
try {
final snapshot = await _database.child('data').get();
if (snapshot.exists) {
final data = Map<String, dynamic>.from(snapshot.value as Map);
return {
'suhu': _parseValue(data['suhu']),
'kelembapan': _parseValue(data['kelembapan']),
'ldr': _parseValue(data['ldr']),
'soil_1': _parseValue(data['soil_1']),
'soil_2': _parseValue(data['soil_2']),
'soil_3': _parseValue(data['soil_3']),
'soil_4': _parseValue(data['soil_4']),
'soil_5': _parseValue(data['soil_5']),
};
}
} catch (e) {
print('Error getting sensor data: $e');
}
return {};
}
// ==================== AKTUATOR CONTROL ====================
/// Get real-time stream of aktuator status
Stream<Map<String, bool>> getAktuatorStream() {
return _database.child('aktuator').onValue.map((event) {
if (event.snapshot.value != null) {
final data = Map<String, dynamic>.from(
event.snapshot.value as Map,
);
return {
'mosvet_1': data['mosvet_1'] ?? false, // Pompa Air
'mosvet_2': data['mosvet_2'] ?? false, // Pompa Pupuk
'mosvet_3': data['mosvet_3'] ?? false, // Valve 1
'mosvet_4': data['mosvet_4'] ?? false, // Valve 2
'mosvet_5': data['mosvet_5'] ?? false, // Valve 3
'mosvet_6': data['mosvet_6'] ?? false, // Valve 4
'mosvet_7': data['mosvet_7'] ?? false, // Valve 5
};
}
return {};
});
}
/// Set aktuator status (untuk kontrol manual)
Future<void> setAktuator(String mosfetName, bool value) async {
try {
await _database.child('aktuator/$mosfetName').set(value);
} catch (e) {
print('Error setting aktuator: $e');
rethrow;
}
}
// ==================== KONTROL CONFIG ====================
/// Get real-time stream of kontrol configuration
Stream<Map<String, dynamic>> getKontrolStream() {
return _database.child('kontrol').onValue.map((event) {
if (event.snapshot.value != null) {
return Map<String, dynamic>.from(event.snapshot.value as Map);
}
return {};
});
}
/// Get kontrol configuration once
Future<Map<String, dynamic>> getKontrolConfig() async {
try {
final snapshot = await _database.child('kontrol').get();
if (snapshot.exists) {
return Map<String, dynamic>.from(snapshot.value as Map);
}
} catch (e) {
print('Error getting kontrol config: $e');
}
return {};
}
/// Update kontrol configuration
Future<void> updateKontrolConfig(Map<String, dynamic> config) async {
try {
await _database.child('kontrol').update(config);
} catch (e) {
print('Error updating kontrol config: $e');
rethrow;
}
}
/// Set batas threshold (batas_atas, batas_bawah)
Future<void> setThreshold({int? batasAtas, int? batasBawah}) async {
try {
final updates = <String, dynamic>{};
if (batasAtas != null) updates['batas_atas'] = batasAtas;
if (batasBawah != null) updates['batas_bawah'] = batasBawah;
await _database.child('kontrol').update(updates);
} catch (e) {
print('Error setting threshold: $e');
rethrow;
}
}
/// Set waktu penyiraman
Future<void> setWaktuPenyiraman({
String? waktu1,
String? waktu2,
bool? waktuEnabled,
}) async {
try {
final updates = <String, dynamic>{};
if (waktu1 != null) updates['waktu_1'] = waktu1;
if (waktu2 != null) updates['waktu_2'] = waktu2;
if (waktuEnabled != null) updates['waktu'] = waktuEnabled;
await _database.child('kontrol').update(updates);
} catch (e) {
print('Error setting waktu: $e');
rethrow;
}
}
/// Set durasi penyiraman
Future<void> setDurasi({int? durasi1, int? durasi2}) async {
try {
final updates = <String, dynamic>{};
if (durasi1 != null) updates['durasi_1'] = durasi1;
if (durasi2 != null) updates['durasi_2'] = durasi2;
await _database.child('kontrol').update(updates);
} catch (e) {
print('Error setting durasi: $e');
rethrow;
}
}
/// Toggle mode otomatis
Future<void> setOtomatis(bool value) async {
try {
await _database.child('kontrol/otomatis').set(value);
} catch (e) {
print('Error setting otomatis: $e');
rethrow;
}
}
// ==================== HELPER FUNCTIONS ====================
/// Parse value dari Firebase (handle empty string)
String _parseValue(dynamic value) {
if (value == null || value == '') {
return '0';
}
return value.toString();
}
/// Get database reference (untuk advanced usage)
DatabaseReference get databaseRef => _database;
/// Check connection status
Stream<bool> getConnectionStatus() {
return _database.child('.info/connected').onValue.map((event) {
return event.snapshot.value as bool? ?? false;
});
}
}

View File

@ -7,12 +7,14 @@ import Foundation
import firebase_auth import firebase_auth
import firebase_core import firebase_core
import firebase_database
import google_sign_in_ios import google_sign_in_ios
import shared_preferences_foundation import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin"))
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
} }

View File

@ -137,6 +137,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.24.1" version: "2.24.1"
firebase_database:
dependency: "direct main"
description:
name: firebase_database
sha256: "35b37c04307b99c5f746387ce03292531c3aa1de91facffbd9cff5e069a8b5fd"
url: "https://pub.dev"
source: hosted
version: "11.3.10"
firebase_database_platform_interface:
dependency: transitive
description:
name: firebase_database_platform_interface
sha256: "095342e96d94b486b8273afc6327f777d53b63a169bd4201e5153ee3b8210c11"
url: "https://pub.dev"
source: hosted
version: "0.2.6+10"
firebase_database_web:
dependency: transitive
description:
name: firebase_database_web
sha256: "05f9b871d97b3ca879937947d0728ea95294395e7ddd5685583e8662be99eb16"
url: "https://pub.dev"
source: hosted
version: "0.2.6+16"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter

View File

@ -37,6 +37,7 @@ dependencies:
rename: ^3.1.0 rename: ^3.1.0
firebase_core: ^3.8.1 firebase_core: ^3.8.1
firebase_auth: ^5.3.4 firebase_auth: ^5.3.4
firebase_database: ^11.1.8
google_sign_in: ^6.2.1 google_sign_in: ^6.2.1
shared_preferences: ^2.2.2 shared_preferences: ^2.2.2