Using Kotlin Flow and Firebase Realtime Remote Config in an MVVM/Clean Architecture

Kais Jaafoura
Bforbank Tech
Published in
4 min readJul 8, 2024

Introduction

Staying flexible and sensitive to user needs is crucial in the always changing world of mobile app development. The capacity to remotely customize your application without requiring frequent updates and redeployments is a vital component of achieving this agility. Firebase Realtime Remote Config, provides a potent answer to this problem.

In this post, we’ll set out on a journey via the integration of Firebase Realtime Remote Config into an Android application, all the while sticking to the MVVM (Model-View-ViewModel) architectural pattern’s tenets and making use of Kotlin Flow’s adaptability. You will have a strong idea of how to build your code for clean architecture, how to effortlessly retrieve and apply distant data by the end of this guide.

Prerequisites

In this section, list the prerequisites for following along with your tutorial. Mention that readers should have:

-Basic knowledge of Android app development with Kotlin.
-A Firebase project set up with Firebase Remote Config.
-A basic understanding of the MVVM (Model-View-ViewModel) architectural pattern.

-Dependency injection with Hilt
-Kotlin Flow knowledge or a willingness to learn.

Enabling Real-Time Remote Config in GCP

Before diving into the code, ensure that the Firebase Remote Config Realtime API is enabled in your Google Cloud Platform (GCP) project:

  1. Open the Google Cloud Platform Console.
  2. Select your Firebase project.
  3. Navigate to the APIs & Services section.
  4. Search for “Firebase Remote Config Realtime API” and enable it.
  • Important: This API should already be enabled for you, but it’s good to double-check.

Clean Architecture: A Solid Foundation

Clean Architecture provides a robust structure for our implementation:

  • Data Layer: Interacts with Firebase Remote Config, fetching initial values and establishing a listener for real-time updates.
  • Domain Layer: Encapsulates the business logic related to config parameters, ensuring a clean separation of concerns.
  • Presentation Layer: Reacts to config changes, updating the UI in real-time using MVVM and Jetpack Compose.

Implementation: Real-Time Updates in Action

  1. Project Setup:
  • Ensure you have the necessary Firebase dependencies:
dependencies {
// Import the BoM for the Firebase platform
implementation(platform("com.google.firebase:firebase-bom:32.8.0"))

// Add the dependencies for the Remote Config and Analytics libraries
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation("com.google.firebase:firebase-config")
implementation("com.google.firebase:firebase-analytics")
}
  • Initialize Firebase in your application.

Implement in Data layer the repository of Remote Config using addOnConfigUpdateListener method from FirebaseRemoteConfig and the callbackFlow

The callbackFlow function generates an instance of a cold Flow in our case it’s a flow of strings.

When an update occurs, it tries to send the new value for the specified key using trySend, awaitClose is used to define a cleanup action when the flow is cancelled.

Neglecting to use awaitClose could result in lingering resources, potentially leading to degraded app performance or unexpected behavior over time.

class FirebaseRealtimeRemoteConfigRepository @Inject constructor(private val firebaseRemoteConfig: FirebaseRemoteConfig) :
RealtimeRemoteConfigRepository {
override fun getConfig(key: String): Flow<String> = callbackFlow {
val listener = FirebaseRemoteConfig.OnConfigUpdateListener {
trySend(firebaseRemoteConfig.getString(key))
}
firebaseRemoteConfig.addOnConfigUpdateListener(listener)
awaitClose {
firebaseRemoteConfig.removeOnConfigUpdateListener(listener)
}
}
}
  • Create an interface for real-time config interaction in Domain Layer :
interface RealtimeRemoteConfigRepository {
fun getConfig(key: String): Flow<String>
}
  • Now we can implement our Hilt Module to link the Interface with the Implementation in the Data Layer
@Module
abstract class RealTimeRemoteConfigModule {
@Binds
abstract fun providesRealTimeRemoteConfig(repository : FirebaseRealtimeRemoteConfigRepository): RealtimeRemoteConfigRepository
}
  • Create a use case to encapsulate the logic of fetching and processing config values:
class GetRemoteConfigUseCase @Inject constructor(
private val repository: RealtimeRemoteConfigRepository
) {
operator fun invoke(key: String): Flow<String> = repository.getConfig(key)
}

Presentation Layer (ViewModel):

  • Inject the use case and expose config values as Flows:
@HiltViewModel
class MainViewModel @Inject constructor(
getRemoteConfigUseCase: GetRemoteConfigUseCase
) : ViewModel() {
val featureEnabled = getRemoteConfigUseCase("enable_new_feature")
.map { it.toBoolean() } // Example transformation
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
}

Jetpack Compose (UI):

  • Observe the Flow in your composable and update the UI reactively:
@Composable
fun MyScreen(viewModel: MainViewModel = hiltViewModel()) {
val featureEnabled by viewModel.featureEnabled.collectAsState()
// ... use featureEnabled to conditionally render UI elements
}

Conclusion

Realtime Remote Config offers a significant advantage over the classic Remote Config system. Unlike the traditional approach, which typically requires a minimum of 12 minutes for updates to propagate, Realtime Remote Config delivers changes instantaneously. This means that modifications to your app’s configuration are reflected immediately on users’ screens, without the need for manual refreshes or lengthy waiting periods.
This real-time capability enhances the responsiveness of your app, allowing for more dynamic and timely adjustments to user experiences, features, or content. It provides developers with greater control and flexibility in managing app configurations, enabling faster iteration and more agile responses to user needs or business requirements.

--

--