================================================================================
                    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<String, String> get headers => {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  };

  // ==================== NOTIFICATIONS ====================
  
  /// GET semua notifikasi dengan pagination
  static Future<Map<String, dynamic>> 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<Map<String, dynamic>> 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<Map<String, dynamic>> 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<Map<String, dynamic>> 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<Map<String, dynamic>> updateNotification(
    int id,
    Map<String, dynamic> 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<Map<String, dynamic>> 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<Map<String, dynamic>> 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<Map<String, dynamic>> 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<Map<String, dynamic>> 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<Map<String, dynamic>> updateLog(
    int id,
    Map<String, dynamic> 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<Map<String, dynamic>> 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<String, dynamic> _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<void> setSuiId(int suiId) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setInt(_suiIdKey, suiId);
  }
  
  /// Dapatkan sui_id dari local storage
  static Future<int?> getSuiId() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getInt(_suiIdKey);
  }
  
  /// Hapus sui_id (logout)
  static Future<void> clearSuiId() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove(_suiIdKey);
  }
  
  /// Check apakah user sudah login
  static Future<bool> 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<NotificationsScreen> {
  late Future<Map<String, dynamic>> _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<Map<String, dynamic>>(
        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<LogsScreen> {
  late Future<Map<String, dynamic>> _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<Map<String, dynamic>>(
        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<MainScreen> {
  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
