분류, 패키지 및 저장소 검색

force_update_helper 표지
패키지 상세v0.3.0

force_update_helper

원격에서 제어되는 강제 업데이트 프롬프트를 표시하기 위한 패키지입니다.

AndroidiOS
30일 다운로드
5.6천
좋아요
116
Pub 점수
150

패키지 설치

flutter pub add force_update_helper

패키지 개요

공개 정보를 바탕으로 생성한 선택 가이드입니다.

Android 및 iOS 앱에 대해 원격 제어를 통해 강제 업데이트 알림을 가능하게 하는 플러터 패키지입니다. 앱 시작 시 원격 구성(예: JSON 엔드포인트)을 확인하여 개발자가 앱 업데이트를 강제할 수 있습니다. 업데이트가 필요할 경우 사용자는 즉시 업데이트하도록 안내되어 모든 사용자가 최신 버전을 사용하게 됩니다. 이 패키지는 가볍고 복잡한 설정 없이 기존 앱에 쉽게 통합됩니다.

사용 사례

  • 중요한 버그 수정 강제 적용
  • 보안 업데이트 푸시
  • 사용자 간 기능 일치 유지
  • 업데이트 롤아웃 시기 조절
  • 새로운 앱 정책 준수 보장

주요 기능

  • HTTP 엔드포인트를 통한 원격 구성
  • 최소한의 코드로 간단한 통합
  • Android 및 iOS 지원
  • 사용자 정의 가능한 프롬프트 UI
  • 강제 업데이트 전 선택적 지연

적합한 대상

  • 엄격한 버전 제어가 필요한 앱
  • 기업 또는 규제 대상 애플리케이션
  • 자주 기능을 출시하는 앱
  • 여러 앱 버전을 관리하는 팀

고려 사항

  • 안정적인 인터넷 연결이 필요합니다
  • 외부 서버의 가용성에 의존합니다
  • 과도하게 사용할 경우 사용자 경험에 영향을 줄 수 있습니다
  • 실패한 검사에 대한 적절한 오류 처리가 필요합니다
  • 백엔드 지원이 없는 앱에는 적합하지 않습니다

원본 README

연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.

Force Update Helper

Pub Language License Twitter

A package for showing a force update prompt that is controlled remotely.

Force update alert preview

Features

  • Remote control: control the force update logic remotely with a custom backend, or Firebase Remote Config, or anything that resolves to a Future<String>.
  • UI-agnostic: the package tells you when to show the update UI, you decide how to show it (localization is up to you).
  • Small and opinionated: the package is made of only two classes. Use it as is, or fork it to suit your needs.

Getting started

Depend on it:

dependencies:
  force_update_helper:

Use it by adding a ForceUpdateWidget to your MaterialApp's builder property:

void main() {
  runApp(const MainApp());
}

final _rootNavigatorKey = GlobalKey<NavigatorState>();

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: _rootNavigatorKey,
      builder: (context, child) {
        return ForceUpdateWidget(
          navigatorKey: _rootNavigatorKey,
          forceUpdateClient: ForceUpdateClient(
            // * Real apps should fetch this from an API endpoint or via
            // * Firebase Remote Config
            fetchRequiredVersion: () => Future.value('2.0.0'),
            // * Example ID from this app: https://fluttertips.dev/
            // * To avoid mistakes, store the ID as an environment variable and
            // * read it with String.fromEnvironment
            iosAppStoreId: '6482293361',
          ),
          allowCancel: false,
          showForceUpdateAlert: (context, allowCancel) => showAlertDialog(
            context: context,
            title: 'App Update Required',
            content: 'Please update to continue using the app.',
            cancelActionText: allowCancel ? 'Later' : null,
            defaultActionText: 'Update Now',
          ),
          showStoreListing: (storeUrl) async {
            if (await canLaunchUrl(storeUrl)) {
              await launchUrl(
                storeUrl,
                // * Open app store app directly (or fallback to browser)
                mode: LaunchMode.externalApplication,
              );
            } else {
              log('Cannot launch URL: $storeUrl');
            }
          },
          onException: (e, st) {
            log(e.toString());
          },
          child: child!,
        );
      },
      home: const Scaffold(
        body: Center(
          child: Text('Hello World!'),
        ),
      ),
    );
  }
}

Note that in order to show the update dialog, a root navigator key needs to be added to MaterialApp (this is the same technique used by the upgrader package).

How the package works

Unlike the upgrader package, this package does not use the app store APIs to check if a newer version is available.

Instead, it allows you to store the required version remotely (using a custom backend or Firebase Remote Config), and compare it with the current version from your pubspec.yaml.

Here's how you may use this in production:

  • Submit a new version of your app to the stores
  • Once it's approved, publish it
  • Wait for an hour or so, to account for the time it takes for the new version to be visible on all stores/countries
  • Update the required_version endpoint in your custom backend or via Firebase Remote Config
  • Once users open the app, the force update logic will kick in and force them to update

Additional details

The package is made of two classes: ForceUpdateClient and ForceUpdateWidget.

  • The ForceUpdateClient class fetches the required version and compares it with the current version from package_info_plus. Versions are compared using the pub_semver package.
  • The fetchRequiredVersion callback should fetch the required version from an API endpoint or Firebase Remote Config.
  • When creating your iOS app in App Store Connect, copy the app ID and use it as the iosAppStoreId, otherwise the force upgrade alert will not show. I recommend storing an APP_STORE_ID as an environment variable that is set with --dart-define or --dart-define-from-file and read with String.fromEnvironment.
  • The Play Store URL is automatically generated from the package name (which is retrieved with the package_info_plus package)
  • If you want to make the update optional, pass allowCancel: true to the ForceUpdateWidget and use it to add a cancel button to the alert dialog. This will make the alert dismissable, but the prompt will still show on the next app start.
  • You can catch and handle any exceptions with the onException handler. Alternatively, omit the onException and handle exceptions globally.
  • If you use the url_launcher package to open the app store URLs (which is the recommended way), don't forget to add the necessary query intent inside AndroidManifest.xml:
    <queries>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https"/>
        </intent>
    </queries>

When is the Force Update Alert shown?

The force update logic is triggered in two cases:

  • when the app has just started (from a cold boot)
  • when the app returns to the foreground (common when switching between apps)

Then, the update alert will show if all these conditions are true:

  • the app is running on iOS or Android (web and desktop are not supported)
  • the requiredVersion is fetched successfully
  • the requiredVersion is greater than the currentVersion
  • (iOS only) the iosAppStoreId is a non-empty string

If the user clicks on "Update Now" and lands on the app store page but does not update the app, the force update alert will show again when returning to the app.

If the update alert shows on Android and the back button is pressed, it will be shown again unless allowCancel is true.

Where to find the iosAppStoreId

Once you have created your app in App Store Connect, you can grab the app ID from the browser URL:

Force update alert preview

Make sure to set the correct iosAppStoreId before releasing the first version of your app, otherwise users on old version won't be able to update.

Example: fetching a remote config JSON from a GitHub Gist

The example app shows how to fetch some remote config JSON from a RemoteConfigGistClient:

ForceUpdateWidget(
  navigatorKey: _rootNavigatorKey,
  forceUpdateClient: ForceUpdateClient(
    fetchRequiredVersion: () async {
      // * Fetch remote config from an API endpoint.
      // * Alternatively, you can use Firebase Remote Config
      final client = RemoteConfigGistClient(dio: Dio());
      final remoteConfig = await client.fetchRemoteConfig();
      return remoteConfig.requiredVersion;
    },
    // * Example ID from this app: https://fluttertips.dev/
    // * To avoid mistakes, store the ID as an environment variable and
    // * read it with String.fromEnvironment
    iosAppStoreId: '6482293361',
  ),
  allowCancel: false,
  showForceUpdateAlert: (context, allowCancel) => showAlertDialog(
    context: context,
    title: 'App Update Required',
    content: 'Please update to continue using the app.',
    cancelActionText: allowCancel ? 'Later' : null,
    defaultActionText: 'Update Now',
  ),
  showStoreListing: (storeUrl) async {
    if (await canLaunchUrl(storeUrl)) {
      await launchUrl(
        storeUrl,
        // * Open app store app directly (or fallback to browser)
        mode: LaunchMode.externalApplication,
      );
    } else {
      log('Cannot launch URL: $storeUrl');
    }
  },
  onException: (e, st) {
    log(e.toString());
  },
  child: child!,
)

The RemoteConfigGistData class can be used to fetch and parse some JSON in this format:

{
  "config" : {
    "required_version": "2.0.0"
  }
}

Here's the reference RemoteConfigGistData class:

import 'dart:convert';

import 'package:dio/dio.dart';

class RemoteConfigGistData {
  RemoteConfigGistData({required this.requiredVersion});
  final String requiredVersion;

  factory RemoteConfigGistData.fromJson(Map<String, dynamic> json) {
    final requiredVersion = json['config']?['required_version'];
    if (requiredVersion == null) {
      throw FormatException('required_version not found in JSON: $json');
    }
    return RemoteConfigGistData(requiredVersion: requiredVersion);
  }
}

/// An API client class for fetching a remote config JSON from a GitHub gist
class RemoteConfigGistClient {
  const RemoteConfigGistClient({required this.dio});
  final Dio dio;

  /// Fetch the remote config JSON
  Future<RemoteConfigGistData> fetchRemoteConfig() async {
    // TODO: Update this with your GitHub username
    const owner = 'bizz84';
    // TODO: Update this with your gist IDs
    const gistId = 'e5b8041b35c58a3eba2baa23096d1678';
    // TODO: Update this with your gist file name
    const fileName = 'app_name_remote_config.json';
    const url =
        'https://gist.githubusercontent.com/$owner/$gistId/raw/$fileName';
    final response = await dio.get(url);
    final jsonData = jsonDecode(response.data);
    return RemoteConfigGistData.fromJson(jsonData);
  }
}

For more info, see this example app:

Example: fetching the required version from a backend endpoint

The package comes with a sample server-side app that implements a required_version endpoint using Dart Shelf.

This can be used as part of the force update logic in your Flutter apps.

For more info, see this example app:

Are contributions welcome?

I created this package so I can reuse the force update logic in my own apps.

While you're welcome to suggest improvements, I don't want the package to become bloated, and I only plan to make changes that suit my needs.

If the package doesn't suit your use case, consider forking and maintaining it yourself.

LICENSE: MIT

카테고리

의존성

의존성 목록 2 개

소스 저장소

이 패키지를 사용하는 프로젝트