← กรณีการใช้งาน

Flutter บน iOS, Android และเว็บ

ไคลเอนต์ Dart ตัวเดียว แชนเนลส่วนตัว เชื่อมต่อใหม่ และพุชเมื่อแอปปิดอยู่

Flutter สร้างแอปเดียวสำหรับ iOS, Android, เว็บ และเดสก์ท็อป โค้ดเรียลไทม์ก็ควรเป็นชุดเดียวด้วย ปลั๊กอิน Pusher Flutter ทางการเชื่อมต่อกับโฮสต์ของ pusher.com เสมอ จึงควรใช้ dart_pusher_channels แทน ซึ่งเป็นไคลเอนต์โปรโตคอล Pusher ที่เขียนด้วย Dart ล้วน ตั้งค่าโฮสต์ได้ และรันได้ทุกที่ที่ Flutter รัน

สิ่งที่ผู้ใช้ได้รับ

  • หน้าคำสั่งซื้อ แชต และคะแนนที่อัปเดตในที่ โดยไม่ต้องดึงลงเพื่อรีเฟรช
  • การเชื่อมต่อที่กลับมาเองหลังผ่านอุโมงค์ ขึ้นลิฟต์ หรือสลับจาก Wi-Fi เป็นเน็ตมือถือ
  • ทำงานเหมือนกันทั้งบนมือถือและในเบราว์เซอร์

ฟีเจอร์ที่ใช้

  • dart_pusher_channels ไคลเอนต์ตัวเดียวสำหรับทั้งแอป
  • แชนเนลส่วนตัว (private-) ที่แบ็กเอนด์ของคุณยืนยันสิทธิ์ด้วยโทเคนของผู้ใช้ที่ล็อกอินอยู่
  • อีเวนต์ฝั่งไคลเอนต์บนแชนเนลส่วนตัวและ presence สำหรับสถานะกำลังพิมพ์และสัญญาณเล็ก ๆ อื่น ๆ
  • Hibira Push สำหรับช่วงที่แอปอยู่เบื้องหลังหรือปิดอยู่

ขั้นที่ 1: ติดตั้ง

flutter pub add dart_pusher_channels

ขั้นที่ 2: ไคลเอนต์ตัวเดียวสำหรับทั้งแอป

// lib/realtime.dart
import 'package:dart_pusher_channels/dart_pusher_channels.dart';

final realtime = PusherChannelsClient.websocket(
  options: PusherChannelsOptions.fromHost(
    scheme: 'wss',
    host: 'ws.hibira.io',
    port: 443,
    key: const String.fromEnvironment('HIBIRA_KEY'),
  ),
  connectionErrorHandler: (error, trace, refresh) => refresh(),
);

ส่ง key ตอน build: flutter run --dart-define=HIBIRA_KEY=YOUR_APP_KEY ห้ามใส่ app secret ไว้ในแอปเด็ดขาด แชนเนลส่วนตัวลงนามโดยแบ็กเอนด์ของคุณ

ขั้นที่ 3: ติดตามคำสั่งซื้อหนึ่งรายการบนหน้าจอ

// lib/order_screen.dart
import 'dart:async';
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
import 'package:flutter/material.dart';
import 'realtime.dart';

class OrderScreen extends StatefulWidget {
  const OrderScreen({super.key, required this.orderId, required this.token});
  final String orderId;
  final String token;

  @override
  State<OrderScreen> createState() => _OrderScreenState();
}

class _OrderScreenState extends State<OrderScreen> {
  late final PrivateChannel channel;
  final subs = <StreamSubscription>[];
  String status = '…';

  @override
  void initState() {
    super.initState();
    channel = realtime.privateChannel(
      'private-order-${widget.orderId}',
      authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
        authorizationEndpoint: Uri.parse('https://api.example.com/pusher/auth'),
        headers: {'Authorization': 'Bearer ${widget.token}'},
      ),
    );
    subs.add(channel.bind('status').listen((event) {
      setState(() => status = event.tryGetDataAsMap()?['status'] ?? status);
    }));
    subs.add(realtime.onConnectionEstablished.listen((_) => channel.subscribeIfNotUnsubscribed()));
    channel.subscribeIfNotUnsubscribed();
  }

  @override
  void dispose() {
    for (final s in subs) {
      s.cancel();
    }
    channel.unsubscribe();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Text(status);
}

การสมัครรับใหม่ใน onConnectionEstablished ทำให้แชนเนลกลับมาทุกครั้งหลังเชื่อมต่อใหม่ endpoint ยืนยันสิทธิ์ของแบ็กเอนด์จะได้รับ socket_id และ channel_name ตรวจโทเคน แล้วส่งลายเซ็นที่สร้างจากไลบรารีฝั่งเซิร์ฟเวอร์ที่เข้ากันได้กับ Pusher กลับไป

ขั้นที่ 4: เชื่อมต่อเฉพาะตอนที่แอปเปิดอยู่

class _AppState extends State<App> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    realtime.connect();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) realtime.disconnect();
    if (state == AppLifecycleState.resumed) realtime.connect();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => const MaterialApp(home: HomeScreen());
}

iOS และ Android หยุดเครือข่ายของแอปที่อยู่เบื้องหลังอยู่แล้ว จึงควรตัดการเชื่อมต่อเมื่อ paused และเชื่อมต่อใหม่เมื่อ resumed เรื่องที่ผู้ใช้ต้องรู้ขณะที่แอปปิดอยู่ควรส่งเป็นการแจ้งเตือนแบบพุช ดูการแจ้งเตือนแบบพุชที่ดึงผู้ใช้กลับมา และคู่มือพุช ซึ่งอธิบาย SDK ของ Android และ iOS ที่แอปโฮสต์ของ Flutter เรียกใช้ได้

เคล็ดลับ

  • โหลดสิ่งที่หน้าจอต้องใช้จาก API ก่อน แล้วให้อีเวนต์อัปเดตต่อ สถานะที่ต้องมีตั้งแต่เปิดหน้า ให้เผยแพร่ไปที่แชนเนล cache-
  • อีเวนต์ฝั่งไคลเอนต์ (channel.trigger(eventName: 'client-typing', data: {...})) ใช้ได้บนแชนเนลส่วนตัวและ presence เมื่อเปิด Allow client events ในการตั้งค่าแอป
  • เมื่อเข้าสู่ระบบแล้ว คู่มือ Flutter จะมีการตั้งค่าไคลเอนต์ที่กรอกค่าของแอปคุณไว้ให้