Integrasi Mobile•Flutter SDK

Implementasi Native Flutter

Panduan integrasi lengkap untuk aplikasi **Flutter** menggunakan package resmi webview_flutter versi 4+.

1. Tambahkan Dependencies (pubspec.yaml)

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  webview_flutter: ^4.10.0
  permission_handler: ^11.3.1
  geolocator: ^13.0.1

2. Screen Widget Lengkap (qoin_webview_screen.dart)

Kode berikut menangani inisialisasi WebView, pemasangan JavaScript Channel, izin lokasi, injeksi Signature & Palette, serta penutupan layar saat transaksi selesai.

lib/screens/qoin_webview_screen.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:permission_handler/permission_handler.dart';

class QoinWebviewScreen extends StatefulWidget {
  final String baseUrl;
  final String signature;
  final String palette;

  const QoinWebviewScreen({
    Key? key,
    required this.baseUrl,
    required this.signature,
    this.palette = 'original',
  }) : super(key: key);

  @override
  State<QoinWebviewScreen> createState() => _QoinWebviewScreenState();
}

class _QoinWebviewScreenState extends State<QoinWebviewScreen> {
  late final WebViewController _controller;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _initPermissionsAndWebview();
  }

  Future<void> _initPermissionsAndWebview() async {
    // 1. Minta izin lokasi GPS sebelum webview dibuka
    await Permission.locationWhenInUse.request();

    // 2. Konfigurasi Controller Webview
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(Colors.white)
      ..addJavaScriptChannel(
        'QoinBridge',
        onMessageReceived: (JavaScriptMessage message) {
          _handleWebviewMessage(message.message);
        },
      )
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageFinished: (String url) {
            setState(() => _isLoading = false);
            _injectPostMessage();
          },
        ),
      )
      ..loadRequest(Uri.parse('${widget.baseUrl}/'));
  }

  void _injectPostMessage() {
    final payload = jsonEncode({
      'Signature': widget.signature,
      'Palette': widget.palette,
    });

    // Pasang polyfill bridge agar window.postMessage di webview diteruskan ke channel Flutter
    const bridgeScript = """
      window.addEventListener('message', function(event) {
        if (window.QoinBridge) {
          var data = typeof event.data === 'string' ? event.data : JSON.stringify(event.data);
          window.QoinBridge.postMessage(data);
        }
      });
    """;

    _controller.runJavaScript(bridgeScript);
    _controller.runJavaScript("window.postMessage($payload, '*');");

    // Retry kirim ulang 500ms kemudian
    Future.delayed(const Duration(milliseconds: 500), () {
      _controller.runJavaScript("window.postMessage($payload, '*');");
    });
  }

  void _handleWebviewMessage(String messageText) {
    try {
      final data = jsonDecode(messageText);
      if (data is Map<String, dynamic>) {
        // Tangkap sinyal penutupan
        if (data['close'] == true) {
          Navigator.of(context).pop(data);
        }
      }
    } catch (e) {
      debugPrint('Format pesan non-JSON: $messageText');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: [
            WebViewWidget(controller: _controller),
            if (_isLoading)
              const Center(child: CircularProgressIndicator(color: Colors.amber)),
          ],
        ),
      ),
    );
  }
}
Tips Hasil Navigator Pop
Saat Navigator.of(context).pop(data) terpanggil, screen pemanggil akan menerima Map kembalian. Cek result['STATUS'] == 'TRUE' untuk mengupdate state aplikasi kamu!