Update Feature Flags to use Firebase

This commit is contained in:
Lucas Lima 2021-01-03 12:06:02 -03:00 committed by Lucas Nunes
parent 460e92bee2
commit 543adc0d68
13 changed files with 126 additions and 39 deletions

View file

@ -488,8 +488,8 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
val isNotInstant = !instantAppManager.isEnabled(applicationContext) val isNotInstant = !instantAppManager.isEnabled(applicationContext)
findItem(R.id.share_now).isVisible = isNotInstant findItem(R.id.share_now).isVisible = isNotInstant
findItem(R.id.remove_ads).isVisible = !preferencesRepository.isPremiumEnabled() && isNotInstant findItem(R.id.remove_ads).isVisible = !preferencesRepository.isPremiumEnabled() && isNotInstant
findItem(R.id.previous_games).isVisible = featureFlagManager.isGameHistoryEnabled() findItem(R.id.previous_games).isVisible = featureFlagManager.isGameHistoryEnabled
findItem(R.id.rate).isVisible = featureFlagManager.isRateUsEnabled() findItem(R.id.rate).isVisible = featureFlagManager.isRateUsEnabled
if (!playGamesManager.hasGooglePlayGames()) { if (!playGamesManager.hasGooglePlayGames()) {
removeGroup(R.id.play_games_group) removeGroup(R.id.play_games_group)
@ -928,7 +928,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
} }
private fun refreshAds() { private fun refreshAds() {
if (featureFlagManager.isInAppAdsEnabled()) { if (featureFlagManager.isInAppAdsEnabled) {
val isTutorialComplete = preferencesRepository.isTutorialCompleted() val isTutorialComplete = preferencesRepository.isTutorialCompleted()
if (isTutorialComplete && !preferencesRepository.isPremiumEnabled() && billingManager.isEnabled()) { if (isTutorialComplete && !preferencesRepository.isPremiumEnabled() && billingManager.isEnabled()) {
if (!instantAppManager.isEnabled(this)) { if (!instantAppManager.isEnabled(this)) {

View file

@ -27,9 +27,13 @@ val ViewModelModule = module {
viewModel { ThemeViewModel(get(), get(), get(), get()) } viewModel { ThemeViewModel(get(), get(), get(), get()) }
viewModel { SplashViewModel(get(), get(), get(), get()) } viewModel { SplashViewModel(get(), get(), get(), get()) }
viewModel { viewModel {
GameViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) GameViewModel(
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()
)
} }
viewModel { viewModel {
TutorialViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) TutorialViewModel(
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()
)
} }
} }

View file

@ -8,6 +8,7 @@ import androidx.core.app.ActivityCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import dev.lucasnlm.antimine.GameActivity import dev.lucasnlm.antimine.GameActivity
import dev.lucasnlm.antimine.splash.viewmodel.SplashViewModel import dev.lucasnlm.antimine.splash.viewmodel.SplashViewModel
import dev.lucasnlm.external.IFeatureFlagManager
import dev.lucasnlm.external.IPlayGamesManager import dev.lucasnlm.external.IPlayGamesManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -16,6 +17,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
class SplashActivity : AppCompatActivity() { class SplashActivity : AppCompatActivity() {
private val playGamesManager: IPlayGamesManager by inject() private val playGamesManager: IPlayGamesManager by inject()
private val featureFlagManager: IFeatureFlagManager by inject()
private val splashViewMode: SplashViewModel by viewModel() private val splashViewMode: SplashViewModel by viewModel()
@ -24,6 +26,10 @@ class SplashActivity : AppCompatActivity() {
splashViewMode.startIap() splashViewMode.startIap()
lifecycleScope.launchWhenCreated {
featureFlagManager.refresh()
}
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
if (playGamesManager.hasGooglePlayGames()) { if (playGamesManager.hasGooglePlayGames()) {
var logged: Boolean var logged: Boolean

View file

@ -18,6 +18,7 @@ import dev.lucasnlm.antimine.preferences.IPreferencesRepository
import dev.lucasnlm.antimine.core.sound.ISoundManager import dev.lucasnlm.antimine.core.sound.ISoundManager
import dev.lucasnlm.antimine.ui.repository.IThemeRepository import dev.lucasnlm.antimine.ui.repository.IThemeRepository
import dev.lucasnlm.antimine.tutorial.view.TutorialField import dev.lucasnlm.antimine.tutorial.view.TutorialField
import dev.lucasnlm.external.IFeatureFlagManager
import dev.lucasnlm.external.IPlayGamesManager import dev.lucasnlm.external.IPlayGamesManager
import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -31,6 +32,7 @@ class TutorialViewModel(
minefieldRepository: IMinefieldRepository, minefieldRepository: IMinefieldRepository,
analyticsManager: IAnalyticsManager, analyticsManager: IAnalyticsManager,
playGamesManager: IPlayGamesManager, playGamesManager: IPlayGamesManager,
featureFlagManager: IFeatureFlagManager,
tipRepository: ITipRepository, tipRepository: ITipRepository,
private val clock: Clock, private val clock: Clock,
private val context: Context, private val context: Context,
@ -48,6 +50,7 @@ class TutorialViewModel(
analyticsManager, analyticsManager,
playGamesManager, playGamesManager,
tipRepository, tipRepository,
featureFlagManager,
clock, clock,
) { ) {
val tutorialState = MutableStateFlow( val tutorialState = MutableStateFlow(

View file

@ -95,7 +95,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="8dp" android:layout_marginVertical="8dp"
android:minHeight="66dp" android:minHeight="60dp"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" tools:visibility="visible"
tools:background="@color/accent" tools:background="@color/accent"

View file

@ -27,6 +27,7 @@ import dev.lucasnlm.antimine.preferences.models.GameControl
import dev.lucasnlm.antimine.ui.model.AppTheme import dev.lucasnlm.antimine.ui.model.AppTheme
import dev.lucasnlm.antimine.ui.repository.IThemeRepository import dev.lucasnlm.antimine.ui.repository.IThemeRepository
import dev.lucasnlm.external.Achievement import dev.lucasnlm.external.Achievement
import dev.lucasnlm.external.IFeatureFlagManager
import dev.lucasnlm.external.IPlayGamesManager import dev.lucasnlm.external.IPlayGamesManager
import dev.lucasnlm.external.Leaderboard import dev.lucasnlm.external.Leaderboard
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -48,6 +49,7 @@ open class GameViewModel(
private val analyticsManager: IAnalyticsManager, private val analyticsManager: IAnalyticsManager,
private val playGamesManager: IPlayGamesManager, private val playGamesManager: IPlayGamesManager,
private val tipRepository: ITipRepository, private val tipRepository: ITipRepository,
private val featureFlagManager: IFeatureFlagManager,
private val clock: Clock, private val clock: Clock,
) : ViewModel() { ) : ViewModel() {
val eventObserver = MutableLiveData<Event>() val eventObserver = MutableLiveData<Event>()
@ -308,21 +310,23 @@ open class GameViewModel(
} }
private fun onFeedbackAnalytics(action: ActionResponse, index: Int) { private fun onFeedbackAnalytics(action: ActionResponse, index: Int) {
when (action) { if (featureFlagManager.isGameplayAnalyticsEnabled) {
ActionResponse.OpenTile -> { when (action) {
analyticsManager.sentEvent(Analytics.OpenTile(index)) ActionResponse.OpenTile -> {
} analyticsManager.sentEvent(Analytics.OpenTile(index))
ActionResponse.SwitchMark -> { }
analyticsManager.sentEvent(Analytics.SwitchMark(index)) ActionResponse.SwitchMark -> {
} analyticsManager.sentEvent(Analytics.SwitchMark(index))
ActionResponse.HighlightNeighbors -> { }
analyticsManager.sentEvent(Analytics.HighlightNeighbors(index)) ActionResponse.HighlightNeighbors -> {
} analyticsManager.sentEvent(Analytics.HighlightNeighbors(index))
ActionResponse.OpenNeighbors -> { }
analyticsManager.sentEvent(Analytics.OpenNeighbors(index)) ActionResponse.OpenNeighbors -> {
} analyticsManager.sentEvent(Analytics.OpenNeighbors(index))
ActionResponse.OpenOrMark -> { }
analyticsManager.sentEvent(Analytics.OpenOrFlagTile(index)) ActionResponse.OpenOrMark -> {
analyticsManager.sentEvent(Analytics.OpenOrFlagTile(index))
}
} }
} }
} }

View file

@ -113,10 +113,6 @@ sealed class Analytics(
object UnlockIapDialog : Analytics("IAP Dialog Unlock") object UnlockIapDialog : Analytics("IAP Dialog Unlock")
object UnlockRewardedDialog : Analytics("Rewarded Dialog Unlock")
class ShowRatingRequest(usages: Int) : Analytics("Shown Rating Request", mapOf("Usages" to usages.toString()))
object TapRatingRequest : Analytics("Rating Request") object TapRatingRequest : Analytics("Rating Request")
object UseTip : Analytics("Use Tip") object UseTip : Analytics("Use Tip")

View file

@ -1,7 +1,10 @@
package dev.lucasnlm.external package dev.lucasnlm.external
interface IFeatureFlagManager { abstract class IFeatureFlagManager {
fun isGameHistoryEnabled(): Boolean abstract val isGameHistoryEnabled: Boolean
fun isRateUsEnabled(): Boolean abstract val isRateUsEnabled: Boolean
fun isInAppAdsEnabled(): Boolean abstract val isInAppAdsEnabled: Boolean
abstract val isGameplayAnalyticsEnabled: Boolean
abstract suspend fun refresh()
} }

View file

@ -1,7 +1,12 @@
package dev.lucasnlm.external package dev.lucasnlm.external
class FeatureFlagManager : IFeatureFlagManager { class FeatureFlagManager : IFeatureFlagManager() {
override fun isGameHistoryEnabled(): Boolean = true override val isGameHistoryEnabled: Boolean = true
override fun isRateUsEnabled(): Boolean = false override val isRateUsEnabled: Boolean = false
override fun isInAppAdsEnabled(): Boolean = false override val isInAppAdsEnabled: Boolean = false
override val isGameplayAnalyticsEnabled: Boolean = false
override suspend fun refresh() {
// No Feature Flags on FOSS
}
} }

View file

@ -50,7 +50,8 @@ dependencies {
// Firebase // Firebase
implementation 'com.google.firebase:firebase-analytics-ktx:18.0.0' implementation 'com.google.firebase:firebase-analytics-ktx:18.0.0'
implementation 'com.google.firebase:firebase-crashlytics:17.3.0' implementation 'com.google.firebase:firebase-crashlytics:17.3.0'
implementation 'com.google.firebase:firebase-firestore-ktx:22.0.0' implementation 'com.google.firebase:firebase-firestore-ktx:22.0.1'
implementation 'com.google.firebase:firebase-config-ktx:20.0.2'
// Kotlin // Kotlin
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'

View file

@ -1,7 +1,48 @@
package dev.lucasnlm.external package dev.lucasnlm.external
class FeatureFlagManager : IFeatureFlagManager { import com.google.android.gms.tasks.Tasks
override fun isGameHistoryEnabled(): Boolean = false import com.google.firebase.remoteconfig.FirebaseRemoteConfig
override fun isRateUsEnabled(): Boolean = true import kotlinx.coroutines.Dispatchers
override fun isInAppAdsEnabled(): Boolean = false import kotlinx.coroutines.withContext
class FeatureFlagManager : IFeatureFlagManager() {
private val remoteConfig: FirebaseRemoteConfig by lazy {
FirebaseRemoteConfig.getInstance().apply {
setDefaultsAsync(mapOf(
HISTORY_ENABLED to false,
RATE_US_ENABLED to true,
IN_APP_ADS_ENABLED to false,
GAMEPLAY_EVENTS_ENABLED to false,
))
}
}
override val isGameHistoryEnabled: Boolean by lazy {
remoteConfig.getBoolean(HISTORY_ENABLED)
}
override val isRateUsEnabled: Boolean by lazy {
remoteConfig.getBoolean(RATE_US_ENABLED)
}
override val isInAppAdsEnabled: Boolean by lazy {
remoteConfig.getBoolean(IN_APP_ADS_ENABLED)
}
override val isGameplayAnalyticsEnabled: Boolean by lazy {
remoteConfig.getBoolean(GAMEPLAY_EVENTS_ENABLED)
}
override suspend fun refresh() {
withContext(Dispatchers.IO) {
Tasks.await(remoteConfig.fetchAndActivate())
}
}
companion object {
private const val HISTORY_ENABLED = "history_enabled"
private const val RATE_US_ENABLED = "rate_us_enabled"
private const val IN_APP_ADS_ENABLED = "in_app_ads_enabled"
private const val GAMEPLAY_EVENTS_ENABLED = "gameplay_events_enabled"
}
} }

View file

@ -29,6 +29,30 @@ android {
} }
} }
signingConfigs {
release {
if (System.getenv('IS_RELEASE_BUILD')) {
storeFile file(System.getenv('KEYSTORE'))
keyAlias System.getenv('KEY_ALIAS')
storePassword System.getenv('KEY_STORE_PASSWORD')
keyPassword System.getenv('KEY_PASSWORD')
}
}
}
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
compileOptions { compileOptions {
targetCompatibility 1.8 targetCompatibility 1.8
sourceCompatibility 1.8 sourceCompatibility 1.8

View file

@ -6,6 +6,6 @@ import org.koin.dsl.module
val ViewModelModule = module { val ViewModelModule = module {
viewModel { viewModel {
GameViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) GameViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
} }
} }