================================================================================ TUTORIAL INTEGRASI SUI API DENGAN FLUTTER ================================================================================ Dibuat: 27 Mei 2026 API URL: https://sui.akfo.cc/api Owner: AKFO ================================================================================ I. STRUKTUR DATABASE ================================================================================ Database: akfomyid_sui @ ftp.akfo.cc:3306 Tabel yang digunakan: 1. sui_notifications - Menyimpan notifikasi push dari aplikasi 2. sui_logs - Menyimpan log aktivitas pengguna Struktur sui_notifications: - sui_id (INT) - ID pengguna (Primary Key) - title (TEXT) - Judul notifikasi - text (TEXT) - Isi notifikasi - package (TEXT) - Package name aplikasi - post_time (BIGINT) - Waktu dalam milliseconds - notification_key (TEXT) - Unique key untuk notifikasi Struktur sui_logs: - sui_id (INT) - ID pengguna (Primary Key) - message (TEXT) - Pesan log - timestamp (BIGINT) - Waktu dalam milliseconds ================================================================================ II. API ENDPOINTS ================================================================================ BASE URL: https://sui.akfo.cc/api A. NOTIFICATIONS ENDPOINTS ------------------------ 1. GET semua notifikasi GET /notifications Query Parameters: - limit (default: 100) - offset (default: 0) Response: { "status": "success", "code": 200, "data": { "data": [ { "sui_id": 1, "title": "Welcome", "text": "Hello user", "package": "com.example.app", "post_time": 1622000000000, "notification_key": "abc123" } ] } } 2. GET notifikasi berdasarkan sui_id GET /notifications?sui_id=1 3. GET notifikasi spesifik berdasarkan ID GET /notifications/1 4. CREATE notifikasi baru POST /notifications Body (JSON): { "sui_id": 1, "title": "Test Notification", "text": "This is a test notification", "package": "com.example.app", "post_time": 1622000000000, "notification_key": "unique_key_123" } Response (201): { "status": "success", "code": 201, "data": { "id": 1, "message": "Notification created" } } 5. UPDATE notifikasi PUT /notifications/1 Body (JSON): { "title": "Updated Title", "text": "Updated text" } 6. DELETE notifikasi DELETE /notifications/1 B. LOGS ENDPOINTS ---------------- 1. GET semua logs GET /logs Query Parameters: - limit (default: 100) - offset (default: 0) 2. GET logs berdasarkan sui_id GET /logs?sui_id=1 3. GET log spesifik berdasarkan ID GET /logs/1 4. CREATE log baru POST /logs Body (JSON): { "sui_id": 1, "message": "User logged in", "timestamp": 1622000000000 } 5. UPDATE log PUT /logs/1 Body (JSON): { "message": "Updated message" } 6. DELETE log DELETE /logs/1 ================================================================================ III. IMPLEMENTASI DI FLUTTER (NATIVE CODE) ================================================================================ A. MENAMBAHKAN DEPENDENCIES --------------------------- Di file pubspec.yaml, tambahkan: dependencies: flutter: sdk: flutter http: ^1.1.0 # Untuk HTTP requests shared_preferences: ^2.1.0 # Untuk menyimpan sui_id lokal intl: ^0.19.0 # Untuk formatting tanggal Jalankan: $ flutter pub get B. MEMBUAT HTTP SERVICE CLASS ------------------------------ File: lib/services/api_service.dart ```dart import 'package:http/http.dart' as http; import 'dart:convert'; class ApiService { static const String baseUrl = 'https://sui.akfo.cc/api'; static const String notificationsEndpoint = '/notifications'; static const String logsEndpoint = '/logs'; // Headers untuk semua request static Map get headers => { 'Content-Type': 'application/json', 'Accept': 'application/json', }; // ==================== NOTIFICATIONS ==================== /// GET semua notifikasi dengan pagination static Future> getNotifications({ int limit = 100, int offset = 0, }) async { try { final url = Uri.parse( '$baseUrl$notificationsEndpoint?limit=$limit&offset=$offset' ); final response = await http.get(url, headers: headers) .timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } /// GET notifikasi berdasarkan sui_id static Future> getNotificationsBySuiId(int suiId) async { try { final url = Uri.parse('$baseUrl$notificationsEndpoint?sui_id=$suiId'); final response = await http.get(url, headers: headers) .timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } /// GET notifikasi spesifik static Future> getNotificationById(int id) async { try { final url = Uri.parse('$baseUrl$notificationsEndpoint/$id'); final response = await http.get(url, headers: headers) .timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } /// POST notifikasi baru static Future> createNotification({ required int suiId, required String title, required String text, String package = 'com.example.app', int? postTime, String? notificationKey, }) async { try { final url = Uri.parse('$baseUrl$notificationsEndpoint'); final body = jsonEncode({ 'sui_id': suiId, 'title': title, 'text': text, 'package': package, 'post_time': postTime ?? DateTime.now().millisecondsSinceEpoch, 'notification_key': notificationKey ?? DateTime.now().toString(), }); final response = await http.post(url, headers: headers, body: body) .timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } /// PUT update notifikasi static Future> updateNotification( int id, Map data, ) async { try { final url = Uri.parse('$baseUrl$notificationsEndpoint/$id'); final response = await http.put( url, headers: headers, body: jsonEncode(data), ).timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } /// DELETE notifikasi static Future> deleteNotification(int id) async { try { final url = Uri.parse('$baseUrl$notificationsEndpoint/$id'); final response = await http.delete(url, headers: headers) .timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } // ==================== LOGS ==================== /// GET semua logs static Future> getLogs({ int limit = 100, int offset = 0, }) async { try { final url = Uri.parse( '$baseUrl$logsEndpoint?limit=$limit&offset=$offset' ); final response = await http.get(url, headers: headers) .timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } /// GET logs berdasarkan sui_id static Future> getLogsBySuiId(int suiId) async { try { final url = Uri.parse('$baseUrl$logsEndpoint?sui_id=$suiId'); final response = await http.get(url, headers: headers) .timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } /// POST log baru static Future> createLog({ required int suiId, required String message, int? timestamp, }) async { try { final url = Uri.parse('$baseUrl$logsEndpoint'); final body = jsonEncode({ 'sui_id': suiId, 'message': message, 'timestamp': timestamp ?? DateTime.now().millisecondsSinceEpoch, }); final response = await http.post(url, headers: headers, body: body) .timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } /// PUT update log static Future> updateLog( int id, Map data, ) async { try { final url = Uri.parse('$baseUrl$logsEndpoint/$id'); final response = await http.put( url, headers: headers, body: jsonEncode(data), ).timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } /// DELETE log static Future> deleteLog(int id) async { try { final url = Uri.parse('$baseUrl$logsEndpoint/$id'); final response = await http.delete(url, headers: headers) .timeout(const Duration(seconds: 10)); return _handleResponse(response); } catch (e) { return {'status': 'error', 'message': 'Connection error: $e'}; } } // Helper untuk handle HTTP response static Map _handleResponse(http.Response response) { if (response.statusCode >= 200 && response.statusCode < 300) { return jsonDecode(response.body); } else { return { 'status': 'error', 'code': response.statusCode, 'message': 'Server error: ${response.statusCode}', 'body': response.body, }; } } } ``` C. USER SERVICE UNTUK MANAGE sui_id ----------------------------------- File: lib/services/user_service.dart ```dart import 'package:shared_preferences/shared_preferences.dart'; class UserService { static const String _suiIdKey = 'sui_id'; /// Simpan sui_id lokal (setelah login) static Future setSuiId(int suiId) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_suiIdKey, suiId); } /// Dapatkan sui_id dari local storage static Future getSuiId() async { final prefs = await SharedPreferences.getInstance(); return prefs.getInt(_suiIdKey); } /// Hapus sui_id (logout) static Future clearSuiId() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_suiIdKey); } /// Check apakah user sudah login static Future isLoggedIn() async { final suiId = await getSuiId(); return suiId != null; } } ``` D. CONTOH PENGGUNAAN DI WIDGET ----------------------------- File: lib/screens/notifications_screen.dart ```dart import 'package:flutter/material.dart'; import '/services/api_service.dart'; import '/services/user_service.dart'; class NotificationsScreen extends StatefulWidget { @override _NotificationsScreenState createState() => _NotificationsScreenState(); } class _NotificationsScreenState extends State { late Future> _notificationsFuture; int? _suiId; @override void initState() { super.initState(); _loadNotifications(); } void _loadNotifications() async { // Dapatkan sui_id dari local storage _suiId = await UserService.getSuiId(); setState(() { if (_suiId != null) { // Load notifikasi berdasarkan sui_id _notificationsFuture = ApiService.getNotificationsBySuiId(_suiId!); } else { // Load semua notifikasi _notificationsFuture = ApiService.getNotifications(); } }); } void _sendNotification() async { if (_suiId == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('User not logged in')), ); return; } final result = await ApiService.createNotification( suiId: _suiId!, title: 'Test Notification', text: 'This is a test from Flutter app', package: 'com.example.flutterapp', ); if (result['status'] == 'success') { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Notification sent successfully')), ); _loadNotifications(); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to send notification')), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Notifications')), body: FutureBuilder>( future: _notificationsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } if (!snapshot.hasData || snapshot.data!['status'] != 'success') { return Center(child: Text('Failed to load notifications')); } final data = snapshot.data!['data']['data'] as List; if (data.isEmpty) { return Center(child: Text('No notifications')); } return ListView.builder( itemCount: data.length, itemBuilder: (context, index) { final notification = data[index]; return Card( margin: EdgeInsets.all(8), child: ListTile( title: Text(notification['title'] ?? 'No title'), subtitle: Text(notification['text'] ?? 'No text'), trailing: Text( DateTime.fromMillisecondsSinceEpoch( notification['post_time'] ?? 0 ).toString(), ), ), ); }, ); }, ), floatingActionButton: FloatingActionButton( onPressed: _sendNotification, child: Icon(Icons.add), ), ); } } ``` File: lib/screens/logs_screen.dart ```dart import 'package:flutter/material.dart'; import '/services/api_service.dart'; import '/services/user_service.dart'; class LogsScreen extends StatefulWidget { @override _LogsScreenState createState() => _LogsScreenState(); } class _LogsScreenState extends State { late Future> _logsFuture; int? _suiId; @override void initState() { super.initState(); _loadLogs(); } void _loadLogs() async { _suiId = await UserService.getSuiId(); setState(() { if (_suiId != null) { _logsFuture = ApiService.getLogsBySuiId(_suiId!); } else { _logsFuture = ApiService.getLogs(); } }); } void _addLog(String message) async { if (_suiId == null) return; final result = await ApiService.createLog( suiId: _suiId!, message: message, ); if (result['status'] == 'success') { _loadLogs(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Logs')), body: FutureBuilder>( future: _logsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: CircularProgressIndicator()); } if (!snapshot.hasData || snapshot.data!['status'] != 'success') { return Center(child: Text('Failed to load logs')); } final data = snapshot.data!['data']['data'] as List; if (data.isEmpty) { return Center(child: Text('No logs')); } return ListView.builder( itemCount: data.length, itemBuilder: (context, index) { final log = data[index]; return Card( margin: EdgeInsets.all(8), child: ListTile( title: Text(log['message'] ?? 'No message'), subtitle: Text( DateTime.fromMillisecondsSinceEpoch( log['timestamp'] ?? 0 ).toString(), ), ), ); }, ); }, ), ); } } ``` E. UPDATE main.dart DENGAN NAVIGATION ------------------------------------- ```dart import 'package:flutter/material.dart'; import '/screens/notifications_screen.dart'; import '/screens/logs_screen.dart'; import '/services/user_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'SUI API App', theme: ThemeData(primarySwatch: Colors.blue), home: MainScreen(), ); } } class MainScreen extends StatefulWidget { @override _MainScreenState createState() => _MainScreenState(); } class _MainScreenState extends State { int _selectedIndex = 0; int? _suiId; @override void initState() { super.initState(); _loadSuiId(); } void _loadSuiId() async { final suiId = await UserService.getSuiId(); setState(() => _suiId = suiId); } void _handleLogin() async { // Simulasi login - ganti dengan proses login sebenarnya final TextEditingController controller = TextEditingController(); showDialog( context: context, builder: (context) => AlertDialog( title: Text('Enter Your ID'), content: TextField( controller: controller, keyboardType: TextInputType.number, decoration: InputDecoration(labelText: 'SUI ID'), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Cancel'), ), TextButton( onPressed: () async { final id = int.tryParse(controller.text); if (id != null) { await UserService.setSuiId(id); setState(() => _suiId = id); Navigator.pop(context); } }, child: Text('Login'), ), ], ), ); } void _handleLogout() async { await UserService.clearSuiId(); setState(() => _suiId = null); } @override Widget build(BuildContext context) { final screens = [ NotificationsScreen(), LogsScreen(), ]; return Scaffold( appBar: AppBar( title: Text('SUI API'), actions: [ if (_suiId != null) Padding( padding: EdgeInsets.all(16), child: Center(child: Text('ID: $_suiId')), ), IconButton( icon: Icon(_suiId != null ? Icons.logout : Icons.login), onPressed: _suiId != null ? _handleLogout : _handleLogin, ), ], ), body: screens[_selectedIndex], bottomNavigationBar: BottomNavigationBar( currentIndex: _selectedIndex, onTap: (index) => setState(() => _selectedIndex = index), items: [ BottomNavigationBarItem( icon: Icon(Icons.notifications), label: 'Notifications', ), BottomNavigationBarItem( icon: Icon(Icons.history), label: 'Logs', ), ], ), ); } } ``` ================================================================================ IV. CARA TESTING ================================================================================ 1. Unit Testing API Service: File: test/services/api_service_test.dart ```dart import 'package:flutter_test/flutter_test.dart'; import 'package:myapp/services/api_service.dart'; void main() { group('ApiService Tests', () { test('Create notification', () async { final result = await ApiService.createNotification( suiId: 1, title: 'Test', text: 'Test notification', ); expect(result['status'], 'success'); expect(result['code'], 201); }); test('Get notifications', () async { final result = await ApiService.getNotifications(); expect(result['status'], 'success'); expect(result['code'], 200); }); test('Create log', () async { final result = await ApiService.createLog( suiId: 1, message: 'Test log message', ); expect(result['status'], 'success'); expect(result['code'], 201); }); }); } ``` 2. Manual Testing dengan Postman/cURL: CREATE Notification: $ curl -X POST https://sui.akfo.cc/api/notifications \ -H "Content-Type: application/json" \ -d '{ "sui_id": 1, "title": "Test", "text": "Test message", "package": "com.example.app" }' GET Notifications: $ curl https://sui.akfo.cc/api/notifications?sui_id=1 GET Logs: $ curl https://sui.akfo.cc/api/logs?sui_id=1 CREATE Log: $ curl -X POST https://sui.akfo.cc/api/logs \ -H "Content-Type: application/json" \ -d '{ "sui_id": 1, "message": "User action log" }' ================================================================================ V. NOTES & BEST PRACTICES ================================================================================ 1. AUTHENTICATION: - Saat ini API tidak memiliki authentication - Untuk production, tambahkan API key atau JWT token - Simpan API credentials di environment variables 2. ERROR HANDLING: - Selalu handle connection errors - Gunakan try-catch block - Tampilkan user-friendly error messages 3. DATA STORAGE: - Gunakan SharedPreferences untuk sui_id - Cache data lokal untuk offline capability - Implement local database (SQLite/Hive) untuk data besar 4. PERFORMANCE: - Implement pagination (limit & offset) - Cache API responses - Implement lazy loading untuk lists 5. SECURITY: - Validate input data sebelum send ke server - Gunakan HTTPS (sudah configured di API) - Jangan simpan sensitive data di local storage ================================================================================ VI. TROUBLESHOOTING ================================================================================ 1. Certificate Error: - Pastikan HTTPS certificate valid - Add certificate pinning untuk production 2. Connection Timeout: - Check internet connection - Increase timeout duration jika diperlukan - Check API server status 3. Invalid Response: - Validate JSON response structure - Check API endpoint documentation - Log response body untuk debugging 4. 401 Unauthorized / 403 Forbidden: - Check credentials/API key - Verify user permissions - Check API access control ================================================================================ Untuk support atau pertanyaan: developer@akfo.cc Version: 1.0 Last Updated: 27 Mei 2026