Etiqueta: Firebase

  • Boosting iOS App Flexibility with Firebase Remote Config

    Boosting iOS App Flexibility with Firebase Remote Config

    This post demonstrates how developers can dynamically update app behavior, UI elements, and features without requiring an App Store update. This capability is especially valuable for A/B testing, feature flagging, and personalized user experiences, making apps more adaptable and data-driven. Despite its benefits, many developers underutilize Remote Config. A step-by-step guide covering Firebase setup, SDK integration, fetching configurations, and best practices would offer significant value.

    In this post, we’ll walk through creating a sample iOS app that reads a feature flag and, based on its value, displays a different UI message.

    Setup Firebase and XCode

    In a previous post, Unlocking Firebase iOS Push Notifications, we explained how to set up a Firebase project for iOS from scratch. During this process, you obtained a file called GoogleService-Info.plist.

    You have to include it on your project, next step is include SPM Firebase Library.

    Add FirebaseCore and FirebaseRemoteConfig to your target.

    Create a Firebase Configuration Flag

    From your Firebase project side bar:

    Select ‘Remote Config’. And add a new parameter:

    In our implementation, ‘NewTentativeFeatureFlag’ will be a boolean (false) parameter. Once set, ensure that it is properly published.

    The iOS Remote Configuration App

    The Main ContentView retrieves the newTentativeFeatureFlag @Published attribute from the RemoteConfigManager component in Firebase:

    struct ContentView: View {
        @State private var showNewFeature = false
        @StateObject var remoteConfigManager = appSingletons.remoteConfigManager
        var body: some View {
            VStack {
                if remoteConfigManager.newTentativeFeatureFlag {
                    Text("New Feature is Enabled!")
                        .font(.largeTitle)
                        .foregroundColor(.green)
                } else {
                    Text("New Feature is Disabled.")
                        .font(.largeTitle)
                        .foregroundColor(.red)
                }
            }
        }
    }

    Depending on the value obtained, a different text is printed with a different color. This is a very simplistic example, but it could represent an A/B test, a new feature, or any functionality you want to run live.

    An alternative valid implementation could be to call getBoolValue inside the .onAppear modifier instead of using the @published attribute from RemoteConfigManager.

    RemoteConfigManager is the component that wraps Firebase Remote Config functionality.

    import Foundation
    import Firebase
    
    @globalActor
    actor GlobalManager {
        static var shared = GlobalManager()
    }
    
    @GlobalManager
    class RemoteConfigManager: ObservableObject {
        @MainActor
        @Published var newTentativeFeatureFlag: Bool = false
        
        private var internalNewTentativeFeatureFlag = false {
            didSet {
                Task { @MainActor [internalNewTentativeFeatureFlag]  in
                    newTentativeFeatureFlag = internalNewTentativeFeatureFlag
                }
            }
        }
        
        private var remoteConfig: RemoteConfig =  RemoteConfig.remoteConfig()
        private var configured = false
    
        @MainActor
        init() {
            Task { @GlobalManager in
                await self.setupRemoteConfig()
            }
        }
        
        private func setupRemoteConfig() async {
            guard !configured else { return }
            
            let settings = RemoteConfigSettings()
            settings.minimumFetchInterval = 0
            remoteConfig.configSettings = settings
            
            fetchConfig { [weak self] result in
                guard let self else { return }
                Task { @GlobalManager [result] in
                    configured = result
                    self.internalNewTentativeFeatureFlag = self.getBoolValue(forKey: "NewTentativeFeatureFlag")
                }
            }
        }
    
        private func fetchConfig(completion: @escaping @Sendable (Bool) -> Void) {
            remoteConfig.fetch { status, error in
                if status == .success {
                    Task { @GlobalManager in
                        self.remoteConfig.activate { changed, error in
                            completion(true)
                        }
                    }
                } else {
                    completion(false)
                }
            }
        }
        
        func getBoolValue(forKey key: String) -> Bool {
            return remoteConfig[key].boolValue
        }
        
        func getStringValue(forKey key: String) -> String {
            return remoteConfig[key].stringValue
        }
    }

    The code is already compatible with Swift 6 and defines a RemoteConfigManager class responsible for fetching and managing Firebase Remote Config values in a SwiftUI application. To ensure thread safety, all operations related to Remote Config are handled within a global actor (GlobalManager).

    The RemoteConfigManager class conforms to ObservableObject, allowing SwiftUI views to react to changes in its properties. The newTentativeFeatureFlag property is marked with @Published and updated safely on the main actor to maintain UI responsiveness.

    The class initializes Remote Config settings, fetches values asynchronously, and updates internalNewTentativeFeatureFlag accordingly. This design ensures efficient Remote Config value retrieval while maintaining proper concurrency handling in a Swift application.

    Build and run

    As title suggests, build and run:

    In Firebase Remote Config, ‘NewTentativeFeatureFlag’ is set to true, and the view is displaying the correct message. Now, let’s switch the value to false and restart the application. Yes, I said restart, because when the value is changed in the console, Firebase Remote Config has no mechanism to notify the app. The app must periodically fetch the value to detect any changes in its state.

    Now turn ‘NewTentativeFeatureFlag’  to false and re-start the app.

    Conclusions

    Firebase Remote Config is essential for implementing A/B tests and serves as a great mechanism for disabling immature functionalities that might fail in production (e.g., a new payment method).

    You can find source code used for writing this post in following repository

    References

  • Firebase Authentication in Your iOS App

    Firebase Authentication in Your iOS App

    User authentication is often a cumbersome task that becomes essential as an application grows more robust. Firebase Authentication simplifies this process by handling authentication for you. It supports several authentication methods, but for the purpose of this post, we will focus on email and password authentication.

    Firebase console

    The first step is to create a dashboard to manage your app’s Firebase capabilities. To do this, open the Firebase console and create a new project. Use the name of your app as the project name for better organization and clarity. For the purposes of this post, I will not enable analytics. With these steps completed, your project should be ready to use.

    Later on, we will revisit the setup-specific issues for your iOS app.

    Creating iOS App scaffolder

    Let’s set Firebase aside for a moment and create a blank application. The only important detail in this process is to pay attention to the Bundle Identifier, as we’ll need it in the upcoming steps.

    Connect Firebase to your app

    Return to Firebase to add it to your app, and select the iOS option

    This is the moment to enter your app’s iOS Bundle Identifier.

    The next step is to download the configuration file and incorporate it into your project.

    The final step is incorporating the Firebase SDK using Swift Package Manager (SPM). To do this, select your project, go to Package Dependencies, and click Add Package Dependency.

    Enter the GitHub repository URL provided in Step 3 of the Firebase configuration into the search input box.

    Don’t forget to add FirebaseAuth to your target application.

    Continue through the Firebase configuration steps, and it will eventually provide the code you need to connect your app to Firebase.

    Incorporate this code into your iOS app project, then build and run the project to ensure everything is working properly.

    Implement authentication

    Get back to Firebase console to set up Authentication

    As you can see, there are many authentication methods, but for the purpose of this post, we will only work with the email and password method.

    As you can see, there are many authentication methods, but for the purpose of this post, we will only focus on the email and password method.

    For this post, I have implemented basic views for user registration, login, logout, and password recovery. All authentication functionality has been wrapped into a manager called AuthenticatorManager. The code is very basic but fully functional and compliant with Swift 6.0.

    Don’t worry if I go too fast; the link to the code repository is at the end of this post. You’ll see that the code is very easy to read. Simply run the project, register an account, and log in.

    You can find the project code at following repository.

    Conclusions

    Firebase provides a fast and efficient way to handle user authentication as your app scales.