Skip to content

Mobile In-App Update Implementation

  • Android

    dependencies {
    	implementation "com.google.android.play:app-update:2.1.0"
    }
    java
    @Override
    public void onCreate(Bundle savedInstanceState) {
        // In-App 업데이트를 확인하고 제안
        checkForAppUpdate();
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(this);
    
        appUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {
            if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                // If an in-app update is already running, resume the update.
                appUpdateManager.startUpdateFlow(appUpdateInfo, MainActivity.this, AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());
            }
        });
    }
    
    // 업데이트를 확인하고 제안하는 함수
    private void checkForAppUpdate() {
        AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(this);
    
        // 업데이트를 확인하는 데 사용하는 intent 개체를 반환합니다.
        Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
    
        // 플랫폼에서 지정한 업데이트 유형을 허용하는지 확인합니다.
        appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
            // 이 예제는 즉시 업데이트를 적용합니다. 유연한 업데이트를 적용하려면 대신 AppUpdateType.FLEXIBLE 전달합니다
            // AppUpdateType.FLEXIBLE
            // AppUpdateType.IMMEDIATE
            if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
                // 업데이트 가능한 상황
                try {
                    appUpdateManager.startUpdateFlow(appUpdateInfo, MainActivity.this, AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());
                } catch (Exception e) {
                    e.printStackTrace();
                }
    
            }
        });
    }
  • iOS

    • ngstudy 파일 version 변경 필요

      • 코드 수정하고 prepare 했을때, 앱 실행중일 경우(시뮬레이터) xcode에 수정된 코드가 반영됨
      • 패키지 불러올때 중지할 경우, 다 날아감 (중지할때 패키지 관련 경고문구 뜨면 취소 눌러야함)
        • 이미 날아갔다면 Swift Packager Manager 이용하여 재설치 (firebase)
    • Objective-C와 Swift를 혼용해서 사용하기 위한 세팅

    • Firebase Remote Config 이용

      • https://firebase.google.com/docs/remote-config/get-started?hl=ko&platform=ios
      • Cordova Project에서는 GoogleService-Info.plist를 Project Root가 아닌 Resources 폴더 내에 추가해야함. (※ 파일 복사 대신 Recources 폴더 우클릭 → Add Files to 이용)
      • Firebase SDK 설치 : Cordova Project에서 cocoapods 이용시 FirebaseCore Import가 원활하지 않아, Swift Package Manager 이용 추천.
    • 소스 참고

    • 비고

      • RemoteConfigManager.swift 파일은 새 파일 생성 후 소스 복붙 권장.
        • Objective-C와 Swift 혼용시 Bridging-Header.h 자동 생성
      • Build Settings - Product Module Name 과 <ProjectName>-Swift.h 은 같아야 함.
        • 같지 않을 경우 <ProjectName>-Swift.h file not found. 에러 발생.
      • Firebase module 을 찾지 못한다면, General - Frameworks, Libraries 부분에 Firebase 추가
        • FirebaseAuth, FirebaseAnalytics, FirebaseRemoteConfig, FirebaseDatabase, FirebaseCrashlytics
    java
    #import "AppDelegate.h"
    #import "MainViewController.h"
    #import "ngstudy-Swift.h"
    @import FirebaseCore;
    
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
    {
        self.viewController = [[MainViewController alloc] init];
    
        [FIRApp configure]; // 초기화
    
        RemoteConfigManager *sharedManager = [RemoteConfigManager sharedManager];
    
        // completionHandler 클로저 정의
        void (^completionHandler)(AppConfig *) = ^(AppConfig *config) {
            // 이 부분에서 config 객체를 사용하여 작업 수행
            NSLog(@"Lastest Version: %@", config.lastestVersion);
            NSLog(@"Min Version: %@", config.minVersion);
        };
    
        // forceUpdate 클로저 정의
        void (^forceUpdate)(BOOL) = ^(BOOL need) {
    //        if (!need) {
    //        }
        };
    
        [sharedManager launching:completionHandler forceUpdate:forceUpdate];
    
        return [super application:application didFinishLaunchingWithOptions:launchOptions];
    }
    
    @end
    swift
    //
    //  RemoteConfigManager.swift
    //  ngstudy
    //
    //  Created by jaeuk on 2023/09/19.
    //
    
    import UIKit
    import Firebase
    
    @objc
    class AppConfig: NSObject {
        @objc var lastestVersion: String?
        @objc var minVersion: String?
    }
    
    @objc
    class RemoteConfigManager: NSObject {
    
        @objc
        static let sharedManager = RemoteConfigManager()
    //    override private init() {}
    
        @objc
        public func launching(_ completionHandler: @escaping (_ conf: AppConfig) -> (), forceUpdate:@escaping (_ need: Bool)->()) {
            let remoteConfig = RemoteConfig.remoteConfig()
            let nowVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String;
    
            let appleID = "1526291703"
            let appStoreOpenUrlString = "itms-apps://itunes.apple.com/app/apple-store/\(appleID)"
    
            remoteConfig.fetch(withExpirationDuration: TimeInterval(3600)) { (status, error) -> Void in
                if status == .success {
                    remoteConfig.activate()
    
                    // 데이터 Fetch
                    let appConfig: AppConfig = AppConfig()
                    appConfig.lastestVersion = remoteConfig["lastestVersion"].stringValue
                    appConfig.minVersion = remoteConfig["minVersion"].stringValue
    
                    completionHandler(appConfig)
    
                    // 강제업데이트
                    let needForcedUpdate:Bool = (self.compareVersion(versionA: nowVersion, versionB: appConfig.minVersion) == ComparisonResult.orderedAscending)
                    forceUpdate(needForcedUpdate)
    
                    if needForcedUpdate {
                        print("needForcedUpdate")
                        let alertController = UIAlertController.init(title: "업데이트", message: "필수 업데이트가 있습니다. 업데이트하시겠습니까?", preferredStyle: UIAlertController.Style.alert)
                        alertController.addAction(UIAlertAction.init(title: "업데이트", style: UIAlertAction.Style.default, handler: { (action) in
                            // 앱스토어마켓으로 이동
                            if let url = URL(string: appStoreOpenUrlString),
                               UIApplication.shared.canOpenURL(url) {
                                UIApplication.shared.open(url, options: [:], completionHandler: nil)
                            }
                        }))
                        var topController = UIApplication.shared.keyWindow?.rootViewController
                        if topController != nil {
                            while let presentedViewController = topController?.presentedViewController {
                                topController = presentedViewController
                            }
                        }
                        topController!.present(alertController, animated: false, completion: {
    
                        })
                    }
    
                    // 선택업데이트
                    let needUpdate:Bool = (self.compareVersion(versionA: nowVersion, versionB: appConfig.minVersion) != ComparisonResult.orderedAscending) && (self.compareVersion(versionA: nowVersion, versionB: appConfig.lastestVersion) == ComparisonResult.orderedAscending)
    
                    if needUpdate {
                        print("needUpdate")
                        let alertController = UIAlertController.init(title: "업데이트", message: "최신 업데이트가 있습니다. 업데이트하시겠습니까?", preferredStyle: UIAlertController.Style.alert)
                        alertController.addAction(UIAlertAction.init(title: "업데이트", style: UIAlertAction.Style.default, handler: { (action) in
                            // 앱스토어마켓으로 이동
                            if let url = URL(string: appStoreOpenUrlString),
                               UIApplication.shared.canOpenURL(url) {
                                UIApplication.shared.open(url, options: [:], completionHandler: nil)
                            }
                        }))
                        alertController.addAction(UIAlertAction.init(title: "나중에", style: UIAlertAction.Style.default, handler: { (action) in
                            // 앱으로 진입
                        }))
                        var topController = UIApplication.shared.keyWindow?.rootViewController
                        if topController != nil {
                            while let presentedViewController = topController?.presentedViewController {
                                topController = presentedViewController
                            }
                        }
                        topController!.present(alertController, animated: false, completion: {
    
                        })
                    }
    
                    }
                }
        }
    
        @objc
        private func compareVersion(versionA:String!, versionB:String!) -> ComparisonResult {
            let majorA = Int(Array(versionA.split(separator: "."))[1])!
            let majorB = Int(Array(versionB.split(separator: "."))[1])!
    
            if majorA > majorB {
                return ComparisonResult.orderedDescending
            } else if majorB > majorA {
                return ComparisonResult.orderedAscending
            }
    
            let minorA = Int(Array(versionA.split(separator: "."))[2])!
            let minorB = Int(Array(versionB.split(separator: "."))[2])!
            if minorA > minorB {
                return ComparisonResult.orderedDescending
            } else if minorB > minorA {
                return ComparisonResult.orderedAscending
            }
            return ComparisonResult.orderedSame
        }
    
    }

IronTrain Tech Blog