├── samples ├── android-java │ ├── app │ │ ├── .gitignore │ │ ├── src │ │ │ └── main │ │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ ├── dimens.xml │ │ │ │ │ ├── strings.xml │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── themes.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── layout │ │ │ │ │ └── activity_main.xml │ │ │ │ └── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ ├── assets │ │ │ │ └── logback.xml │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── configcat │ │ │ │ └── configcatsample │ │ │ │ └── MainActivity.java │ │ └── build.gradle │ ├── settings.gradle │ ├── .gitignore │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── build.gradle │ ├── gradle.properties │ └── gradlew.bat └── android-kotlin │ ├── app │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── layout │ │ │ │ │ └── activity_main.xml │ │ │ │ └── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── configcat │ │ │ │ └── configcatsample │ │ │ │ └── MainActivity.kt │ │ └── android-logger.properties │ ├── proguard-rules.pro │ └── build.gradle │ ├── settings.gradle │ ├── .gitignore │ ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── build.gradle │ └── gradlew.bat ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── stale.yml │ ├── scheduled-test.yml │ ├── test.yml │ └── android-ci.yml ├── gradle.properties ├── settings.gradle ├── src ├── test │ ├── resources │ │ ├── specialCharacters.txt │ │ ├── evaluation │ │ │ ├── simple_value │ │ │ │ ├── on_flag.txt │ │ │ │ ├── int_setting.txt │ │ │ │ ├── off_flag.txt │ │ │ │ ├── text_setting.txt │ │ │ │ └── double_setting.txt │ │ │ ├── 1_targeting_rule │ │ │ │ ├── 1_rule_not_matching_targeted_attribute.txt │ │ │ │ ├── 1_rule_matching_targeted_attribute.txt │ │ │ │ ├── 1_rule_no_user.txt │ │ │ │ └── 1_rule_no_targeted_attribute.txt │ │ │ ├── options_after_targeting_rule │ │ │ │ ├── options_after_targeting_rule_matching_targeted_attribute.txt │ │ │ │ ├── options_after_targeting_rule_not_matching_targeted_attribute.txt │ │ │ │ ├── options_after_targeting_rule_no_user.txt │ │ │ │ └── options_after_targeting_rule_no_targeted_attribute.txt │ │ │ ├── number_validation.json │ │ │ ├── list_truncation.json │ │ │ ├── options_within_targeting_rule │ │ │ │ ├── options_within_targeting_rule_not_matching_targeted_attribute.txt │ │ │ │ ├── options_within_targeting_rule_matching_targeted_attribute_options_attribute.txt │ │ │ │ ├── options_within_targeting_rule_no_user.txt │ │ │ │ ├── options_within_targeting_rule_no_targeted_attribute.txt │ │ │ │ └── options_within_targeting_rule_matching_targeted_attribute_no_options_attribute.txt │ │ │ ├── options_based_on_user_id │ │ │ │ ├── options_user_attribute_user.txt │ │ │ │ └── options_user_attribute_no_user.txt │ │ │ ├── epoch_date_validation.json │ │ │ ├── options_based_on_custom_attr │ │ │ │ ├── matching_options_custom_attribute.txt │ │ │ │ ├── no_options_custom_attribute.txt │ │ │ │ └── options_custom_attribute_no_user.txt │ │ │ ├── and_rules │ │ │ │ ├── and_rules_user.txt │ │ │ │ └── and_rules_no_user.txt │ │ │ ├── comparators.json │ │ │ ├── and_rules.json │ │ │ ├── list_truncation │ │ │ │ ├── list_truncation.txt │ │ │ │ └── test_list_truncation.json │ │ │ ├── segment │ │ │ │ ├── segment_matching.txt │ │ │ │ ├── segment_no_matching.txt │ │ │ │ ├── segment_no_user.txt │ │ │ │ ├── segment_no_user_multi_conditions.txt │ │ │ │ └── segment_no_targeted_attribute.txt │ │ │ ├── options_based_on_user_id.json │ │ │ ├── semver_validation.json │ │ │ ├── number_validation │ │ │ │ └── number_error.txt │ │ │ ├── 2_targeting_rules │ │ │ │ ├── 2_rules_not_matching_targeted_attribute.txt │ │ │ │ ├── 2_rules_matching_targeted_attribute.txt │ │ │ │ ├── 2_rules_no_user.txt │ │ │ │ └── 2_rules_no_targeted_attribute.txt │ │ │ ├── epoch_date_validation │ │ │ │ └── date_error.txt │ │ │ ├── options_based_on_custom_attr.json │ │ │ ├── simple_value.json │ │ │ ├── 2_targeting_rules.json │ │ │ ├── 1_targeting_rule.json │ │ │ ├── prerequisite_flag │ │ │ │ ├── prerequisite_flag_no_user_needed_by_dep.txt │ │ │ │ ├── prerequisite_flag_multilevel.txt │ │ │ │ ├── prerequisite_flag_no_user_needed_by_prereq.txt │ │ │ │ ├── prerequisite_flag.txt │ │ │ │ └── prerequisite_flag_no_user_needed_by_both.txt │ │ │ ├── prerequisite_flag.json │ │ │ ├── options_after_targeting_rule.json │ │ │ ├── segment.json │ │ │ ├── semver_validation │ │ │ │ ├── semver_error.txt │ │ │ │ └── semver_relations_error.txt │ │ │ ├── options_within_targeting_rule.json │ │ │ └── comparators │ │ │ │ └── allinone.txt │ │ ├── matrix │ │ │ ├── testmatrix_sensitive.csv │ │ │ ├── testmatrix_segment.csv │ │ │ ├── testmatrix_variationId.csv │ │ │ ├── testmatrix_number.csv │ │ │ ├── testmatrix_segments_old.csv │ │ │ ├── testmatrix_prerequisite_flag.csv │ │ │ ├── testmatrix_and_or.csv │ │ │ ├── testmatrix_unicode.csv │ │ │ ├── testmatrix_semantic_2.csv │ │ │ └── testmatrix_semantic.csv │ │ └── test_circulardependency.json │ ├── .DS_Store │ └── java │ │ └── com │ │ └── configcat │ │ ├── ClassPathResourceOverrideDataSource.java │ │ ├── TestCache.java │ │ ├── UserTests.java │ │ ├── FormattableLogMessageTests.java │ │ ├── Helpers.java │ │ ├── EvaluationLoggerTurnOffTest.java │ │ └── EntrySerializationTest.java ├── .DS_Store └── main │ ├── java │ └── com │ │ └── configcat │ │ ├── PollingMode.java │ │ ├── ConditionAccessor.java │ │ ├── NullConfigCache.java │ │ ├── DataGovernance.java │ │ ├── LogLevel.java │ │ ├── Preferences.java │ │ ├── SettingType.java │ │ ├── RefreshResult.java │ │ ├── Segment.java │ │ ├── ClientCacheState.java │ │ ├── FormattableLogMessageWithUserCondition.java │ │ ├── SegmentCondition.java │ │ ├── OverrideDataSource.java │ │ ├── Condition.java │ │ ├── LogFilterFunction.java │ │ ├── SharedPreferencesCache.java │ │ ├── ConfigCache.java │ │ ├── DateTimeUtils.java │ │ ├── PercentageOption.java │ │ ├── SegmentComparator.java │ │ ├── PrerequisiteComparator.java │ │ ├── FormattableLogMessage.java │ │ ├── FormattableLogMessageWithKeySet.java │ │ ├── OverrideBehaviour.java │ │ ├── PrerequisiteFlagCondition.java │ │ ├── Config.java │ │ ├── EvaluationContext.java │ │ ├── TargetingRule.java │ │ ├── LocalMapDataSource.java │ │ ├── UserCondition.java │ │ ├── Setting.java │ │ ├── SettingValue.java │ │ ├── Entry.java │ │ ├── ConfigCatLogger.java │ │ ├── Utils.java │ │ ├── UserAttributeConverter.java │ │ ├── EvaluationDetails.java │ │ └── PollingModes.java │ └── AndroidManifest.xml ├── .DS_Store ├── .vscode └── settings.json ├── media └── readme02-3.png ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── CHANGELOG.md ├── .gitignore ├── configcat-proguard-rules.pro ├── LICENSE ├── DEPLOY.md ├── CONTRIBUTING.md └── gradlew.bat /samples/android-java/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @configcat/developers 2 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /samples/android-kotlin/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=10.4.3 2 | 3 | org.gradle.jvmargs=-Xmx2g -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'configcat-android-client' 2 | -------------------------------------------------------------------------------- /src/test/resources/specialCharacters.txt: -------------------------------------------------------------------------------- 1 | äöüÄÖÜçéèñışğ⢙✓😀 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /src/test/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/src/test/.DS_Store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic" 3 | } -------------------------------------------------------------------------------- /media/readme02-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/media/readme02-3.png -------------------------------------------------------------------------------- /samples/android-java/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "ConfigCat Android Java Sample" 2 | include ':app' 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/evaluation/simple_value/on_flag.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'boolDefaultTrue' 2 | Returning 'true'. 3 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/simple_value/int_setting.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'integerDefaultOne' 2 | Returning '1'. 3 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/simple_value/off_flag.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'boolDefaultFalse' 2 | Returning 'false'. 3 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/simple_value/text_setting.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'stringDefaultCat' 2 | Returning 'Cat'. 3 | -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/simple_value/double_setting.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'doubleDefaultPi' 2 | Returning '3.1415'. 3 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ConfigCatSample 3 | 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Please check the [Github Releases](https://github.com/configcat/android-sdk/releases) page for the changelog of the ConfigCat Android SDK. -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-java/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-java/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-java/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-java/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | enable-beta-ecosystems: true 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | -------------------------------------------------------------------------------- /samples/android-java/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-java/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /samples/android-kotlin/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-java/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-java/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-java/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-java/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-java/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/configcat/android-sdk/HEAD/samples/android-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ConfigCat Android Java Sample 3 | MainActivity 4 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/PollingMode.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * The base class of a polling mode configuration. 5 | */ 6 | public interface PollingMode { 7 | String getPollingIdentifier(); 8 | } 9 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/android-logger.properties: -------------------------------------------------------------------------------- 1 | root=ERROR:MyApplication 2 | # DEBUG (and higher) messages from classes of com.example.database 3 | # will be logged with "MyApplication-Database" tag 4 | logger.com.configcat=DEBUG -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 1 * * *' 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | stale: 11 | uses: configcat/.github/.github/workflows/stale.yml@master 12 | secrets: inherit 13 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Aug 04 20:00:01 CEST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/ConditionAccessor.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | interface ConditionAccessor { 4 | UserCondition getUserCondition(); 5 | 6 | SegmentCondition getSegmentCondition(); 7 | 8 | PrerequisiteFlagCondition getPrerequisiteFlagCondition(); 9 | } 10 | -------------------------------------------------------------------------------- /samples/android-java/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 05 11:38:37 CET 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /samples/android-kotlin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Sep 22 18:23:09 CEST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /src/test/resources/matrix/testmatrix_sensitive.csv: -------------------------------------------------------------------------------- 1 | Identifier;Email;Country;Custom1;isOneOfSensitive;isNotOneOfSensitive 2 | ##null##;;;;ToAll;ToAll 3 | id1;macska@example.com;;;Macska;Kigyo 4 | Kutya;;;;Allat;ToAll 5 | Sas;;;;ToAll;Kigyo 6 | Kutya;macska@example.com;;;Macska;ToAll 7 | id1;;Scotland;;Britt;Kigyo 8 | Macska;;USA;;ToAll;Ireland -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/1_targeting_rule/1_rule_not_matching_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'stringContainsDogDefaultCat' for User '{"Identifier":"12345","Email":"joe@example.com"}' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN 'Dog' => no match 4 | Returning 'Cat'. 5 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/1_targeting_rule/1_rule_matching_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'stringContainsDogDefaultCat' for User '{"Identifier":"12345","Email":"joe@configcat.com"}' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN 'Dog' => MATCH, applying rule 4 | Returning 'Dog'. 5 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/NullConfigCache.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * A null cache implementation. 5 | */ 6 | class NullConfigCache extends ConfigCache { 7 | 8 | @Override 9 | protected String read(String key) { 10 | return null; 11 | } 12 | 13 | @Override 14 | protected void write(String key, String value) { /* do nothing */ } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_after_targeting_rule/options_after_targeting_rule_matching_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'integer25One25Two25Three25FourAdvancedRules' for User '{"Identifier":"12345","Email":"joe@configcat.com"}' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN '5' => MATCH, applying rule 4 | Returning '5'. 5 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/number_validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/uGyK3q9_ckmdxRyI7vjwCw", 3 | "tests": [ 4 | { 5 | "key": "number", 6 | "defaultValue": "default", 7 | "returnValue": "Default", 8 | "expectedLog": "number_error.txt", 9 | "user": { 10 | "Identifier": "12345", 11 | "Custom1": "not_a_number" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/list_truncation.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonOverride": "test_list_truncation.json", 3 | "sdkKey": "configcat-sdk-test-key/0000000000000000000001", 4 | "tests": [ 5 | { 6 | "key": "booleanKey1", 7 | "defaultValue": false, 8 | "user": { 9 | "Identifier": "12" 10 | }, 11 | "returnValue": true, 12 | "expectedLog": "list_truncation.txt" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_within_targeting_rule/options_within_targeting_rule_not_matching_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' for User '{"Identifier":"12345","Email":"joe@example.com"}' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN % options => no match 4 | Returning 'Cat'. 5 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_based_on_user_id/options_user_attribute_user.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'string75Cat0Dog25Falcon0Horse' for User '{"Identifier":"12345"}' 2 | Evaluating % options based on the User.Identifier attribute: 3 | - Computing hash in the [0..99] range from User.Identifier => 21 (this value is sticky and consistent across all SDKs) 4 | - Hash value 21 selects % option 1 (75%), 'Cat'. 5 | Returning 'Cat'. 6 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/epoch_date_validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", 3 | "tests": [ 4 | { 5 | "key": "boolTrueIn202304", 6 | "defaultValue": true, 7 | "returnValue": false, 8 | "expectedLog": "date_error.txt", 9 | "user": { 10 | "Identifier": "12345", 11 | "Custom1": "2023.04.10" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_based_on_custom_attr/matching_options_custom_attribute.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'string75Cat0Dog25Falcon0HorseCustomAttr' for User '{"Identifier":"12345","Country":"US"}' 2 | Evaluating % options based on the User.Country attribute: 3 | - Computing hash in the [0..99] range from User.Country => 70 (this value is sticky and consistent across all SDKs) 4 | - Hash value 70 selects % option 1 (75%), 'Cat'. 5 | Returning 'Cat'. 6 | -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/DataGovernance.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * Describes the location of your feature flag and setting data within the ConfigCat CDN. 5 | */ 6 | public enum DataGovernance { 7 | /** 8 | * Select this if your feature flags are published to all global CDN nodes. 9 | */ 10 | GLOBAL, 11 | /** 12 | * Select this if your feature flags are published to CDN nodes only in the EU. 13 | */ 14 | EU_ONLY 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/LogLevel.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * Describes the log level. 5 | */ 6 | public enum LogLevel { 7 | /** 8 | * Debug level. 9 | */ 10 | DEBUG, 11 | /** 12 | * Info level. 13 | */ 14 | INFO, 15 | /** 16 | * Warning level. 17 | */ 18 | WARNING, 19 | /** 20 | * Error level. 21 | */ 22 | ERROR, 23 | /** 24 | * Turns logging off. 25 | */ 26 | NO_LOG, 27 | } 28 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/and_rules/and_rules_user.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'emailAnd' for User '{"Identifier":"12345","Email":"jane@configcat.com"}' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF User.Email STARTS WITH ANY OF [<1 hashed value>] => true 4 | AND User.Email CONTAINS ANY OF ['@'] => true 5 | AND User.Email ENDS WITH ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions 6 | THEN 'Dog' => no match 7 | Returning 'Cat'. 8 | -------------------------------------------------------------------------------- /src/test/resources/matrix/testmatrix_segment.csv: -------------------------------------------------------------------------------- 1 | Identifier;Email;Country;Custom1;developerAndBetaUserSegment;developerAndBetaUserCleartextSegment;notDeveloperAndNotBetaUserSegment;notDeveloperAndNotBetaUserCleartextSegment 2 | ##null##;;;;False;False;False;False 3 | ;;;;False;False;False;False 4 | john@example.com;john@example.com;##null##;##null##;False;False;False;False 5 | jane@example.com;jane@example.com;##null##;##null##;False;False;False;False 6 | kate@example.com;kate@example.com;##null##;##null##;True;True;True;True 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | bin 5 | # Compiled class file 6 | *.class 7 | 8 | # Log file 9 | *.log 10 | 11 | # BlueJ files 12 | *.ctxt 13 | 14 | # Mobile Tools for Java (J2ME) 15 | .mtj.tmp/ 16 | 17 | # Package Files # 18 | *.jar 19 | *.war 20 | *.ear 21 | *.zip 22 | *.tar.gz 23 | *.rar 24 | 25 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 26 | hs_err_pid* 27 | 28 | **/.idea/* 29 | 30 | **/.gradle/* 31 | **/build/* 32 | out/ 33 | 34 | .DS_Store 35 | local.properties 36 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_based_on_user_id/options_user_attribute_no_user.txt: -------------------------------------------------------------------------------- 1 | WARNING [3001] Cannot evaluate targeting rules and % options for setting 'string75Cat0Dog25Falcon0Horse' (User Object is missing). You should pass a User Object to the evaluation methods like `getValue()`/`getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'string75Cat0Dog25Falcon0Horse' 3 | Skipping % options because the User Object is missing. 4 | Returning 'Chicken'. 5 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/comparators.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", 3 | "tests": [ 4 | { 5 | "key": "allinone", 6 | "defaultValue": "", 7 | "user": { 8 | "Identifier": "12345", 9 | "Email": "joe@example.com", 10 | "Country": "[\"USA\"]", 11 | "Version": "1.0.0", 12 | "Number": "1.0", 13 | "Date": "1693497500" 14 | }, 15 | "returnValue": "default", 16 | "expectedLog": "allinone.txt" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_based_on_custom_attr/no_options_custom_attribute.txt: -------------------------------------------------------------------------------- 1 | WARNING [3003] Cannot evaluate % options for setting 'string75Cat0Dog25Falcon0HorseCustomAttr' (the User.Country attribute is missing). You should set the User.Country attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'string75Cat0Dog25Falcon0HorseCustomAttr' for User '{"Identifier":"12345"}' 3 | Skipping % options because the User.Country attribute is missing. 4 | Returning 'Chicken'. 5 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_based_on_custom_attr/options_custom_attribute_no_user.txt: -------------------------------------------------------------------------------- 1 | WARNING [3001] Cannot evaluate targeting rules and % options for setting 'string75Cat0Dog25Falcon0HorseCustomAttr' (User Object is missing). You should pass a User Object to the evaluation methods like `getValue()`/`getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'string75Cat0Dog25Falcon0HorseCustomAttr' 3 | Skipping % options because the User Object is missing. 4 | Returning 'Chicken'. 5 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/Preferences.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | class Preferences { 6 | @SerializedName(value = "u") 7 | private String baseUrl; 8 | @SerializedName(value = "r") 9 | private int redirect; 10 | @SerializedName(value = "s") 11 | private String salt; 12 | 13 | public String getBaseUrl() { 14 | return baseUrl; 15 | } 16 | 17 | public int getRedirect() { 18 | return redirect; 19 | } 20 | 21 | public String getSalt() { 22 | return salt; 23 | } 24 | } -------------------------------------------------------------------------------- /src/test/resources/evaluation/and_rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/ByMO9yZNn02kXcm72lnY1A", 3 | "tests": [ 4 | { 5 | "key": "emailAnd", 6 | "defaultValue": "default", 7 | "returnValue": "Cat", 8 | "expectedLog": "and_rules_no_user.txt" 9 | }, 10 | { 11 | "key": "emailAnd", 12 | "defaultValue": "default", 13 | "user": { 14 | "Identifier": "12345", 15 | "Email": "jane@configcat.com" 16 | }, 17 | "returnValue": "Cat", 18 | "expectedLog": "and_rules_user.txt" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/list_truncation/list_truncation.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'booleanKey1' for User '{"Identifier":"12"}' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF User.Identifier CONTAINS ANY OF ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] => true 4 | AND User.Identifier CONTAINS ANY OF ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', ... <1 more value>] => true 5 | AND User.Identifier CONTAINS ANY OF ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', ... <2 more values>] => true 6 | THEN 'true' => MATCH, applying rule 7 | Returning 'true'. 8 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/segment/segment_matching.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'featureWithSegmentTargeting' for User '{"Identifier":"12345","Email":"jane@example.com"}' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF User IS IN SEGMENT 'Beta users' 4 | ( 5 | Evaluating segment 'Beta users': 6 | - IF User.Email IS ONE OF [<2 hashed values>] => true 7 | Segment evaluation result: User IS IN SEGMENT. 8 | Condition (User IS IN SEGMENT 'Beta users') evaluates to true. 9 | ) 10 | THEN 'true' => MATCH, applying rule 11 | Returning 'true'. 12 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/segment/segment_no_matching.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'featureWithNegatedSegmentTargeting' for User '{"Identifier":"12345","Email":"jane@example.com"}' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF User IS NOT IN SEGMENT 'Beta users' 4 | ( 5 | Evaluating segment 'Beta users': 6 | - IF User.Email IS ONE OF [<2 hashed values>] => true 7 | Segment evaluation result: User IS IN SEGMENT. 8 | Condition (User IS NOT IN SEGMENT 'Beta users') evaluates to false. 9 | ) 10 | THEN 'true' => no match 11 | Returning 'false'. 12 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_based_on_user_id.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A", 3 | "tests": [ 4 | { 5 | "key": "string75Cat0Dog25Falcon0Horse", 6 | "defaultValue": "default", 7 | "returnValue": "Chicken", 8 | "expectedLog": "options_user_attribute_no_user.txt" 9 | }, 10 | { 11 | "key": "string75Cat0Dog25Falcon0Horse", 12 | "defaultValue": "default", 13 | "user": { 14 | "Identifier": "12345" 15 | }, 16 | "returnValue": "Cat", 17 | "expectedLog": "options_user_attribute_user.txt" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/SettingType.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Setting type. 7 | */ 8 | public enum SettingType { 9 | /** 10 | * On/off type (feature flag). 11 | */ 12 | @SerializedName("0") 13 | BOOLEAN, 14 | 15 | /** 16 | * Text type. 17 | */ 18 | @SerializedName("1") 19 | STRING, 20 | 21 | /** 22 | * Whole number type. 23 | */ 24 | @SerializedName("2") 25 | INT, 26 | 27 | /** 28 | * Decimal number type. 29 | */ 30 | @SerializedName("3") 31 | DOUBLE, 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/RefreshResult.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * Represents the result of a forceRefresh() call. 5 | */ 6 | public class RefreshResult { 7 | private final boolean success; 8 | private final Object error; 9 | 10 | RefreshResult(boolean success, Object error) { 11 | this.success = success; 12 | this.error = error; 13 | } 14 | 15 | public boolean isSuccess() { 16 | return success; 17 | } 18 | 19 | public String error() { 20 | if(error != null) { 21 | return error.toString(); 22 | } 23 | return null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_after_targeting_rule/options_after_targeting_rule_not_matching_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'integer25One25Two25Three25FourAdvancedRules' for User '{"Identifier":"12345","Email":"joe@example.com"}' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN '5' => no match 4 | Evaluating % options based on the User.Identifier attribute: 5 | - Computing hash in the [0..99] range from User.Identifier => 25 (this value is sticky and consistent across all SDKs) 6 | - Hash value 25 selects % option 2 (25%), '2'. 7 | Returning '2'. 8 | -------------------------------------------------------------------------------- /src/test/resources/matrix/testmatrix_variationId.csv: -------------------------------------------------------------------------------- 1 | Identifier;Email;Country;Custom1;boolean;decimal;text;whole 2 | ##null##;;;;a0e56eda;63612d39;3f05be89;cf2e9162; 3 | a@configcat.com;a@configcat.com;Hungary;admin;67787ae4;8f9559cf;9bdc6a1f;ab30533b; 4 | b@configcat.com;b@configcat.com;Hungary;admin;67787ae4;8f9559cf;9bdc6a1f;ab30533b; 5 | a@test.com;a@test.com;Hungary;admin;67787ae4;d66c5781;65310deb;ec14f6a9; 6 | b@test.com;b@test.com;Hungary;admin;a0e56eda;d66c5781;65310deb;ec14f6a9; 7 | cliffordj@aol.com;cliffordj@aol.com;Hungary;admin;67787ae4;8155ad7b;cf19e913;ec14f6a9; 8 | bryanw@verizon.net;bryanw@verizon.net;Hungary;;a0e56eda;d0dbc27f;30ba32b9;61a5a033; 9 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/segment/segment_no_user.txt: -------------------------------------------------------------------------------- 1 | WARNING [3001] Cannot evaluate targeting rules and % options for setting 'featureWithSegmentTargeting' (User Object is missing). You should pass a User Object to the evaluation methods like `getValue()`/`getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'featureWithSegmentTargeting' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User IS IN SEGMENT 'Beta users' THEN 'true' => cannot evaluate, User Object is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | Returning 'false'. 7 | -------------------------------------------------------------------------------- /src/test/resources/matrix/testmatrix_number.csv: -------------------------------------------------------------------------------- 1 | Identifier;Email;Country;Custom1;numberWithPercentage;number 2 | ##null##;;;;Default;Default 3 | id1;;;0;<2.1;<>5 4 | id1;;;0.0;<2.1;<>5 5 | id1;;;0,0;<2.1;<>5 6 | id1;;;0.2;<2.1;<>5 7 | id2;;;0,2;<2.1;<>5 8 | id3;;;1;<2.1;<>5 9 | id4;;;1.0;<2.1;<>5 10 | id5;;;1,0;<2.1;<>5 11 | id6;;;1.5;<2.1;<>5 12 | id7;;;1,5;<2.1;<>5 13 | id8;;;2.1;<=2,1;<>5 14 | id9;;;2,1;<=2,1;<>5 15 | id10;;;3.50;=3.5;<>5 16 | id11;;;3,50;=3.5;<>5 17 | id12;;;5;>=5;Default 18 | id13;;;5.0;>=5;Default 19 | id14;;;5,0;>=5;Default 20 | id13;;;5.76;>5;<>5 21 | id14;;;5,76;>5;<>5 22 | id15;;;4;<>4.2;<>5 23 | id16;;;4.0;<>4.2;<>5 24 | id17;;;4,0;<>4.2;<>5 25 | id18;;;4.2;80%;<>5 26 | id19;;;4,2;20%;<>5 -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_within_targeting_rule/options_within_targeting_rule_matching_targeted_attribute_options_attribute.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' for User '{"Identifier":"12345","Email":"joe@configcat.com","Country":"US"}' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN % options => MATCH, applying rule 4 | Evaluating % options based on the User.Country attribute: 5 | - Computing hash in the [0..99] range from User.Country => 63 (this value is sticky and consistent across all SDKs) 6 | - Hash value 63 selects % option 1 (75%), 'Cat'. 7 | Returning 'Cat'. 8 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/semver_validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/BAr3KgLTP0ObzKnBTo5nhA", 3 | "tests": [ 4 | { 5 | "key": "isNotOneOf", 6 | "defaultValue": "default", 7 | "returnValue": "Default", 8 | "expectedLog": "semver_error.txt", 9 | "user": { 10 | "Identifier": "12345", 11 | "Custom1": "wrong_semver" 12 | } 13 | }, 14 | { 15 | "key": "relations", 16 | "defaultValue": "default", 17 | "returnValue": "Default", 18 | "expectedLog": "semver_relations_error.txt", 19 | "user": { 20 | "Identifier": "12345", 21 | "Custom1": "wrong_semver" 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/number_validation/number_error.txt: -------------------------------------------------------------------------------- 1 | WARNING [3004] Cannot evaluate condition (User.Custom1 != '5') for setting 'number' ('not_a_number' is not a valid decimal number). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. 2 | INFO [5000] Evaluating 'number' for User '{"Identifier":"12345","Custom1":"not_a_number"}' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Custom1 != '5' THEN '<>5' => cannot evaluate, the User.Custom1 attribute is invalid ('not_a_number' is not a valid decimal number) 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | Returning 'Default'. 7 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/Segment.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * ConfigCat segment. 7 | */ 8 | public class Segment { 9 | 10 | @SerializedName(value = "n") 11 | private String name; 12 | @SerializedName(value = "r") 13 | private UserCondition[] segmentRules; 14 | 15 | /** 16 | * The name of the segment. 17 | */ 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | /** 23 | * The list of segment rule conditions (where there is a logical AND relation between the items). 24 | */ 25 | public UserCondition[] getSegmentRules() { 26 | return segmentRules; 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/resources/evaluation/1_targeting_rule/1_rule_no_user.txt: -------------------------------------------------------------------------------- 1 | WARNING [3001] Cannot evaluate targeting rules and % options for setting 'stringContainsDogDefaultCat' (User Object is missing). You should pass a User Object to the evaluation methods like `getValue()`/`getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'stringContainsDogDefaultCat' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN 'Dog' => cannot evaluate, User Object is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | Returning 'Cat'. 7 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/ClientCacheState.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * Describes the Client state. 5 | */ 6 | public enum ClientCacheState { 7 | /** 8 | * The SDK has no feature flag data neither from the cache nor from the ConfigCat CDN. 9 | */ 10 | NO_FLAG_DATA, 11 | /** 12 | * The SDK runs with local only feature flag data. 13 | */ 14 | HAS_LOCAL_OVERRIDE_FLAG_DATA_ONLY, 15 | /** 16 | * The SDK has feature flag data to work with only from the cache. 17 | */ 18 | HAS_CACHED_FLAG_DATA_ONLY, 19 | /** 20 | * The SDK works with the latest feature flag data received from the ConfigCat CDN. 21 | */ 22 | HAS_UP_TO_DATE_FLAG_DATA, 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/FormattableLogMessageWithUserCondition.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | class FormattableLogMessageWithUserCondition extends FormattableLogMessage { 4 | 5 | FormattableLogMessageWithUserCondition(String message, Object... args) { 6 | super(message,args); 7 | } 8 | 9 | @Override 10 | protected String formatLogMessage() { 11 | Object userConditionObject = args[0]; 12 | if(userConditionObject instanceof UserCondition) { 13 | UserCondition userCondition = (UserCondition) userConditionObject; 14 | args[0] = EvaluateLogger.formatUserCondition(userCondition); 15 | } 16 | return String.format(message, args); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/configcat/ClassPathResourceOverrideDataSource.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | 6 | public class ClassPathResourceOverrideDataSource extends OverrideDataSource { 7 | private final Map loadedSettings; 8 | 9 | @Override 10 | public Map getLocalConfiguration() { 11 | return this.loadedSettings; 12 | } 13 | 14 | public ClassPathResourceOverrideDataSource(String fileName) throws IOException { 15 | String contents = Helpers.readFileFromClassPath(fileName); 16 | Config config = Utils.deserializeConfig(contents); 17 | this.loadedSettings = config.getEntries(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/and_rules/and_rules_no_user.txt: -------------------------------------------------------------------------------- 1 | WARNING [3001] Cannot evaluate targeting rules and % options for setting 'emailAnd' (User Object is missing). You should pass a User Object to the evaluation methods like `getValue()`/`getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'emailAnd' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email STARTS WITH ANY OF [<1 hashed value>] => false, skipping the remaining AND conditions 5 | THEN 'Dog' => cannot evaluate, User Object is missing 6 | The current targeting rule is ignored and the evaluation continues with the next rule. 7 | Returning 'Cat'. 8 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/1_targeting_rule/1_rule_no_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | WARNING [3003] Cannot evaluate condition (User.Email CONTAINS ANY OF ['@configcat.com']) for setting 'stringContainsDogDefaultCat' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'stringContainsDogDefaultCat' for User '{"Identifier":"12345"}' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN 'Dog' => cannot evaluate, the User.Email attribute is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | Returning 'Cat'. 7 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/SegmentCondition.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Describes a condition that is based on a segment. 7 | */ 8 | public class SegmentCondition { 9 | 10 | @SerializedName(value = "s") 11 | private int segmentIndex; 12 | @SerializedName(value = "c") 13 | private int segmentComparator; 14 | 15 | /** 16 | * The index of the segment that the condition is based on. 17 | */ 18 | public int getSegmentIndex() { 19 | return segmentIndex; 20 | } 21 | 22 | /** 23 | * The operator which defines the expected result of the evaluation of the segment. 24 | */ 25 | public int getSegmentComparator() { 26 | return segmentComparator; 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/resources/matrix/testmatrix_segments_old.csv: -------------------------------------------------------------------------------- 1 | Identifier;Email;Country;Custom1;featureWithSegmentTargeting;featureWithSegmentTargetingCleartext;featureWithNegatedSegmentTargeting;featureWithNegatedSegmentTargetingCleartext;featureWithSegmentTargetingInverse;featureWithSegmentTargetingInverseCleartext;featureWithNegatedSegmentTargetingInverse;featureWithNegatedSegmentTargetingInverseCleartext 2 | ##null##;;;;False;False;False;False;False;False;False;False 3 | ;;;;False;False;False;False;False;False;False;False 4 | john@example.com;john@example.com;##null##;##null##;True;True;False;False;False;False;True;True 5 | jane@example.com;jane@example.com;##null##;##null##;True;True;False;False;False;False;True;True 6 | kate@example.com;kate@example.com;##null##;##null##;False;False;True;True;True;True;False;False 7 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_within_targeting_rule/options_within_targeting_rule_no_user.txt: -------------------------------------------------------------------------------- 1 | WARNING [3001] Cannot evaluate targeting rules and % options for setting 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' (User Object is missing). You should pass a User Object to the evaluation methods like `getValue()`/`getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN % options => cannot evaluate, User Object is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | Returning 'Cat'. 7 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/segment/segment_no_user_multi_conditions.txt: -------------------------------------------------------------------------------- 1 | WARNING [3001] Cannot evaluate targeting rules and % options for setting 'featureWithSegmentTargetingMultipleConditions' (User Object is missing). You should pass a User Object to the evaluation methods like `getValue()`/`getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'featureWithSegmentTargetingMultipleConditions' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User IS IN SEGMENT 'Beta users (cleartext)' => false, skipping the remaining AND conditions 5 | THEN 'true' => cannot evaluate, User Object is missing 6 | The current targeting rule is ignored and the evaluation continues with the next rule. 7 | Returning 'false'. 8 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_after_targeting_rule/options_after_targeting_rule_no_user.txt: -------------------------------------------------------------------------------- 1 | WARNING [3001] Cannot evaluate targeting rules and % options for setting 'integer25One25Two25Three25FourAdvancedRules' (User Object is missing). You should pass a User Object to the evaluation methods like `getValue()`/`getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'integer25One25Two25Three25FourAdvancedRules' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN '5' => cannot evaluate, User Object is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | Skipping % options because the User Object is missing. 7 | Returning '-1'. 8 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_within_targeting_rule/options_within_targeting_rule_no_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | WARNING [3003] Cannot evaluate condition (User.Email CONTAINS ANY OF ['@configcat.com']) for setting 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' for User '{"Identifier":"12345"}' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN % options => cannot evaluate, the User.Email attribute is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | Returning 'Cat'. 7 | -------------------------------------------------------------------------------- /configcat-proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # ConfigCat Android SDK Proguard rules 2 | 3 | -keep class com.configcat.Config { *; } 4 | -keep class com.configcat.Preferences { *; } 5 | -keep class com.configcat.Setting { *; } 6 | -keep class com.configcat.SettingType { *; } 7 | -keep class com.configcat.EvaluationDetails { *; } 8 | -keep class com.configcat.TargetingRule { *; } 9 | -keep class com.configcat.SettingType { *; } 10 | -keep class com.configcat.SettingValue { *; } 11 | -keep class com.configcat.Segment { *; } 12 | -keep class com.configcat.Condition { *; } 13 | -keep class com.configcat.SimpleValue { *; } 14 | -keep class com.configcat.Condition { *; } 15 | -keep class com.configcat.UserCondition { *; } 16 | -keep class com.configcat.SegmentCondition { *; } 17 | -keep class com.configcat.PrerequisiteFlagCondition { *; } 18 | -keep class com.configcat.PercentageOption { *; } 19 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/2_targeting_rules/2_rules_not_matching_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | WARNING [3003] Cannot evaluate condition (User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com']) for setting 'stringIsInDogDefaultCat' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'stringIsInDogDefaultCat' for User '{"Identifier":"12345","Custom1":"user"}' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com'] THEN 'Dog' => cannot evaluate, the User.Email attribute is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | - IF User.Custom1 IS ONE OF ['admin'] THEN 'Dog' => no match 7 | Returning 'Cat'. 8 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/android-kotlin/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | android.enableJetifier=true 13 | android.useAndroidX=true 14 | org.gradle.jvmargs=-Xmx1536m 15 | 16 | # When configured, Gradle will run in incubating parallel mode. 17 | # This option should only be used with decoupled projects. More details, visit 18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 19 | # org.gradle.parallel=true 20 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/OverrideDataSource.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Describes a data source for feature flag and setting overrides. 8 | */ 9 | public class OverrideDataSource { 10 | /** 11 | * Gets all the overrides defined in the given source. 12 | * 13 | * @return the overrides key-setting map. 14 | */ 15 | public Map getLocalConfiguration() { 16 | return new HashMap<>(); 17 | } 18 | 19 | /** 20 | * Create an override data source that stores the overrides in a key-value map. 21 | * 22 | * @param map the map that holds the overrides. 23 | * @return the map based data source. 24 | */ 25 | public static OverrideDataSource map(Map map) { 26 | return new LocalMapDataSource(map); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/2_targeting_rules/2_rules_matching_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | WARNING [3003] Cannot evaluate condition (User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com']) for setting 'stringIsInDogDefaultCat' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'stringIsInDogDefaultCat' for User '{"Identifier":"12345","Custom1":"admin"}' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com'] THEN 'Dog' => cannot evaluate, the User.Email attribute is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | - IF User.Custom1 IS ONE OF ['admin'] THEN 'Dog' => MATCH, applying rule 7 | Returning 'Dog'. 8 | -------------------------------------------------------------------------------- /samples/android-kotlin/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21" 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | mavenLocal() 22 | 23 | maven { 24 | url "https://oss.sonatype.org/content/repositories/snapshots" 25 | } 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_within_targeting_rule/options_within_targeting_rule_matching_targeted_attribute_no_options_attribute.txt: -------------------------------------------------------------------------------- 1 | WARNING [3003] Cannot evaluate % options for setting 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' (the User.Country attribute is missing). You should set the User.Country attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'stringContainsString75Cat0Dog25Falcon0HorseDefaultCat' for User '{"Identifier":"12345","Email":"joe@configcat.com"}' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN % options => MATCH, applying rule 5 | Skipping % options because the User.Country attribute is missing. 6 | The current targeting rule is ignored and the evaluation continues with the next rule. 7 | Returning 'Cat'. 8 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/2_targeting_rules/2_rules_no_user.txt: -------------------------------------------------------------------------------- 1 | WARNING [3001] Cannot evaluate targeting rules and % options for setting 'stringIsInDogDefaultCat' (User Object is missing). You should pass a User Object to the evaluation methods like `getValue()`/`getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'stringIsInDogDefaultCat' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com'] THEN 'Dog' => cannot evaluate, User Object is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | - IF User.Custom1 IS ONE OF ['admin'] THEN 'Dog' => cannot evaluate, User Object is missing 7 | The current targeting rule is ignored and the evaluation continues with the next rule. 8 | Returning 'Cat'. 9 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/Condition.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Represents a condition. 7 | */ 8 | public class Condition implements ConditionAccessor { 9 | 10 | @SerializedName(value = "u") 11 | private UserCondition userCondition; 12 | @SerializedName(value = "s") 13 | private SegmentCondition segmentCondition; 14 | @SerializedName(value = "p") 15 | private PrerequisiteFlagCondition prerequisiteFlagCondition; 16 | 17 | @Override 18 | public UserCondition getUserCondition() { 19 | return userCondition; 20 | } 21 | 22 | @Override 23 | public SegmentCondition getSegmentCondition() { 24 | return segmentCondition; 25 | } 26 | 27 | @Override 28 | public PrerequisiteFlagCondition getPrerequisiteFlagCondition() { 29 | return prerequisiteFlagCondition; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/LogFilterFunction.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * The Log Filter Functional Interface provides a custom filter option for the ConfigCat Logger. 5 | */ 6 | @FunctionalInterface 7 | public interface LogFilterFunction { 8 | 9 | /** 10 | * Apply the custom filter option to the ConfigCatLogger. 11 | * 12 | * @param logLevel Event severity level. 13 | * @param eventId Event identifier. 14 | * @param message Message object. The formatted message String can be obtained by calling {@code toString}. (It is guaranteed that the message string is built only once even if {@code toString} called multiple times.) 15 | * @param exception The exception object related to the message (if any). 16 | * @return True to log the event, false will leave out the log. 17 | */ 18 | boolean apply(LogLevel logLevel, int eventId, Object message, Throwable exception); 19 | } 20 | -------------------------------------------------------------------------------- /samples/android-java/app/src/main/assets/logback.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | %logger{12} 9 | 10 | 11 | [%-20thread] %msg 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/SharedPreferencesCache.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | /** 7 | * {@link ConfigCache} implementation that uses {@link SharedPreferences} for persistent storage. 8 | */ 9 | public class SharedPreferencesCache extends ConfigCache { 10 | private final SharedPreferences sharedPreferences; 11 | 12 | public SharedPreferencesCache(android.content.Context context) { 13 | this.sharedPreferences = context.getApplicationContext().getSharedPreferences("configcat_preferences", Context.MODE_PRIVATE); 14 | } 15 | 16 | @Override 17 | protected String read(String key) { 18 | return this.sharedPreferences.getString(key, null); 19 | } 20 | 21 | @Override 22 | protected void write(String key, String value) { 23 | this.sharedPreferences.edit().putString(key, value).apply(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/epoch_date_validation/date_error.txt: -------------------------------------------------------------------------------- 1 | WARNING [3004] Cannot evaluate condition (User.Custom1 AFTER '1680307200' (2023-04-01T00:00:00.000Z UTC)) for setting 'boolTrueIn202304' ('2023.04.10' is not a valid Unix timestamp (number of seconds elapsed since Unix epoch)). Please check the User.Custom1 attribute and make sure that its value corresponds to the comparison operator. 2 | INFO [5000] Evaluating 'boolTrueIn202304' for User '{"Identifier":"12345","Custom1":"2023.04.10"}' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Custom1 AFTER '1680307200' (2023-04-01T00:00:00.000Z UTC) => false, skipping the remaining AND conditions 5 | THEN 'true' => cannot evaluate, the User.Custom1 attribute is invalid ('2023.04.10' is not a valid Unix timestamp (number of seconds elapsed since Unix epoch)) 6 | The current targeting rule is ignored and the evaluation continues with the next rule. 7 | Returning 'false'. 8 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_based_on_custom_attr.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw", 3 | "tests": [ 4 | { 5 | "key": "string75Cat0Dog25Falcon0HorseCustomAttr", 6 | "defaultValue": "default", 7 | "returnValue": "Chicken", 8 | "expectedLog": "options_custom_attribute_no_user.txt" 9 | }, 10 | { 11 | "key": "string75Cat0Dog25Falcon0HorseCustomAttr", 12 | "defaultValue": "default", 13 | "user": { 14 | "Identifier": "12345" 15 | }, 16 | "returnValue": "Chicken", 17 | "expectedLog": "no_options_custom_attribute.txt" 18 | }, 19 | { 20 | "key": "string75Cat0Dog25Falcon0HorseCustomAttr", 21 | "defaultValue": "default", 22 | "user": { 23 | "Identifier": "12345", 24 | "Country": "US" 25 | }, 26 | "returnValue": "Cat", 27 | "expectedLog": "matching_options_custom_attribute.txt" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/test/resources/matrix/testmatrix_prerequisite_flag.csv: -------------------------------------------------------------------------------- 1 | Identifier;Email;Country;Custom1;mainBoolFlag;mainStringFlag;mainIntFlag;mainDoubleFlag;stringDependsOnBool;stringDependsOnString;stringDependsOnStringCaseCheck;stringDependsOnInt;stringDependsOnDouble;stringDependsOnDoubleIntValue;boolDependsOnBool;intDependsOnBool;doubleDependsOnBool;boolDependsOnBoolDependsOnBool;mainBoolFlagEmpty;stringDependsOnEmptyBool;stringInverseDependsOnEmptyBool;mainBoolFlagInverse;boolDependsOnBoolInverse 2 | ##null##;;;;True;public;42;3.14;Dog;Cat;Cat;Cat;Cat;Cat;True;1;1.1;False;True;EmptyOn;EmptyOn;False;True 3 | ;;;;True;public;42;3.14;Dog;Cat;Cat;Cat;Cat;Cat;True;1;1.1;False;True;EmptyOn;EmptyOn;False;True 4 | john@sensitivecompany.com;john@sensitivecompany.com;##null##;##null##;False;private;2;0.1;Cat;Dog;Cat;Dog;Dog;Cat;False;42;3.14;True;True;EmptyOn;EmptyOn;True;False 5 | jane@example.com;jane@example.com;##null##;##null##;True;public;42;3.14;Dog;Cat;Cat;Cat;Cat;Cat;True;1;1.1;False;True;EmptyOn;EmptyOn;False;True 6 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/simple_value.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A", 3 | "tests": [ 4 | { 5 | "key": "boolDefaultFalse", 6 | "defaultValue": true, 7 | "returnValue": false, 8 | "expectedLog": "off_flag.txt" 9 | }, 10 | { 11 | "key": "boolDefaultTrue", 12 | "defaultValue": false, 13 | "returnValue": true, 14 | "expectedLog": "on_flag.txt" 15 | }, 16 | { 17 | "key": "stringDefaultCat", 18 | "defaultValue": "Default", 19 | "returnValue": "Cat", 20 | "expectedLog": "text_setting.txt" 21 | }, 22 | { 23 | "key": "integerDefaultOne", 24 | "defaultValue": 0, 25 | "returnValue": 1, 26 | "expectedLog": "int_setting.txt" 27 | }, 28 | { 29 | "testName": "double_setting", 30 | "key": "doubleDefaultPi", 31 | "defaultValue": 0.0, 32 | "returnValue": 3.1415, 33 | "expectedLog": "double_setting.txt" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/ConfigCache.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * A cache API used to make custom cache implementations for {@link ConfigCatClient}. 5 | */ 6 | public abstract class ConfigCache { 7 | /** 8 | * Child classes has to implement this method, the {@link ConfigCatClient} 9 | * uses it to get the actual value from the cache. 10 | * 11 | * @param key the key of the cache entry. 12 | * @return the cached configuration. 13 | * @throws Exception if unable to read the cache. 14 | */ 15 | protected abstract String read(String key) throws Exception; 16 | 17 | /** 18 | * * Child classes has to implement this method, the {@link ConfigCatClient} 19 | * uses it to set the actual cached value. 20 | * 21 | * @param key the key of the cache entry. 22 | * @param value the new value to cache. 23 | * @throws Exception if unable to save the value. 24 | */ 25 | protected abstract void write(String key, String value) throws Exception; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/DateTimeUtils.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | import java.util.TimeZone; 7 | 8 | public class DateTimeUtils { 9 | 10 | private DateTimeUtils() { /* prevent from instantiation*/ } 11 | 12 | public static boolean isValidDate(String date) { 13 | try { 14 | Long.parseLong(date); 15 | } catch (NumberFormatException e) { 16 | return false; 17 | } 18 | return true; 19 | } 20 | 21 | public static String doubleToFormattedUTC(double dateInDouble) { 22 | long dateInMilliSec = (long) dateInDouble * 1000; 23 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); 24 | simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 25 | return simpleDateFormat.format(new Date(dateInMilliSec)); 26 | } 27 | 28 | public static double getUnixSeconds(Date date) { 29 | return date.getTime() / 1000d; 30 | } 31 | } -------------------------------------------------------------------------------- /samples/android-java/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21" 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | mavenLocal() 22 | 23 | maven { 24 | url "https://oss.sonatype.org/content/repositories/snapshots" 25 | } 26 | } 27 | } 28 | 29 | //plugins { 30 | // id 'com.android.application' version '7.3.1' apply false 31 | // id 'com.android.library' version '7.3.1' apply false 32 | //} 33 | 34 | task clean(type: Delete) { 35 | delete rootProject.buildDir 36 | } -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_after_targeting_rule/options_after_targeting_rule_no_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | WARNING [3003] Cannot evaluate condition (User.Email CONTAINS ANY OF ['@configcat.com']) for setting 'integer25One25Two25Three25FourAdvancedRules' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'integer25One25Two25Three25FourAdvancedRules' for User '{"Identifier":"12345"}' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email CONTAINS ANY OF ['@configcat.com'] THEN '5' => cannot evaluate, the User.Email attribute is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | Evaluating % options based on the User.Identifier attribute: 7 | - Computing hash in the [0..99] range from User.Identifier => 25 (this value is sticky and consistent across all SDKs) 8 | - Hash value 25 selects % option 2 (25%), '2'. 9 | Returning '2'. 10 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/PercentageOption.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Represents a percentage option. 7 | */ 8 | public class PercentageOption { 9 | 10 | @SerializedName(value = "p") 11 | private int percentage; 12 | @SerializedName(value = "v") 13 | private SettingValue value; 14 | @SerializedName(value = "i") 15 | private String variationId; 16 | 17 | /** 18 | * A number between 0 and 100 that represents a randomly allocated fraction of the users. 19 | */ 20 | public int getPercentage() { 21 | return percentage; 22 | } 23 | 24 | /** 25 | * The value associated with the percentage option. 26 | * Can be a value of the following types: {@link Boolean}, {@link String}, {@link Integer} or {@link Double}. 27 | */ 28 | public SettingValue getValue() { 29 | return value; 30 | } 31 | 32 | /** 33 | * Variation ID. 34 | */ 35 | public String getVariationId() { 36 | return variationId; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/android-kotlin/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/SegmentComparator.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * Segment comparison operator used during the evaluation process. 5 | */ 6 | public enum SegmentComparator { 7 | 8 | /** 9 | * IS IN SEGMENT - Checks whether the conditions of the specified segment are evaluated to true. 10 | */ 11 | IS_IN_SEGMENT(0, "IS IN SEGMENT"), 12 | /** 13 | * IS NOT IN SEGMENT - Checks whether the conditions of the specified segment are evaluated to false. 14 | */ 15 | IS_NOT_IN_SEGMENT(1, "IS NOT IN SEGMENT"); 16 | 17 | private final int id; 18 | private final String name; 19 | 20 | SegmentComparator(int id, String name) { 21 | this.id = id; 22 | this.name = name; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public static SegmentComparator fromId(int id) { 30 | for (SegmentComparator comparator : SegmentComparator.values()) { 31 | if (comparator.id == id) { 32 | return comparator; 33 | } 34 | } 35 | return null; 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ConfigCat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/resources/matrix/testmatrix_and_or.csv: -------------------------------------------------------------------------------- 1 | Identifier;Email;Country;Custom1;mainFeature;dependentFeature;emailAnd;emailOr 2 | ##null##;;;;public;Chicken;Cat;Cat 3 | ;;;;public;Chicken;Cat;Cat 4 | jane@example.com;jane@example.com;##null##;##null##;public;Chicken;Cat;Jane 5 | john@example.com;john@example.com;##null##;##null##;public;Chicken;Cat;John 6 | a@example.com;a@example.com;USA;##null##;target;Cat;Cat;Cat 7 | mark@example.com;mark@example.com;USA;##null##;target;Dog;Cat;Mark 8 | nora@example.com;nora@example.com;USA;##null##;target;Falcon;Cat;Cat 9 | stern@msn.com;stern@msn.com;USA;##null##;target;Horse;Cat;Cat 10 | jane@sensitivecompany.com;jane@sensitivecompany.com;England;##null##;private;Chicken;Dog;Jane 11 | anna@sensitivecompany.com;anna@sensitivecompany.com;France;##null##;private;Chicken;Cat;Cat 12 | jane@sensitivecompany.com;jane@sensitivecompany.com;england;##null##;public;Chicken;Dog;Jane 13 | jane;jane;##null##;##null##;public;Chicken;Cat;Cat 14 | @sensitivecompany.com;@sensitivecompany.com;##null##;##null##;public;Chicken;Cat;Cat 15 | jane.sensitivecompany.com;jane.sensitivecompany.com;##null##;##null##;public;Chicken;Cat;Cat 16 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/PrerequisiteComparator.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * Prerequisite flag comparison operator used during the evaluation process. 5 | */ 6 | public enum PrerequisiteComparator { 7 | /** 8 | * EQUALS - Checks whether the evaluated value of the specified prerequisite flag is equal to the comparison value. 9 | */ 10 | EQUALS(0, "EQUALS"), 11 | /** 12 | * NOT EQUALS - Checks whether the evaluated value of the specified prerequisite flag is not equal to the comparison value. 13 | */ 14 | NOT_EQUALS(1, "NOT EQUALS"); 15 | 16 | private final int id; 17 | private final String name; 18 | 19 | PrerequisiteComparator(int id, String name) { 20 | this.id = id; 21 | this.name = name; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public static PrerequisiteComparator fromId(int id) { 29 | for (PrerequisiteComparator comparator : PrerequisiteComparator.values()) { 30 | if (comparator.id == id) { 31 | return comparator; 32 | } 33 | } 34 | return null; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/test/resources/evaluation/2_targeting_rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A", 3 | "tests": [ 4 | { 5 | "key": "stringIsInDogDefaultCat", 6 | "defaultValue": "default", 7 | "returnValue": "Cat", 8 | "expectedLog": "2_rules_no_user.txt" 9 | }, 10 | { 11 | "key": "stringIsInDogDefaultCat", 12 | "defaultValue": "default", 13 | "user": { 14 | "Identifier": "12345" 15 | }, 16 | "returnValue": "Cat", 17 | "expectedLog": "2_rules_no_targeted_attribute.txt" 18 | }, 19 | { 20 | "key": "stringIsInDogDefaultCat", 21 | "defaultValue": "default", 22 | "user": { 23 | "Identifier": "12345", 24 | "Custom1": "user" 25 | }, 26 | "returnValue": "Cat", 27 | "expectedLog": "2_rules_not_matching_targeted_attribute.txt" 28 | }, 29 | { 30 | "key": "stringIsInDogDefaultCat", 31 | "defaultValue": "default", 32 | "user": { 33 | "Identifier": "12345", 34 | "Custom1": "admin" 35 | }, 36 | "returnValue": "Dog", 37 | "expectedLog": "2_rules_matching_targeted_attribute.txt" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/segment/segment_no_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | WARNING [3003] Cannot evaluate condition (User.Email IS ONE OF ['jane@example.com', 'john@example.com']) for setting 'featureWithNegatedSegmentTargetingCleartext' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'featureWithNegatedSegmentTargetingCleartext' for User '{"Identifier":"12345"}' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User IS NOT IN SEGMENT 'Beta users (cleartext)' 5 | ( 6 | Evaluating segment 'Beta users (cleartext)': 7 | - IF User.Email IS ONE OF ['jane@example.com', 'john@example.com'] => false, skipping the remaining AND conditions 8 | Segment evaluation result: cannot evaluate, the User.Email attribute is missing. 9 | Condition (User IS NOT IN SEGMENT 'Beta users (cleartext)') failed to evaluate. 10 | ) 11 | THEN 'true' => cannot evaluate, the User.Email attribute is missing 12 | The current targeting rule is ignored and the evaluation continues with the next rule. 13 | Returning 'false'. 14 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/FormattableLogMessage.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.Arrays; 6 | import java.util.Objects; 7 | 8 | class FormattableLogMessage { 9 | 10 | private String cachedMessage; 11 | protected final String message; 12 | protected final Object[] args; 13 | 14 | FormattableLogMessage(String message, Object... args) { 15 | this.message = message; 16 | this.args = args; 17 | } 18 | 19 | protected String formatLogMessage(){ 20 | return String.format(message, args); 21 | } 22 | 23 | @NotNull 24 | @Override 25 | public String toString() { 26 | if(cachedMessage == null) { 27 | cachedMessage = formatLogMessage(); 28 | } 29 | return cachedMessage; 30 | } 31 | 32 | 33 | @Override 34 | public boolean equals(Object obj) { 35 | if(obj instanceof FormattableLogMessage) { 36 | return toString().equals(obj.toString()); 37 | } 38 | return false; 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | return Objects.hash(cachedMessage, message, Arrays.hashCode(args)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /samples/android-java/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/FormattableLogMessageWithKeySet.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import java.util.Iterator; 4 | import java.util.Set; 5 | 6 | class FormattableLogMessageWithKeySet extends FormattableLogMessage { 7 | 8 | FormattableLogMessageWithKeySet(String message, Object... args) { 9 | super(message,args); 10 | } 11 | 12 | @Override 13 | protected String formatLogMessage() { 14 | Object keySetObject = args[args.length - 1]; 15 | if(keySetObject instanceof Set) { 16 | Set keySet = (Set) keySetObject; 17 | args[args.length - 1] = convertKeySetToFormattedString(keySet); 18 | } 19 | return String.format(message, args); 20 | } 21 | 22 | private static String convertKeySetToFormattedString(final Set availableKeys) { 23 | StringBuilder sb = new StringBuilder(); 24 | Iterator it = availableKeys.iterator(); 25 | if (it.hasNext()) { 26 | sb.append("'").append(it.next()).append("'"); 27 | } 28 | while (it.hasNext()) { 29 | sb.append(", ").append("'").append(it.next()).append("'"); 30 | } 31 | return sb.toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/1_targeting_rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A", 3 | "tests": [ 4 | { 5 | "key": "stringContainsDogDefaultCat", 6 | "defaultValue": "default", 7 | "returnValue": "Cat", 8 | "expectedLog": "1_rule_no_user.txt" 9 | }, 10 | { 11 | "key": "stringContainsDogDefaultCat", 12 | "defaultValue": "default", 13 | "user": { 14 | "Identifier": "12345" 15 | }, 16 | "returnValue": "Cat", 17 | "expectedLog": "1_rule_no_targeted_attribute.txt" 18 | }, 19 | { 20 | "key": "stringContainsDogDefaultCat", 21 | "defaultValue": "default", 22 | "user": { 23 | "Identifier": "12345", 24 | "Email": "joe@example.com" 25 | }, 26 | "returnValue": "Cat", 27 | "expectedLog": "1_rule_not_matching_targeted_attribute.txt" 28 | }, 29 | { 30 | "key": "stringContainsDogDefaultCat", 31 | "defaultValue": "default", 32 | "user": { 33 | "Identifier": "12345", 34 | "Email": "joe@configcat.com" 35 | }, 36 | "returnValue": "Dog", 37 | "expectedLog": "1_rule_matching_targeted_attribute.txt" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/prerequisite_flag/prerequisite_flag_no_user_needed_by_dep.txt: -------------------------------------------------------------------------------- 1 | WARNING [3001] Cannot evaluate targeting rules and % options for setting 'dependentFeatureWithUserCondition' (User Object is missing). You should pass a User Object to the evaluation methods like `getValue()`/`getValueAsync()` in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | INFO [5000] Evaluating 'dependentFeatureWithUserCondition' 3 | Evaluating targeting rules and applying the first match if any: 4 | - IF User.Email IS ONE OF [<2 hashed values>] THEN 'Dog' => cannot evaluate, User Object is missing 5 | The current targeting rule is ignored and the evaluation continues with the next rule. 6 | - IF Flag 'mainFeatureWithoutUserCondition' EQUALS 'true' 7 | ( 8 | Evaluating prerequisite flag 'mainFeatureWithoutUserCondition': 9 | Prerequisite flag evaluation result: 'true'. 10 | Condition (Flag 'mainFeatureWithoutUserCondition' EQUALS 'true') evaluates to true. 11 | ) 12 | THEN % options => MATCH, applying rule 13 | Skipping % options because the User Object is missing. 14 | The current targeting rule is ignored and the evaluation continues with the next rule. 15 | Returning 'Chicken'. 16 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/OverrideBehaviour.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | /** 4 | * Describes how the overrides should behave. 5 | */ 6 | public enum OverrideBehaviour { 7 | /** 8 | * When evaluating values, the SDK will not use feature flags and settings from the ConfigCat CDN, but it will use 9 | * all feature flags and settings that are loaded from local-override sources. 10 | */ 11 | LOCAL_ONLY, 12 | /** 13 | * When evaluating values, the SDK will use all feature flags and settings that are downloaded from the ConfigCat CDN, 14 | * plus all feature flags and settings that are loaded from local-override sources. If a feature flag or a setting is 15 | * defined both in the fetched and the local-override source then the local-override version will take precedence. 16 | */ 17 | LOCAL_OVER_REMOTE, 18 | /** 19 | * When evaluating values, the SDK will use all feature flags and settings that are downloaded from the ConfigCat CDN, 20 | * plus all feature flags and settings that are loaded from local-override sources. If a feature flag or a setting is 21 | * defined both in the fetched and the local-override source then the fetched version will take precedence. 22 | */ 23 | REMOTE_OVER_LOCAL, 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/PrerequisiteFlagCondition.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Describes a condition that is based on a prerequisite flag. 7 | */ 8 | public class PrerequisiteFlagCondition { 9 | 10 | @SerializedName(value = "f") 11 | private String prerequisiteFlagKey; 12 | @SerializedName(value = "c") 13 | private int prerequisiteComparator; 14 | @SerializedName(value = "v") 15 | private SettingValue value; 16 | 17 | /** 18 | * The key of the prerequisite flag that the condition is based on. 19 | */ 20 | public String getPrerequisiteFlagKey() { 21 | return prerequisiteFlagKey; 22 | } 23 | 24 | /** 25 | * The operator which defines the relation between the evaluated value of the prerequisite flag and the comparison value. 26 | */ 27 | public int getPrerequisiteComparator() { 28 | return prerequisiteComparator; 29 | } 30 | 31 | /** 32 | * The value that the evaluated value of the prerequisite flag is compared to. 33 | * Can be a value of the following types: {@link Boolean}, {@link String}, {@link Integer} or {@link Double}. 34 | */ 35 | public SettingValue getValue() { 36 | return value; 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/java/com/configcat/TestCache.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import java.util.HashMap; 4 | 5 | class FailingCache extends ConfigCache { 6 | 7 | @Override 8 | protected String read(String key) throws Exception { 9 | throw new Exception(); 10 | } 11 | 12 | @Override 13 | protected void write(String key, String value) throws Exception { 14 | throw new Exception(); 15 | } 16 | } 17 | 18 | class SingleValueCache extends ConfigCache { 19 | private String value; 20 | 21 | public SingleValueCache(String value) { 22 | this.value = value; 23 | } 24 | 25 | @Override 26 | protected String read(String key) { 27 | return this.value; 28 | } 29 | 30 | @Override 31 | protected void write(String key, String value) { 32 | this.value = value; 33 | } 34 | } 35 | 36 | class InMemoryCache extends ConfigCache { 37 | HashMap map = new HashMap<>(); 38 | 39 | @Override 40 | protected String read(String key) { 41 | return map.get(key); 42 | } 43 | 44 | @Override 45 | protected void write(String key, String value) { 46 | this.map.put(key, value); 47 | } 48 | 49 | public HashMap getMap() { 50 | return map; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/2_targeting_rules/2_rules_no_targeted_attribute.txt: -------------------------------------------------------------------------------- 1 | WARNING [3003] Cannot evaluate condition (User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com']) for setting 'stringIsInDogDefaultCat' (the User.Email attribute is missing). You should set the User.Email attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 2 | WARNING [3003] Cannot evaluate condition (User.Custom1 IS ONE OF ['admin']) for setting 'stringIsInDogDefaultCat' (the User.Custom1 attribute is missing). You should set the User.Custom1 attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/ 3 | INFO [5000] Evaluating 'stringIsInDogDefaultCat' for User '{"Identifier":"12345"}' 4 | Evaluating targeting rules and applying the first match if any: 5 | - IF User.Email IS ONE OF ['a@configcat.com', 'b@configcat.com'] THEN 'Dog' => cannot evaluate, the User.Email attribute is missing 6 | The current targeting rule is ignored and the evaluation continues with the next rule. 7 | - IF User.Custom1 IS ONE OF ['admin'] THEN 'Dog' => cannot evaluate, the User.Custom1 attribute is missing 8 | The current targeting rule is ignored and the evaluation continues with the next rule. 9 | Returning 'Cat'. 10 | -------------------------------------------------------------------------------- /samples/android-java/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'com.configcat.configcatsample' 7 | compileSdk 32 8 | 9 | defaultConfig { 10 | applicationId "com.configcat.configcatsample" 11 | minSdk 21 12 | targetSdk 32 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | } 23 | } 24 | compileOptions { 25 | // Sets Java compatibility to Java 8 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | buildFeatures { 30 | viewBinding true 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation 'androidx.appcompat:appcompat:1.5.1' 36 | implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.0' 37 | implementation 'com.google.android.material:material:1.5.0' 38 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 39 | implementation 'com.configcat:configcat-android-client:10.0.0' 40 | implementation 'org.slf4j:slf4j-api:2.0.7' 41 | implementation 'com.github.tony19:logback-android:3.0.0' 42 | } -------------------------------------------------------------------------------- /src/test/resources/evaluation/prerequisite_flag.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/ByMO9yZNn02kXcm72lnY1A", 3 | "tests": [ 4 | { 5 | "key": "dependentFeatureWithUserCondition", 6 | "defaultValue": "default", 7 | "returnValue": "Chicken", 8 | "expectedLog": "prerequisite_flag_no_user_needed_by_dep.txt" 9 | }, 10 | { 11 | "key": "dependentFeature", 12 | "defaultValue": "default", 13 | "returnValue": "Chicken", 14 | "expectedLog": "prerequisite_flag_no_user_needed_by_prereq.txt" 15 | }, 16 | { 17 | "key": "dependentFeatureWithUserCondition2", 18 | "defaultValue": "default", 19 | "returnValue": "Frog", 20 | "expectedLog": "prerequisite_flag_no_user_needed_by_both.txt" 21 | }, 22 | { 23 | "key": "dependentFeature", 24 | "defaultValue": "default", 25 | "user": { 26 | "Identifier": "12345", 27 | "Email": "kate@configcat.com", 28 | "Country": "USA" 29 | }, 30 | "returnValue": "Horse", 31 | "expectedLog": "prerequisite_flag.txt" 32 | }, 33 | { 34 | "key": "dependentFeatureMultipleLevels", 35 | "defaultValue": "default", 36 | "returnValue": "Dog", 37 | "expectedLog": "prerequisite_flag_multilevel.txt" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/test/resources/evaluation/prerequisite_flag/prerequisite_flag_multilevel.txt: -------------------------------------------------------------------------------- 1 | INFO [5000] Evaluating 'dependentFeatureMultipleLevels' 2 | Evaluating targeting rules and applying the first match if any: 3 | - IF Flag 'intermediateFeature' EQUALS 'true' 4 | ( 5 | Evaluating prerequisite flag 'intermediateFeature': 6 | Evaluating targeting rules and applying the first match if any: 7 | - IF Flag 'mainFeatureWithoutUserCondition' EQUALS 'true' 8 | ( 9 | Evaluating prerequisite flag 'mainFeatureWithoutUserCondition': 10 | Prerequisite flag evaluation result: 'true'. 11 | Condition (Flag 'mainFeatureWithoutUserCondition' EQUALS 'true') evaluates to true. 12 | ) => true 13 | AND Flag 'mainFeatureWithoutUserCondition' EQUALS 'true' 14 | ( 15 | Evaluating prerequisite flag 'mainFeatureWithoutUserCondition': 16 | Prerequisite flag evaluation result: 'true'. 17 | Condition (Flag 'mainFeatureWithoutUserCondition' EQUALS 'true') evaluates to true. 18 | ) => true 19 | THEN 'true' => MATCH, applying rule 20 | Prerequisite flag evaluation result: 'true'. 21 | Condition (Flag 'intermediateFeature' EQUALS 'true') evaluates to true. 22 | ) 23 | THEN 'Dog' => MATCH, applying rule 24 | Returning 'Dog'. 25 | -------------------------------------------------------------------------------- /src/main/java/com/configcat/Config.java: -------------------------------------------------------------------------------- 1 | package com.configcat; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * Details of a ConfigCat config. 10 | */ 11 | public class Config { 12 | 13 | @SerializedName(value = "p") 14 | private Preferences preferences; 15 | @SerializedName(value = "f") 16 | private Map entries = new HashMap<>(); 17 | @SerializedName(value = "s") 18 | private Segment[] segments; 19 | 20 | /** 21 | * The config preferences. 22 | */ 23 | public Preferences getPreferences() { 24 | return preferences; 25 | } 26 | 27 | /** 28 | * The list of segments. 29 | */ 30 | public Segment[] getSegments() { 31 | return segments; 32 | } 33 | 34 | /** 35 | * The map of settings. 36 | */ 37 | public Map getEntries() { 38 | // NOTE: Deserializing a JSON like '{ "f": null }' overwrites entries with null. 39 | // However, we want to treat null as an empty map in that case too. 40 | return entries != null ? entries : (entries = new HashMap<>()); 41 | } 42 | 43 | boolean isEmpty() { 44 | return EMPTY.equals(this); 45 | } 46 | 47 | public static final Config EMPTY = new Config(); 48 | } -------------------------------------------------------------------------------- /src/test/resources/evaluation/options_after_targeting_rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdkKey": "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A", 3 | "tests": [ 4 | { 5 | "key": "integer25One25Two25Three25FourAdvancedRules", 6 | "defaultValue": 42, 7 | "returnValue": -1, 8 | "expectedLog": "options_after_targeting_rule_no_user.txt" 9 | }, 10 | { 11 | "key": "integer25One25Two25Three25FourAdvancedRules", 12 | "defaultValue": 42, 13 | "user": { 14 | "Identifier": "12345" 15 | }, 16 | "returnValue": 2, 17 | "expectedLog": "options_after_targeting_rule_no_targeted_attribute.txt" 18 | }, 19 | { 20 | "key": "integer25One25Two25Three25FourAdvancedRules", 21 | "defaultValue": 42, 22 | "user": { 23 | "Identifier": "12345", 24 | "Email": "joe@example.com" 25 | }, 26 | "returnValue": 2, 27 | "expectedLog": "options_after_targeting_rule_not_matching_targeted_attribute.txt" 28 | }, 29 | { 30 | "key": "integer25One25Two25Three25FourAdvancedRules", 31 | "defaultValue": 42, 32 | "user": { 33 | "Identifier": "12345", 34 | "Email": "joe@configcat.com" 35 | }, 36 | "returnValue": 5, 37 | "expectedLog": "options_after_targeting_rule_matching_targeted_attribute.txt" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /samples/android-java/app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 21 | 22 |