Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FFs can now be toggled in release builds too #3101

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package io.element.android.appconfig

object OnBoardingConfig {
/** Whether the user can use QR code login. */
const val CAN_LOGIN_WITH_QR_CODE = false
const val CAN_LOGIN_WITH_QR_CODE = true

/** Whether the user can create an account using the app. */
const val CAN_CREATE_ACCOUNT = false
Expand Down
1 change: 1 addition & 0 deletions changelog.d/3073.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix feature flags not being able to be toggle in developer settings in release builds.
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ class MessagesPresenterTest {

private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
// Skip 2 item if Mentions feature is enabled, else 1
skipItems(if (FeatureFlags.Mentions.defaultValue) 2 else 1)
skipItems(if (FeatureFlags.Mentions.defaultValue(aBuildMeta())) 2 else 1)
return awaitItem()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID_3
import io.element.android.libraries.matrix.test.A_USER_ID_4
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
Expand Down Expand Up @@ -828,7 +829,6 @@ class MessageComposerPresenterTest {

// If room is a DM, `RoomMemberSuggestion.Room` is not returned
room.givenCanTriggerRoomNotification(Result.success(true))
room.isDirect
}
}

Expand Down Expand Up @@ -1299,7 +1299,7 @@ class MessageComposerPresenterTest {

private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
// Skip 2 item if Mentions feature is enabled, else 1
skipItems(if (FeatureFlags.Mentions.defaultValue) 2 else 1)
skipItems(if (FeatureFlags.Mentions.defaultValue(aBuildMeta())) 2 else 1)
return awaitItem()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,18 @@ class OnBoardingPresenterTest {

@Test
fun `present - initial state`() = runTest {
val buildMeta = aBuildMeta(
applicationName = "A",
productionApplicationName = "B",
desktopApplicationName = "C",
)
val featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.QrCodeLogin.key to true),
buildMeta = buildMeta,
)
val presenter = OnBoardingPresenter(
buildMeta = aBuildMeta(
applicationName = "A",
productionApplicationName = "B",
desktopApplicationName = "C",
),
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.QrCodeLogin.name to true)),
buildMeta = buildMeta,
featureFlagService = featureFlagService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
Expand Down
2 changes: 2 additions & 0 deletions libraries/featureflag/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@ android {
}

dependencies {
implementation(projects.appconfig)
implementation(projects.libraries.core)
implementation(libs.coroutines.core)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package io.element.android.libraries.featureflag.api

import io.element.android.libraries.core.meta.BuildMeta

interface Feature {
/**
* Unique key to identify the feature.
Expand All @@ -33,9 +35,9 @@ interface Feature {
val description: String?

/**
* The default value of the feature (enabled or disabled).
* Calculate the default value of the feature (enabled or disabled) given a [BuildMeta].
*/
val defaultValue: Boolean
val defaultValue: (BuildMeta) -> Boolean

/**
* Whether the feature is finished or not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

package io.element.android.libraries.featureflag.api

import io.element.android.appconfig.OnBoardingConfig
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.meta.BuildType

/**
* To enable or disable a FeatureFlags, change the `defaultValue` value.
* Warning: to enable a flag for the release app, you MUST update the file
Expand All @@ -25,82 +29,88 @@ enum class FeatureFlags(
override val key: String,
override val title: String,
override val description: String? = null,
override val defaultValue: Boolean,
override val defaultValue: (BuildMeta) -> Boolean,
override val isFinished: Boolean,
) : Feature {
LocationSharing(
key = "feature.locationsharing",
title = "Allow user to share location",
defaultValue = true,
defaultValue = { true },
isFinished = true,
),
Polls(
key = "feature.polls",
title = "Polls",
description = "Create poll and render poll events in the timeline",
defaultValue = true,
defaultValue = { true },
isFinished = true,
),
NotificationSettings(
key = "feature.notificationsettings",
title = "Show notification settings",
defaultValue = true,
defaultValue = { true },
isFinished = true,
),
VoiceMessages(
key = "feature.voicemessages",
title = "Voice messages",
description = "Send and receive voice messages",
defaultValue = true,
defaultValue = { true },
isFinished = true,
),
PinUnlock(
key = "feature.pinunlock",
title = "Pin unlock",
description = "Allow user to lock/unlock the app with a pin code or biometrics",
defaultValue = true,
defaultValue = { true },
isFinished = true,
),
Mentions(
key = "feature.mentions",
title = "Mentions",
description = "Type `@` to get mention suggestions and insert them",
defaultValue = true,
defaultValue = { true },
isFinished = false,
),
MarkAsUnread(
key = "feature.markAsUnread",
title = "Mark as unread",
description = "Allow user to mark a room as unread",
defaultValue = true,
defaultValue = { true },
isFinished = false,
),
RoomDirectorySearch(
key = "feature.roomdirectorysearch",
title = "Room directory search",
description = "Allow user to search for public rooms in their homeserver",
defaultValue = false,
defaultValue = { false },
isFinished = false,
),
ShowBlockedUsersDetails(
key = "feature.showBlockedUsersDetails",
title = "Show blocked users details",
description = "Show the name and avatar of blocked users in the blocked users list",
defaultValue = false,
defaultValue = { false },
isFinished = false,
),
QrCodeLogin(
key = "feature.qrCodeLogin",
title = "Enable login using QR code",
description = "Allow the user to login using the QR code flow",
defaultValue = true,
defaultValue = { buildMeta ->
when (buildMeta.buildType) {
// TODO remove once the feature is ready to publish
BuildType.RELEASE -> false
else -> OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE
}
},
isFinished = false,
),
IncomingShare(
key = "feature.incomingShare",
title = "Incoming Share support",
description = "Allow the application to receive data from other applications",
defaultValue = true,
defaultValue = { true },
isFinished = false,
),
}
1 change: 1 addition & 0 deletions libraries/featureflag/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ dependencies {
testImplementation(libs.coroutines.test)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.element.android.libraries.featureflag.impl

import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.featureflag.api.Feature
Expand All @@ -28,14 +29,15 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class)
@SingleIn(AppScope::class)
class DefaultFeatureFlagService @Inject constructor(
private val providers: Set<@JvmSuppressWildcards FeatureFlagProvider>
private val providers: Set<@JvmSuppressWildcards FeatureFlagProvider>,
private val buildMeta: BuildMeta,
) : FeatureFlagService {
override fun isFeatureEnabledFlow(feature: Feature): Flow<Boolean> {
return providers.filter { it.hasFeature(feature) }
.sortedByDescending(FeatureFlagProvider::priority)
.firstOrNull()
?.isFeatureEnabledFlow(feature)
?: flowOf(feature.defaultValue)
?: flowOf(feature.defaultValue(buildMeta))
}

override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.featureflag.api.Feature
import kotlinx.coroutines.flow.Flow
Expand All @@ -34,7 +35,10 @@ private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(na
/**
* Note: this will be used only in the nightly and in the debug build.
*/
class PreferencesFeatureFlagProvider @Inject constructor(@ApplicationContext context: Context) : MutableFeatureFlagProvider {
class PreferencesFeatureFlagProvider @Inject constructor(
@ApplicationContext context: Context,
private val buildMeta: BuildMeta,
) : MutableFeatureFlagProvider {
private val store = context.dataStore

override val priority = MEDIUM_PRIORITY
Expand All @@ -47,7 +51,7 @@ class PreferencesFeatureFlagProvider @Inject constructor(@ApplicationContext con

override fun isFeatureEnabledFlow(feature: Feature): Flow<Boolean> {
return store.data.map { prefs ->
prefs[booleanPreferencesKey(feature.key)] ?: feature.defaultValue
prefs[booleanPreferencesKey(feature.key)] ?: feature.defaultValue(buildMeta)
}.distinctUntilChanged()
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
import io.element.android.libraries.core.meta.BuildType
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.featureflag.impl.FeatureFlagProvider
import io.element.android.libraries.featureflag.impl.PreferencesFeatureFlagProvider
import io.element.android.libraries.featureflag.impl.StaticFeatureFlagProvider

@Module
@ContributesTo(AppScope::class)
Expand All @@ -33,20 +31,10 @@ object FeatureFlagModule {
@Provides
@ElementsIntoSet
fun providesFeatureFlagProvider(
buildType: BuildType,
mutableFeatureFlagProvider: PreferencesFeatureFlagProvider,
staticFeatureFlagProvider: StaticFeatureFlagProvider,
): Set<FeatureFlagProvider> {
val providers = HashSet<FeatureFlagProvider>()
when (buildType) {
BuildType.RELEASE -> {
providers.add(staticFeatureFlagProvider)
}
BuildType.NIGHTLY,
BuildType.DEBUG -> {
providers.add(mutableFeatureFlagProvider)
}
return buildSet {
add(mutableFeatureFlagProvider)
}
return providers
}
}
Loading
Loading