iOS, Android, 웹의 Flutter
Dart 클라이언트 하나: 프라이빗 채널, 재연결, 앱이 닫혀 있을 때는 푸시.
Flutter는 하나의 코드로 iOS, Android, 웹, 데스크톱 앱을 만듭니다. 실시간 코드도 하나여야 합니다. 공식 Pusher Flutter 플러그인은 항상 pusher.com 호스트에 연결하므로, 대신 dart_pusher_channels를 쓰세요. 호스트를 지정할 수 있는 순수 Dart Pusher 프로토콜 클라이언트로, Flutter가 돌아가는 곳이면 어디서나 동작합니다.
사용자가 얻는 것
- 주문, 채팅, 점수 화면이 당겨서 새로고침하지 않아도 그 자리에서 업데이트됩니다.
- 터널, 엘리베이터, Wi-Fi에서 모바일 데이터로 바뀐 뒤에도 연결이 스스로 돌아옵니다.
- 휴대폰과 브라우저에서 똑같이 동작합니다.
사용하는 기능
- 앱 전체에 클라이언트 하나인 dart_pusher_channels.
- 로그인한 사용자의 토큰으로 백엔드가 인가하는 프라이빗 채널(
private-). - 입력 중 표시 같은 작은 신호를 위한 프라이빗·프레즌스 채널의 클라이언트 이벤트.
- 앱이 백그라운드에 있거나 닫혀 있을 때를 위한 Hibira Push.
1단계: 설치
flutter pub add dart_pusher_channels2단계: 앱 전체에 클라이언트 하나
// 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(),
);키는 빌드할 때 넘깁니다: flutter run --dart-define=HIBIRA_KEY=YOUR_APP_KEY. 앱 시크릿은 절대 앱에 넣지 않습니다. 프라이빗 채널은 백엔드가 서명합니다.
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;
State<OrderScreen> createState() => _OrderScreenState();
}
class _OrderScreenState extends State<OrderScreen> {
late final PrivateChannel channel;
final subs = <StreamSubscription>[];
String status = '…';
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();
}
void dispose() {
for (final s in subs) {
s.cancel();
}
channel.unsubscribe();
super.dispose();
}
Widget build(BuildContext context) => Text(status);
}onConnectionEstablished에서 다시 구독하면 재연결될 때마다 채널이 돌아옵니다. 백엔드의 인가 엔드포인트는 socket_id와 channel_name을 받아 토큰을 확인하고, Pusher 호환 서버 라이브러리로 만든 서명을 반환합니다.
4단계: 앱이 열려 있을 때만 연결
class _AppState extends State<App> with WidgetsBindingObserver {
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
realtime.connect();
}
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) realtime.disconnect();
if (state == AppLifecycleState.resumed) realtime.connect();
}
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
Widget build(BuildContext context) => const MaterialApp(home: HomeScreen());
}iOS와 Android는 어차피 백그라운드 앱의 네트워크를 멈추므로, paused에서 연결을 끊고 resumed에서 다시 연결하세요. 앱이 닫혀 있는 동안 사용자가 꼭 알아야 할 것은 푸시 알림으로 보내세요. 사용자를 다시 불러오는 푸시 알림과, Flutter 호스트 앱에서 호출할 수 있는 Android·iOS SDK를 다루는 푸시 가이드를 참고하세요.
팁
- 화면에 필요한 것은 먼저 API에서 불러오고, 그다음 이벤트로 업데이트하세요. 열자마자 있어야 하는 상태는
cache-채널에 발행하세요. - 앱 설정에서 Allow client events를 켜면 클라이언트 이벤트(
channel.trigger(eventName: 'client-typing', data: {...}))를 프라이빗·프레즌스 채널에서 쓸 수 있습니다. - 로그인하면 Flutter 가이드에 앱 값이 채워진 클라이언트 설정이 있습니다.