비전공자 개발일기

Real Time Notice' s modal (Firebase Remote Config & A/B Test) 본문

SWIFT

Real Time Notice' s modal (Firebase Remote Config & A/B Test)

HiroDaegu 2022. 12. 20. 17:23
728x90
SMALL

Remote Config

 

  • 배포 및 업데이트 다운로드 없이 앱 변경 
  • 기본값을 설정한 후 값을 재정의
  • 클라우드 기반 Key - Value 저장소
  • 앱 사용층에 변경사항을 빠르게 적용(업데이트 없이 앱의 UI/UX 변경을 지원)
  • 사용자층의 특정 세그먼트에 앱 맞춤 설정(앱 버전, 언어 등으로 분류된 사용자 세그먼트별 환경 제공)
  • A / B Test를 실행하여 앱 개선(사용자 세그먼트별로 개선사항을 검증 후 점진적 적용을 해볼 수 있음)

A / B Test

 

  • Google Analytics, Firebase 예측을 통한 사용자 타겟팅
  • 원격구성(Remote Config or Cloud Messaging)활용
  • 재품, 마케팅 실험을 쉽게 실행, 분석, 확장이 가능
  • 제품 환경 테스트 및 개선(앱 동작 및 모양을 변경하여 최적의 제품 환경을 확인할 수 있음)
  • 사용자의 재참여를 유도할 방안 모색(앱 사용자를 늘리기에 가장 효과적인 문구와 메시징을 설정할 수 있음)
  • 새로운 기능의 안전한 구현(작은 규모의 사용자 집합을 대상으로 원하는 목표를 달성할 수 있는지 확인)
  • 예측된 사용자 그룹 타켓팅(특정 행동을 할 것으로 예측된 사용자에게 A / B Test를 실시해볼 수 있음)

실시간으로 작성해서 보낸 공지 팝업
이용자별로 다른 문구를 보내는 A/B test

import UIKit
import Firebase

@main
class AppDelegate: UIResponder, UIApplicationDelegate {



    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        FirebaseApp.configure()
        Installations.installations().authTokenForcingRefresh(true) { result, error in
            if let error = error {
                print("Error")
                return
            }
            guard let result = result else { return }
            print("Installation auth token: \(result.authToken)")
        }
        return true
    }

    // MARK: UISceneSession Lifecycle
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }


}
import UIKit

class NoticeViewController: UIViewController {

    var noticeContents: (title: String, detail: String, date: String)?
    
    @IBOutlet weak var noticeView: UIView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var detailLabel: UILabel!
    @IBOutlet weak var dateLabel: UILabel!
        
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        noticeView.layer.cornerRadius = 6
        view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
        
        guard let noticeContents = noticeContents else { return }
        titleLabel.text = noticeContents.title
        detailLabel.text = noticeContents.detail
        dateLabel.text = noticeContents.date
    }
    
    @IBAction func okayBTN(_ sender: UIButton) {
        dismiss(animated: true)
    }
    
}
import UIKit
import FirebaseRemoteConfig
import FirebaseAnalytics

class ViewController: UIViewController {
    
    var remoteConfig: RemoteConfig?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        remoteConfig = RemoteConfig.remoteConfig()
        
        let setting = RemoteConfigSettings()
        setting.minimumFetchInterval = 0
        
        remoteConfig?.configSettings = setting
        remoteConfig?.setDefaults(fromPlist: "RemoteConfigDefaults")
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        getNotice()
    }

}

extension ViewController {
    
    func getNotice() {
        guard let remoteConfig = remoteConfig else { return }
        
        remoteConfig.fetch { [weak self] status, _ in
            if status == .success {
                remoteConfig.activate()
            } else {
                print("Error: Config not fetched")
            }
            
            guard let self = self else { return }
            if !self.isNoticeHidden(remoteConfig) {
                let noticeViewController = NoticeViewController(nibName: "NoticeViewController", bundle: nil)
                
                noticeViewController.modalPresentationStyle = .custom
                noticeViewController.modalTransitionStyle = .crossDissolve
                
                let title = (remoteConfig["title"].stringValue ?? "").replacingOccurrences(of: "\\n", with: "\n")
                let detail = (remoteConfig["detail"].stringValue ?? "").replacingOccurrences(of: "\\n", with: "\n")
                let date = (remoteConfig["date"].stringValue ?? "").replacingOccurrences(of: "\\n", with: "\n")
                
                noticeViewController.noticeContents = (title: title, detail: detail, date: date)
                self.present(noticeViewController, animated: true)
            } else {
                self.showEventAlert()
            }
        }
    }
    
    func isNoticeHidden(_ remoteConfig: RemoteConfig) -> Bool {
        return remoteConfig["isHidden"].boolValue
    }
}

// A/B Testing
extension ViewController {
    func showEventAlert() {
        guard let remoteConfig = remoteConfig else {
            return
        }
        remoteConfig.fetch { [weak self] status, _ in
            if status == .success {
                remoteConfig.activate()
            } else {
                print("Config not fetched")
            }
        }
        
        let message = remoteConfig["message"].stringValue ?? ""
        let confirmAction = UIAlertAction(title: "Check", style: .default) { _ in
            Analytics.logEvent("promotion_alert", parameters: nil)
        }
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
        let alertController = UIAlertController(title: "Surprise", message: message, preferredStyle: .alert)
        alertController.addAction(confirmAction)
        alertController.addAction(cancelAction)
        
        self.present(alertController, animated: true)
    }
}
//RemoteConfigDefaults.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>isHidden</key>
	<true/>
	<key>title</key>
	<string>Here&apos;s UR guide</string>
	<key>detail</key>
	<string>Please refer to the service
Please refer to the service
</string>
	<key>date</key>
	<string>22.12.20(Tue) 00:00-03:00(3 hours)</string>
</dict>
</plist>
728x90
LIST

'SWIFT' 카테고리의 다른 글

Egg Timer  (0) 2022.12.24
Xylophone  (0) 2022.12.23
Local Push Notification  (0) 2022.12.19
Remote Push Notification(APNs, FCM)  (0) 2022.12.19
Diary  (0) 2022.12.18