Flutter/ShoreBird
Flutter ) ShoreBird 활용 강제 업데이트 적용
EEYatHo
2025. 4. 14. 16:46
0. 개요
Shorebird는 패치를 다운로드한 후 앱을 재시작하지 않으면 최신패치가 적용되지 않음
치명적인 오류를 OTA로 급히 수정했는데, 유저에게 즉시 패치된 버전을 제공할 수 없다는 점은 큰 단점
(특히, 앱 스토어에서 처음 다운로드시, 패치가 적용되지않은 초기 release 버전을 설치)
하지만, Shorebird 상태를 관리하고 강제로 재실행시키는 로직을 추가하는 등의 커스텀이 가능
-> 그러려면, shorebird에서 제공하는 자동 업데이트를 제거하고, 커스텀하게 조절하는 부분이 필요
1. 자동 업데이트 끄기
shorebird.yaml 에서 auto_update: false
// shorebird.yaml
auto_update: false
2. shorebird_code_push 라이브러리 적용
// pubspec.yaml
# shorebird 버전 파악 및 강제 업데이트 추가를 위함
shorebird_code_push: 2.0.2
3. ShoreBird 상태를 체크하고 패치를 다운로드하는 클래스 선언
특이사항
- 롤백하는 경우 대응하기 위해, upToDate 여도 다운로드 시도
import 'package:flutter/material.dart';
import 'package:shorebird_code_push/shorebird_code_push.dart';
final class ShorebirdService {
final _updater = ShorebirdUpdater();
Patch? currentPatch;
bool _needRestart = false;
Future<bool> isNeedRestart() async {
_needRestart = false;
if (!_updater.isAvailable) {
debugPrint("🕊️🚨 updater.isAvailable == false 이므로 패스");
return false;
}
await _readPatch();
await _checkForUpdate();
return _needRestart;
}
Future<void> _readPatch() async {
try {
final currentPatch = await _updater.readCurrentPatch();
debugPrint("🕊️ 현재 패치 버전: ${currentPatch?.number}");
this.currentPatch = currentPatch;
} catch (error) {
debugPrint("🕊️🚨 _readPatch Error: $error");
}
}
Future<void> _checkForUpdate() async {
try {
debugPrint("🕊️ 업데이트 필요한지 체크");
final status = await _updater.checkForUpdate(track: UpdateTrack.stable);
switch (status) {
case UpdateStatus.upToDate:
debugPrint("🕊️ 최신버전이지만 롤백하는 경우도 있어서 시도해야함 (upToDate)");
await _downloadAndUpdate();
case UpdateStatus.outdated:
debugPrint("🕊️ 패치 다운로드 필요 (outdated)");
await _downloadAndUpdate();
case UpdateStatus.restartRequired:
debugPrint("🕊️ 재시작 필요 (restartRequired)");
_needRestart = true;
case UpdateStatus.unavailable:
debugPrint("🕊️ 불가능 (unavailable)");
}
} catch (error) {
debugPrint("🕊️🚨 checkForUpdate Error: $error");
}
}
Future<void> _downloadAndUpdate() async {
try {
// 다운로드 및 업데이트 (_updater.update 완료시, 재시작하면 되는 상태가 됨)
await _updater.update(track: UpdateTrack.stable);
debugPrint("🕊️ 패치 다운로드 완료");
_needRestart = true;
} on UpdateException catch (error) {
debugPrint("🕊️🚨 downloadPatch Error: ${error.message}");
}
}
}
4. 앱 시작시 체크
// iOS는 조금 오래걸리므로, await 사용 x
shorebirdService.isNeedRestart().then((isNeedRestart) {
if (isNeedRestart) {
showRestartDialog();
return;
}
});
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:homeknocktown_admin/presentation/design_system/atoms/dialog/dialog.dart';
import 'package:homeknocktown_admin/route.dart';
import 'package:homeknocktown_admin/utils/local_notification.dart';
Future<dynamic> showRestartDialog() async {
return showDialog(
context: navigatorKey.currentContext!,
barrierDismissible: false, // 배경 눌러도 안꺼지게
builder: (_) {
return PopScope(
canPop: false,
child: YourAwesomeDialog( /// dialog 정도는 각자 알아서 띄워주기..
text: "개선사항을 적용할 준비가 되었어요! 앱을 재시작 해주세요.",
confirmText: "앱 종료",
onConfirm: () async {
// await showLocalNotification(1, "개선사항 반영 완료", "앱을 다시 시작하기 위해 눌러주세요!");
exit(0);
}));
});
}
- 보너스
- iOS는 앱에서 스스로 재시작이 불가능함.
- 로컬 푸시로 유저에게 재실행하기 좋은 UX 제공
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
// 로컬 푸시 알림 보여주기
Future<void> showLocalNotification(int id, String? title, String? body) async {
debugPrint("🔔 showLocalNotification id: $id, title: $title, body: $body");
await plugin.show(
id,
title,
body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channelDescription: channel.description,
importance: Importance.max,
priority: Priority.high,
icon: '@mipmap/ic_notification', // 알림 아이콘
enableVibration: true, // 진동 활성화
playSound: true, // 소리 활성화
showWhen: true, // 시간 표시
),
), );
}
반응형