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, // 시간 표시
      ),
    ),  );
}

반응형