Appearance
Mobile In-App Update Implementation
Android
- 참고
- https://developer.android.com/guide/playcore/in-app-updates?hl=ko
- https://developer.android.com/guide/playcore/in-app-updates/kotlin-java?hl=ko
- 즉시 업데이트 (AppUpdateType.FLEXIBLE)로 설정하더라도, X 버튼 클릭하여 닫고 앱을 이용할 수 있음.
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를 혼용해서 사용하기 위한 세팅
- Targets → Build Settings → Defines Module → Yes
- https://beenii.tistory.com/198
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
- RemoteConfigManager.swift 파일은 새 파일 생성 후 소스 복붙 권장.
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 } }