diff --git a/build.gradle b/build.gradle
index 585ecb7..a72c99c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -63,8 +63,6 @@ repositories {
dependencies {
api(libs.android.retrofuture)
- api(libs.okhttp)
- api(libs.okio)
api(libs.slf4j.api)
api(libs.gson)
api(libs.commons.codec)
diff --git a/gradle.properties b/gradle.properties
index ad40c0a..d0ec49d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,3 @@
-version=10.4.3
+version=11.0.0
org.gradle.jvmargs=-Xmx2g
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 7914fc8..ff948bc 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,22 +1,18 @@
[versions]
sonarqube = "5.0.0.4638"
maven-publish = "0.32.0"
-okhttp = "4.12.0"
-okio = "3.4.0" # indirect dependency to solve security vulnerability in 3.2.0
slf4j-api = "1.7.36"
gson = "2.9.0"
commons-codec = "1.15"
semantic-version = "2.1.1"
junit-jupiter = "5.10.0"
-logback = "1.5.13"
+logback = "1.5.19"
mockwebserver = "4.12.0"
mockito = "4.8.0"
android-retrofuture = "1.7.4"
android-gradle = "8.0.0"
[libraries]
-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
-okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
commons-codec = { module = "commons-codec:commons-codec", version.ref = "commons-codec" }
diff --git a/samples/android-java/app/build.gradle b/samples/android-java/app/build.gradle
index 783fed9..1bef44e 100644
--- a/samples/android-java/app/build.gradle
+++ b/samples/android-java/app/build.gradle
@@ -36,7 +36,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.0'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'com.configcat:configcat-android-client:10.0.0'
+ implementation 'com.configcat:configcat-android-client:11.0.0'
implementation 'org.slf4j:slf4j-api:2.0.7'
implementation 'com.github.tony19:logback-android:3.0.0'
}
\ No newline at end of file
diff --git a/samples/android-java/app/src/main/java/com/configcat/configcatsample/MainActivity.java b/samples/android-java/app/src/main/java/com/configcat/configcatsample/MainActivity.java
index ff90099..9716ece 100644
--- a/samples/android-java/app/src/main/java/com/configcat/configcatsample/MainActivity.java
+++ b/samples/android-java/app/src/main/java/com/configcat/configcatsample/MainActivity.java
@@ -6,10 +6,7 @@
import androidx.appcompat.app.AppCompatActivity;
-import com.configcat.ConfigCatClient;
-import com.configcat.LogLevel;
-import com.configcat.SharedPreferencesCache;
-import com.configcat.User;
+import com.configcat.*;
public class MainActivity extends AppCompatActivity {
@@ -22,9 +19,15 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
client = ConfigCatClient.get("PKDVCLf-Hq-h-kCzMp-L7Q/HhOWfwVtZ0mb30i9wi17GQ", options -> {
+ options.pollingMode(PollingModes.autoPoll(5));
+
// Use ConfigCat's shared preferences cache.
options.cache(new SharedPreferencesCache(getApplicationContext()));
+ // With this option, the SDK automatically switches between offline and online modes based on
+ // whether the application is in the foreground or background and on network availability.
+ options.watchAppStateChanges(getApplicationContext());
+
// Info level logging helps to inspect the feature flag evaluation process.
// Use the default Warning level to avoid too detailed logging in your application.
options.logLevel(LogLevel.DEBUG);
@@ -42,7 +45,7 @@ private void fetchNewConfig() {
.thenAccept(value -> {
this.runOnUiThread(() -> {
TextView viewById = this.findViewById(R.id.editText);
- viewById.setText("isPOCFeatureEnabled: " + value);
+ viewById.setText(String.format("isPOCFeatureEnabled: %s", value));
});
});
}
diff --git a/samples/android-java/gradle/wrapper/gradle-wrapper.properties b/samples/android-java/gradle/wrapper/gradle-wrapper.properties
index 1d4841f..1af9e09 100644
--- a/samples/android-java/gradle/wrapper/gradle-wrapper.properties
+++ b/samples/android-java/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
-#Thu Jan 05 11:38:37 CET 2023
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/samples/android-java/gradlew b/samples/android-java/gradlew
index 4f906e0..1aa94a4 100644
--- a/samples/android-java/gradlew
+++ b/samples/android-java/gradlew
@@ -1,7 +1,7 @@
-#!/usr/bin/env sh
+#!/bin/sh
#
-# Copyright 2015 the original author or authors.
+# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,99 @@
#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=`expr $i + 1`
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
exec "$JAVACMD" "$@"
diff --git a/samples/android-java/gradlew.bat b/samples/android-java/gradlew.bat
index 107acd3..93e3f59 100644
--- a/samples/android-java/gradlew.bat
+++ b/samples/android-java/gradlew.bat
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/samples/android-kotlin/app/build.gradle b/samples/android-kotlin/app/build.gradle
index 1bc8ca2..eba8336 100644
--- a/samples/android-kotlin/app/build.gradle
+++ b/samples/android-kotlin/app/build.gradle
@@ -36,7 +36,7 @@ dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.21'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'com.configcat:configcat-android-client:10.0.0'
+ implementation 'com.configcat:configcat-android-client:11.0.0'
implementation 'com.noveogroup.android:android-logger:1.3.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
diff --git a/samples/android-kotlin/app/src/main/java/com/configcat/configcatsample/MainActivity.kt b/samples/android-kotlin/app/src/main/java/com/configcat/configcatsample/MainActivity.kt
index 8123ec9..dc62a07 100644
--- a/samples/android-kotlin/app/src/main/java/com/configcat/configcatsample/MainActivity.kt
+++ b/samples/android-kotlin/app/src/main/java/com/configcat/configcatsample/MainActivity.kt
@@ -20,6 +20,10 @@ class MainActivity : AppCompatActivity() {
// Use ConfigCat's shared preferences cache.
options.cache(SharedPreferencesCache(this@MainActivity))
+ // With this option, the SDK automatically switches between offline and online modes based on
+ // whether the application is in the foreground or background and on network availability.
+ options.watchAppStateChanges(this@MainActivity)
+
// Info level logging helps to inspect the feature flag evaluation process.
// Use the default Warning level to avoid too detailed logging in your application.
options.logLevel(LogLevel.DEBUG)
diff --git a/samples/android-kotlin/gradle/wrapper/gradle-wrapper.properties b/samples/android-kotlin/gradle/wrapper/gradle-wrapper.properties
index 09e2b38..f398c33 100644
--- a/samples/android-kotlin/gradle/wrapper/gradle-wrapper.properties
+++ b/samples/android-kotlin/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Sep 22 18:23:09 CEST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
+networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/samples/android-kotlin/gradlew b/samples/android-kotlin/gradlew
index 9d82f78..65dcd68 100644
--- a/samples/android-kotlin/gradlew
+++ b/samples/android-kotlin/gradlew
@@ -1,74 +1,129 @@
-#!/usr/bin/env bash
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
-warn ( ) {
+warn () {
echo "$*"
-}
+} >&2
-die ( ) {
+die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -77,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD="java"
+ JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -85,76 +140,105 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=$((i+1))
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/samples/android-kotlin/gradlew.bat b/samples/android-kotlin/gradlew.bat
index 8a0b282..93e3f59 100644
--- a/samples/android-kotlin/gradlew.bat
+++ b/samples/android-kotlin/gradlew.bat
@@ -1,4 +1,20 @@
-@if "%DEBUG%" == "" @echo off
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -8,20 +24,24 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +55,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,44 +65,26 @@ echo location of your Java installation.
goto fail
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index bd5df81..74a98bc 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/src/main/java/com/configcat/AppStateMonitor.java b/src/main/java/com/configcat/AppStateMonitor.java
new file mode 100644
index 0000000..0016423
--- /dev/null
+++ b/src/main/java/com/configcat/AppStateMonitor.java
@@ -0,0 +1,164 @@
+package com.configcat;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.*;
+import android.content.res.Configuration;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import java9.util.function.Consumer;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+interface StateMonitor extends Closeable {
+ void addStateChangeListener(Consumer listener);
+ boolean isNetworkAvailable();
+}
+
+class AppStateMonitor extends BroadcastReceiver implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2, StateMonitor {
+ static final String CONNECTIVITY_CHANGE = "android.net.conn.CONNECTIVITY_CHANGE";
+
+ private final Context context;
+ private final Application application;
+ private final ConfigCatLogger logger;
+ private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
+ private final List> stateChangeListeners = new ArrayList<>();
+ private final AtomicBoolean inForeground = new AtomicBoolean(true);
+
+ public AppStateMonitor(Context context, ConfigCatLogger logger) {
+ this.context = context;
+ this.logger = logger;
+
+ application = (Application) context.getApplicationContext();
+ application.registerActivityLifecycleCallbacks(this);
+ application.registerComponentCallbacks(this);
+
+ IntentFilter filter = new IntentFilter(CONNECTIVITY_CHANGE);
+ application.registerReceiver(this, filter);
+ }
+
+ public void addStateChangeListener(Consumer listener) {
+ lock.writeLock().lock();
+ try {
+ this.stateChangeListeners.add(listener);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ private void notifyListeners() {
+ boolean hasNetwork = this.isNetworkAvailable();
+ logger.debug(String.format("App state has been changed, in foreground: %s, has network: %s", inForeground.get(), hasNetwork));
+ lock.readLock().lock();
+ try {
+ for (Consumer listener : this.stateChangeListeners) {
+ listener.accept(inForeground.get() && hasNetwork);
+ }
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ if (inForeground.compareAndSet(false, true)) {
+ notifyListeners();
+ }
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ if (inForeground.compareAndSet(false, true)) {
+ notifyListeners();
+ }
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ if (inForeground.compareAndSet(false, true)) {
+ notifyListeners();
+ }
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ // ignore
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ // ignore
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+ // ignore
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ // ignore
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!CONNECTIVITY_CHANGE.equals(intent.getAction())) {
+ return;
+ }
+ notifyListeners();
+ }
+
+ @Override
+ public void onTrimMemory(int i) {
+ // We're in the background
+ if (i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN && inForeground.compareAndSet(true, false)) {
+ notifyListeners();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ // ignore
+ }
+
+ @Override
+ public void onLowMemory() {
+ // ignore
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean isNetworkAvailable() {
+ try {
+ ConnectivityManager conn =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = conn.getActiveNetworkInfo();
+ return networkInfo != null && networkInfo.isConnected();
+ } catch (SecurityException e) {
+ // Fall back to assuming a network is available
+ return true;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ lock.writeLock().lock();
+ try {
+ stateChangeListeners.clear();
+ } finally {
+ lock.writeLock().unlock();
+ }
+ try {
+ application.unregisterReceiver(this);
+ } catch (IllegalArgumentException e) {
+ // ignore, the receiver was not registered
+ }
+ application.unregisterActivityLifecycleCallbacks(this);
+ application.unregisterComponentCallbacks(this);
+ }
+}
diff --git a/src/main/java/com/configcat/ConfigCatClient.java b/src/main/java/com/configcat/ConfigCatClient.java
index 960ccde..0c45087 100644
--- a/src/main/java/com/configcat/ConfigCatClient.java
+++ b/src/main/java/com/configcat/ConfigCatClient.java
@@ -2,10 +2,10 @@
import java9.util.concurrent.CompletableFuture;
import java9.util.function.Consumer;
-import okhttp3.OkHttpClient;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.net.Proxy;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -41,9 +41,7 @@ private ConfigCatClient(String sdkKey, Options options) throws IllegalArgumentEx
this.rolloutEvaluator = new RolloutEvaluator(this.logger);
if (this.overrideBehaviour != OverrideBehaviour.LOCAL_ONLY) {
- ConfigFetcher fetcher = new ConfigFetcher(options.httpClient == null
- ? new OkHttpClient()
- : options.httpClient,
+ ConfigFetcher fetcher = new ConfigFetcher(options.httpOptions,
this.logger,
sdkKey,
!options.isBaseURLCustom()
@@ -54,7 +52,8 @@ private ConfigCatClient(String sdkKey, Options options) throws IllegalArgumentEx
options.isBaseURLCustom(),
options.pollingMode.getPollingIdentifier());
- this.configService = new ConfigService(sdkKey, options.pollingMode, options.cache, logger, fetcher, options.hooks, options.offline);
+ StateMonitor monitor = options.context != null ? new AppStateMonitor(options.context, logger) : null;
+ this.configService = new ConfigService(sdkKey, monitor, options.pollingMode, options.cache, logger, fetcher, options.hooks, options.offline);
} else {
this.hooks.invokeOnClientReady(ClientCacheState.HAS_LOCAL_OVERRIDE_FLAG_DATA_ONLY);
}
@@ -669,7 +668,7 @@ public CompletableFuture waitForReadyAsync() {
* Configuration options for a {@link ConfigCatClient} instance.
*/
public static class Options {
- private OkHttpClient httpClient;
+ private android.content.Context context;
private ConfigCache cache = new NullConfigCache();
private String baseUrl;
private PollingMode pollingMode = PollingModes.autoPoll(60);
@@ -681,15 +680,14 @@ public static class Options {
private boolean offline;
private LogFilterFunction logFilter;
+ private final HttpOptions httpOptions = new HttpOptions();
private final ConfigCatHooks hooks = new ConfigCatHooks();
/**
- * Sets the underlying http client which will be used to fetch the latest configuration.
- *
- * @param httpClient the http client.
+ * HTTP related options for {@link ConfigCatClient}.
*/
- public void httpClient(OkHttpClient httpClient) {
- this.httpClient = httpClient;
+ public final HttpOptions httpOptions() {
+ return this.httpOptions;
}
/**
@@ -794,9 +792,68 @@ public void logFilter(LogFilterFunction logFilter) {
this.logFilter = logFilter;
}
+ /**
+ * Indicates that the SDK should react to application state changes.
+ *
+ * @param context the Android {@link android.content.Context} instance.
+ */
+ public void watchAppStateChanges(android.content.Context context) {
+ this.context = context;
+ }
+
private boolean isBaseURLCustom() {
return this.baseUrl != null && !this.baseUrl.isEmpty();
}
}
+ /**
+ * HTTP configuration options for a {@link ConfigCatClient} instance.
+ */
+ public static class HttpOptions {
+ private int connectTimeoutMillis = 10000;
+ private int readTimeoutMillis = 10000;
+ private Proxy proxy;
+
+ /**
+ * Sets HTTP connect timeout in milliseconds.
+ *
+ * @param connectTimeoutMillis the connect timeout in milliseconds.
+ */
+ public HttpOptions connectTimeoutMillis(int connectTimeoutMillis) {
+ this.connectTimeoutMillis = connectTimeoutMillis;
+ return this;
+ }
+
+ /**
+ * Sets the HTTP read timeout in milliseconds.
+ *
+ * @param readTimeoutMillis the read timeout in milliseconds.
+ */
+ public HttpOptions readTimeoutMillis(int readTimeoutMillis) {
+ this.readTimeoutMillis = readTimeoutMillis;
+ return this;
+ }
+
+ /**
+ * Sets the HTTP proxy.
+ *
+ * @param proxy the HTTP proxy.
+ */
+ public HttpOptions proxy(Proxy proxy) {
+ this.proxy = proxy;
+ return this;
+ }
+
+ int getConnectTimeoutMillis() {
+ return connectTimeoutMillis;
+ }
+
+ int getReadTimeoutMillis() {
+ return readTimeoutMillis;
+ }
+
+ Proxy getProxy() {
+ return proxy;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/configcat/ConfigCatLogMessages.java b/src/main/java/com/configcat/ConfigCatLogMessages.java
index d8204c4..2198981 100644
--- a/src/main/java/com/configcat/ConfigCatLogMessages.java
+++ b/src/main/java/com/configcat/ConfigCatLogMessages.java
@@ -167,11 +167,10 @@ public static FormattableLogMessage getFetchFailedDueToUnexpectedHttpResponse(fi
*
* @param connectTimeoutMillis Connect timeout in milliseconds.
* @param readTimeoutMillis Read timeout in milliseconds.
- * @param writeTimeoutMillis Write timeout in milliseconds.
* @return The formattable log message.
*/
- public static FormattableLogMessage getFetchFailedDueToRequestTimeout(final Integer connectTimeoutMillis, final Integer readTimeoutMillis, final Integer writeTimeoutMillis) {
- return new FormattableLogMessage("Request timed out while trying to fetch config JSON. Timeout values: [connect: %dms, read: %dms, write: %dms]", connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis);
+ public static FormattableLogMessage getFetchFailedDueToRequestTimeout(final Integer connectTimeoutMillis, final Integer readTimeoutMillis) {
+ return new FormattableLogMessage("Request timed out while trying to fetch config JSON. Timeout values: [connect: %dms, read: %dms]", connectTimeoutMillis, readTimeoutMillis);
}
/**
diff --git a/src/main/java/com/configcat/ConfigFetcher.java b/src/main/java/com/configcat/ConfigFetcher.java
index d04b0ca..8f0463a 100644
--- a/src/main/java/com/configcat/ConfigFetcher.java
+++ b/src/main/java/com/configcat/ConfigFetcher.java
@@ -1,12 +1,15 @@
package com.configcat;
import java9.util.concurrent.CompletableFuture;
-import okhttp3.*;
-import org.jetbrains.annotations.NotNull;
-import java.io.Closeable;
-import java.io.IOException;
+import java.io.*;
+import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
class FetchResponse {
@@ -70,9 +73,10 @@ public static FetchResponse failed(Object error, boolean fetchTimeUpdatable, Str
}
class ConfigFetcher implements Closeable {
+ private final ConfigCatClient.HttpOptions httpOptions;
private final AtomicBoolean closed = new AtomicBoolean(false);
+ private final ExecutorService executorService;
private final ConfigCatLogger logger;
- private final OkHttpClient httpClient;
private final String mode;
private final String sdkKey;
private final boolean urlIsCustom;
@@ -85,7 +89,7 @@ enum RedirectMode {
FORCE_REDIRECT
}
- ConfigFetcher(OkHttpClient httpClient,
+ ConfigFetcher(ConfigCatClient.HttpOptions httpOptions,
ConfigCatLogger logger,
String sdkKey,
String url,
@@ -95,8 +99,9 @@ enum RedirectMode {
this.sdkKey = sdkKey;
this.urlIsCustom = urlIsCustom;
this.url = url;
- this.httpClient = httpClient;
this.mode = pollingIdentifier;
+ this.httpOptions = httpOptions;
+ this.executorService = Executors.newCachedThreadPool();
}
public CompletableFuture fetchAsync(String eTag) {
@@ -116,7 +121,7 @@ private CompletableFuture executeFetchAsync(int executionCount, S
}
String newUrl = config.getPreferences().getBaseUrl();
- if (newUrl.equals(this.url)) {
+ if (newUrl == null || newUrl.equals(this.url)) {
return CompletableFuture.completedFuture(fetchResponse);
}
@@ -152,66 +157,75 @@ private CompletableFuture executeFetchAsync(int executionCount, S
}
private CompletableFuture getResponseAsync(String eTag) {
- Request request = this.getRequest(eTag);
CompletableFuture future = new CompletableFuture<>();
- this.httpClient.newCall(request).enqueue(new Callback() {
- @Override
- public void onFailure(@NotNull Call call, @NotNull IOException e) {
- String generalMessage = ConfigCatLogMessages.FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR;
- if (!closed.get()) {
- if (e instanceof SocketTimeoutException) {
- FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpClient.connectTimeoutMillis(), httpClient.readTimeoutMillis(), httpClient.writeTimeoutMillis());
- logger.error(1102, message, e);
- future.complete(FetchResponse.failed(message, false, null));
- return;
- }
- logger.error(1103, generalMessage, e);
+ this.executorService.execute(() -> this.callHTTP(eTag, future));
+ return future;
+ }
+
+ private void callHTTP(String previousETag, CompletableFuture result) {
+ String requestUrl = this.url + "/configuration-files/" + this.sdkKey + "/" + Constants.CONFIG_JSON_NAME;
+ HttpURLConnection urlConnection = null;
+
+ try {
+ URL fetchUrl = new URL(requestUrl);
+ if (httpOptions.getProxy() != null) {
+ urlConnection = (HttpURLConnection) fetchUrl.openConnection(httpOptions.getProxy());
+ } else {
+ urlConnection = (HttpURLConnection) fetchUrl.openConnection();
+ }
+ urlConnection.setConnectTimeout(httpOptions.getConnectTimeoutMillis());
+ urlConnection.setReadTimeout(httpOptions.getReadTimeoutMillis());
+ urlConnection.setUseCaches(false);
+ urlConnection.setInstanceFollowRedirects(false);
+ urlConnection.setRequestProperty("X-ConfigCat-UserAgent", "ConfigCat-Droid/" + this.mode + "-" + Constants.VERSION);
+
+ if (previousETag != null && !previousETag.isEmpty())
+ urlConnection.setRequestProperty("If-None-Match", previousETag);
+
+ int responseCode = urlConnection.getResponseCode();
+ Map> responseHeaders = urlConnection.getHeaderFields();
+
+ String cfRayId = readHeaderValue(responseHeaders, "CF-RAY");
+ if (responseCode == 200) {
+ String content = readBody(urlConnection.getInputStream());
+ String eTag = readHeaderValue(responseHeaders,"ETag");
+ Result configResult = deserializeConfig(content, cfRayId);
+ if (configResult.error() != null) {
+ result.complete(FetchResponse.failed(configResult.error(), false, cfRayId));
+ return;
+ }
+ logger.debug("Fetch was successful: new config fetched.");
+ result.complete(FetchResponse.fetched(new Entry(configResult.value(), eTag, content, System.currentTimeMillis()), cfRayId));
+ } else if (responseCode == 304) {
+ if(cfRayId != null) {
+ logger.debug(String.format("Fetch was successful: config not modified. %s", ConfigCatLogMessages.getCFRayIdPostFix(cfRayId)));
+ } else {
+ logger.debug("Fetch was successful: config not modified.");
}
- future.complete(FetchResponse.failed(generalMessage, false, null));
+ result.complete(FetchResponse.notModified(cfRayId));
+ } else if (responseCode == 403 || responseCode == 404) {
+ FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToInvalidSDKKey(cfRayId);
+ logger.error(1100, message);
+ result.complete(FetchResponse.failed(message, true, cfRayId));
+ } else {
+ FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToUnexpectedHttpResponse(responseCode, urlConnection.getResponseMessage(), cfRayId);
+ logger.error(1101, message);
+ result.complete(FetchResponse.failed(message, false, cfRayId));
}
- @Override
- public void onResponse(@NotNull Call call, @NotNull Response response) {
- try (ResponseBody body = response.body()) {
- String cfRayId = response.header("CF-RAY");
- if (response.code() == 200) {
- String content = body != null ? body.string() : null;
- String eTag = response.header("ETag");
- Result result = deserializeConfig(content, cfRayId);
- if (result.error() != null) {
- future.complete(FetchResponse.failed(result.error(), false, cfRayId));
- return;
- }
- logger.debug("Fetch was successful: new config fetched.");
- future.complete(FetchResponse.fetched(new Entry(result.value(), eTag, content, System.currentTimeMillis()), cfRayId));
- } else if (response.code() == 304) {
- if(cfRayId != null) {
- logger.debug(String.format("Fetch was successful: config not modified. %s", ConfigCatLogMessages.getCFRayIdPostFix(cfRayId)));
- } else {
- logger.debug("Fetch was successful: config not modified.");
- }
- future.complete(FetchResponse.notModified(cfRayId));
- } else if (response.code() == 403 || response.code() == 404) {
- FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToInvalidSDKKey(cfRayId);
- logger.error(1100, message);
- future.complete(FetchResponse.failed(message, true, cfRayId));
- } else {
- FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToUnexpectedHttpResponse(response.code(), response.message(), cfRayId);
- logger.error(1101, message);
- future.complete(FetchResponse.failed(message, false, cfRayId));
- }
- } catch (SocketTimeoutException e) {
- FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpClient.connectTimeoutMillis(), httpClient.readTimeoutMillis(), httpClient.writeTimeoutMillis());
- logger.error(1102, message, e);
- future.complete(FetchResponse.failed(message, false, null));
- } catch (Exception e) {
- String message = ConfigCatLogMessages.FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR;
- logger.error(1103, message, e);
- future.complete(FetchResponse.failed(message + " " + e.getMessage(), false, null));
- }
+ } catch (SocketTimeoutException e) {
+ FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpOptions.getConnectTimeoutMillis(), httpOptions.getReadTimeoutMillis());
+ logger.error(1102, message, e);
+ result.complete(FetchResponse.failed(message, false, null));
+ } catch (Exception e) {
+ String message = ConfigCatLogMessages.FETCH_FAILED_DUE_TO_UNEXPECTED_ERROR;
+ logger.error(1103, message, e);
+ result.complete(FetchResponse.failed(message + " " + e.getMessage(), false, null));
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
}
- });
- return future;
+ }
}
@Override
@@ -219,25 +233,37 @@ public void close() throws IOException {
if (!this.closed.compareAndSet(false, true)) {
return;
}
-
- if (this.httpClient != null) {
- this.httpClient.dispatcher().executorService().shutdownNow();
- this.httpClient.connectionPool().evictAll();
- Cache cache = this.httpClient.cache();
- if (cache != null)
- cache.close();
+ if (this.executorService != null) {
+ this.executorService.shutdownNow();
}
}
- private Request getRequest(String eTag) {
- String requestUrl = this.url + "/configuration-files/" + this.sdkKey + "/" + Constants.CONFIG_JSON_NAME;
- Request.Builder builder = new Request.Builder()
- .addHeader("X-ConfigCat-UserAgent", "ConfigCat-Droid/" + this.mode + "-" + Constants.VERSION);
-
- if (eTag != null && !eTag.isEmpty())
- builder.addHeader("If-None-Match", eTag);
+ private String readHeaderValue(Map> responseHeaders, String headerName) {
+ if (responseHeaders == null || responseHeaders.isEmpty()) {
+ return null;
+ }
+ for (Map.Entry> entry : responseHeaders.entrySet()) {
+ if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(headerName)) {
+ if (entry.getValue() != null && !entry.getValue().isEmpty()) {
+ return entry.getValue().get(0);
+ }
+ }
+ }
+ return null;
+ }
- return builder.url(requestUrl).build();
+ private String readBody(InputStream inputStream) throws IOException {
+ if (inputStream == null) {
+ return null;
+ }
+ StringBuilder body = new StringBuilder();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ body.append(line);
+ }
+ reader.close();
+ return body.toString();
}
private Result deserializeConfig(String json, String cfRayId) {
diff --git a/src/main/java/com/configcat/ConfigService.java b/src/main/java/com/configcat/ConfigService.java
index 9f063cb..6b0808e 100644
--- a/src/main/java/com/configcat/ConfigService.java
+++ b/src/main/java/com/configcat/ConfigService.java
@@ -44,9 +44,12 @@ class ConfigService implements Closeable {
private Entry cachedEntry = Entry.EMPTY;
private CompletableFuture> runningTask;
private final AtomicBoolean initialized = new AtomicBoolean(false);
+ private final AtomicBoolean userIndicatedOffline;
+ private final AtomicBoolean inForegroundAndHasNetwork;
private final AtomicBoolean offline;
private final AtomicBoolean closed = new AtomicBoolean(false);
private final String cacheKey;
+ private final StateMonitor stateMonitor;
private final PollingMode mode;
private final ConfigCache cache;
private final ConfigCatLogger logger;
@@ -55,21 +58,32 @@ class ConfigService implements Closeable {
private final ReentrantLock lock = new ReentrantLock(true);
public ConfigService(String sdkKey,
+ StateMonitor stateMonitor,
PollingMode mode,
ConfigCache cache,
ConfigCatLogger logger,
ConfigFetcher fetcher,
ConfigCatHooks hooks,
- boolean offline) {
+ boolean userIndicatedOffline) {
this.cacheKey = Utils.sha1(String.format(CACHE_BASE, sdkKey));
+ this.stateMonitor = stateMonitor;
this.mode = mode;
this.cache = cache;
this.logger = logger;
this.fetcher = fetcher;
this.hooks = hooks;
- this.offline = new AtomicBoolean(offline);
+ this.userIndicatedOffline = new AtomicBoolean(userIndicatedOffline);
+ this.inForegroundAndHasNetwork = new AtomicBoolean(stateMonitor == null || stateMonitor.isNetworkAvailable());
+ this.offline = new AtomicBoolean(isOffline());
- if (mode instanceof AutoPollingMode && !offline) {
+ if (stateMonitor != null) {
+ stateMonitor.addStateChangeListener(state -> {
+ inForegroundAndHasNetwork.set(state);
+ switchStateIfNeeded();
+ });
+ }
+
+ if (mode instanceof AutoPollingMode) {
AutoPollingMode autoPollingMode = (AutoPollingMode) mode;
startPoll(autoPollingMode);
@@ -116,7 +130,7 @@ public CompletableFuture getSettings() {
}
public CompletableFuture refresh() {
- if (offline.get()) {
+ if (isOffline()) {
String offlineWarning = ConfigCatLogMessages.CONFIG_SERVICE_CANNOT_INITIATE_HTTP_CALLS_WARN;
logger.warn(3200, offlineWarning);
return CompletableFuture.completedFuture(new RefreshResult(false, offlineWarning));
@@ -127,32 +141,30 @@ public CompletableFuture refresh() {
}
public void setOnline() {
- lock.lock();
- try {
- if (!offline.compareAndSet(true, false)) return;
- if (mode instanceof AutoPollingMode) {
- startPoll((AutoPollingMode) mode);
- }
- logger.info(5200, ConfigCatLogMessages.getConfigServiceStatusChanged("ONLINE"));
- } finally {
- lock.unlock();
- }
+ if (!userIndicatedOffline.compareAndSet(true, false)) return;
+ switchStateIfNeeded();
}
public void setOffline() {
- lock.lock();
- try {
- if (!offline.compareAndSet(false, true)) return;
- if (pollScheduler != null) pollScheduler.shutdown();
- if (initScheduler != null) initScheduler.shutdown();
- logger.info(5200, ConfigCatLogMessages.getConfigServiceStatusChanged("OFFLINE"));
- } finally {
- lock.unlock();
- }
+ if (!userIndicatedOffline.compareAndSet(false, true)) return;
+ switchStateIfNeeded();
}
public boolean isOffline() {
- return offline.get();
+ return userIndicatedOffline.get() || !inForegroundAndHasNetwork.get();
+ }
+
+ private void switchStateIfNeeded() {
+ boolean isOffline = isOffline();
+ if (isOffline && offline.compareAndSet(false, true)) {
+ logger.info(5200, ConfigCatLogMessages.getConfigServiceStatusChanged("OFFLINE"));
+ }
+ if (!isOffline && offline.compareAndSet(true, false)) {
+ if (mode instanceof AutoPollingMode) {
+ startPoll((AutoPollingMode) mode);
+ }
+ logger.info(5200, ConfigCatLogMessages.getConfigServiceStatusChanged("ONLINE"));
+ }
}
private CompletableFuture> fetchIfOlder(long threshold, boolean preferCached) {
@@ -170,7 +182,8 @@ private CompletableFuture> fetchIfOlder(long threshold, boolean pr
return CompletableFuture.completedFuture(Result.success(cachedEntry));
}
// If we are in offline mode or the caller prefers cached values, do not initiate fetch.
- if (offline.get() || preferCached) {
+ if (isOffline() || preferCached) {
+ setInitialized();
return CompletableFuture.completedFuture(Result.success(cachedEntry));
}
@@ -224,10 +237,16 @@ private void setInitialized() {
}
private void startPoll(AutoPollingMode mode) {
- long ageThreshold = (mode.getAutoPollRateInSeconds() * 1000L) - 500;
- pollScheduler = Executors.newSingleThreadScheduledExecutor();
- pollScheduler.scheduleWithFixedDelay(() -> this.fetchIfOlder(System.currentTimeMillis() - ageThreshold, false),
- 0, mode.getAutoPollRateInSeconds(), TimeUnit.SECONDS);
+ lock.lock();
+ try {
+ long ageThreshold = (mode.getAutoPollRateInSeconds() * 1000L) - 500;
+ if (pollScheduler != null) pollScheduler.shutdown();
+ pollScheduler = Executors.newSingleThreadScheduledExecutor();
+ pollScheduler.scheduleWithFixedDelay(() -> this.fetchIfOlder(System.currentTimeMillis() - ageThreshold, false),
+ 0, mode.getAutoPollRateInSeconds(), TimeUnit.SECONDS);
+ } finally {
+ lock.unlock();
+ }
}
private void writeCache(Entry entry) {
@@ -280,6 +299,7 @@ public void close() throws IOException {
}
if (pollScheduler != null) pollScheduler.shutdown();
if (initScheduler != null) initScheduler.shutdown();
+ if (stateMonitor != null) stateMonitor.close();
fetcher.close();
}
}
diff --git a/src/main/java/com/configcat/FormattableLogMessage.java b/src/main/java/com/configcat/FormattableLogMessage.java
index c31d3a6..7ac6dbe 100644
--- a/src/main/java/com/configcat/FormattableLogMessage.java
+++ b/src/main/java/com/configcat/FormattableLogMessage.java
@@ -1,7 +1,5 @@
package com.configcat;
-import org.jetbrains.annotations.NotNull;
-
import java.util.Arrays;
import java.util.Objects;
@@ -20,7 +18,6 @@ protected String formatLogMessage(){
return String.format(message, args);
}
- @NotNull
@Override
public String toString() {
if(cachedMessage == null) {
diff --git a/src/main/java/com/configcat/Utils.java b/src/main/java/com/configcat/Utils.java
index 68950b2..a118b0a 100644
--- a/src/main/java/com/configcat/Utils.java
+++ b/src/main/java/com/configcat/Utils.java
@@ -56,7 +56,7 @@ private Constants() { /* prevent from instantiation*/ }
static final long DISTANT_PAST = 0;
static final String CONFIG_JSON_NAME = "config_v6.json";
static final String SERIALIZATION_FORMAT_VERSION = "v2";
- static final String VERSION = "10.4.3";
+ static final String VERSION = "11.0.0";
static final String SDK_KEY_PROXY_PREFIX = "configcat-proxy/";
static final String SDK_KEY_PREFIX = "configcat-sdk-1";
diff --git a/src/test/java/com/configcat/AutoPollingTest.java b/src/test/java/com/configcat/AutoPollingTest.java
index 8b6ff46..5673a59 100644
--- a/src/test/java/com/configcat/AutoPollingTest.java
+++ b/src/test/java/com/configcat/AutoPollingTest.java
@@ -1,6 +1,5 @@
package com.configcat;
-import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
@@ -11,7 +10,6 @@
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
@@ -45,13 +43,13 @@ void get() throws InterruptedException, ExecutionException, IOException {
ConfigCache cache = new NullConfigCache();
PollingMode pollingMode = PollingModes.autoPoll(2);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
//first call
assertEquals("test", policy.getSettings().get().settings().get("fakeKey").getSettingsValue().getStringValue());
@@ -71,13 +69,13 @@ void getFail() throws InterruptedException, ExecutionException, IOException {
ConfigCache cache = new NullConfigCache();
PollingMode pollingMode = PollingModes.autoPoll(2);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
//first call
assertTrue(policy.getSettings().get().settings().isEmpty());
@@ -94,13 +92,13 @@ void getMany() throws InterruptedException, ExecutionException, IOException {
ConfigCache cache = new NullConfigCache();
PollingMode pollingMode = PollingModes.autoPoll(2);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
//first calls
assertEquals("test", policy.getSettings().get().settings().get("fakeKey").getSettingsValue().getStringValue());
@@ -135,13 +133,13 @@ void getWithFailedRefresh() throws InterruptedException, ExecutionException, IOE
ConfigCache cache = new NullConfigCache();
PollingMode pollingMode = PollingModes.autoPoll(2);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
//first call
assertEquals("test", policy.getSettings().get().settings().get("fakeKey").getSettingsValue().getStringValue());
@@ -165,13 +163,13 @@ void getCacheFails() throws Exception {
doThrow(new Exception()).when(cache).write(anyString(), anyString());
PollingMode pollingMode = PollingModes.autoPoll(2);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
assertEquals("test", policy.getSettings().get().settings().get("fakeKey").getSettingsValue().getStringValue());
@@ -183,13 +181,13 @@ void testInitWaitTimeTimeout() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test")).setBodyDelay(2, TimeUnit.SECONDS));
PollingMode pollingMode = PollingModes.autoPoll(60, 1);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
long start = System.currentTimeMillis();
assertTrue(policy.getSettings().get().settings().isEmpty());
@@ -206,13 +204,13 @@ void testPollIntervalRespectsCacheExpiration() throws Exception {
ConfigCache cache = new SingleValueCache(Helpers.cacheValueFromConfigJson(String.format(TEST_JSON, "test")));
PollingMode pollingMode = PollingModes.autoPoll(2);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
policy.getSettings().get();
@@ -230,13 +228,13 @@ void testPollsWhenCacheExpired() throws Exception {
ConfigCache cache = new SingleValueCache(Helpers.cacheValueFromConfigJsonAndTime(String.format(TEST_JSON, "test"), System.currentTimeMillis() - 5000));
PollingMode pollingMode = PollingModes.autoPoll(1);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService configService = new ConfigService("", pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService configService = new ConfigService("", null, pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
configService.getSettings().get();
@@ -254,13 +252,13 @@ void testNonExpiredCacheCallsReady() throws Exception {
ConfigCatHooks hooks = new ConfigCatHooks();
hooks.addOnClientReady(clientReadyState -> ready.set(clientReadyState));
PollingMode pollingMode = PollingModes.autoPoll(2);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, cache, logger, fetcher, hooks, false);
+ ConfigService policy = new ConfigService("", null, pollingMode, cache, logger, fetcher, hooks, false);
assertEquals(0, this.server.getRequestCount());
@@ -277,13 +275,13 @@ void testOnlineOffline() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test")));
PollingMode pollingMode = PollingModes.autoPoll(1);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
Thread.sleep(1500);
@@ -308,13 +306,13 @@ void testInitOffline() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test")));
PollingMode pollingMode = PollingModes.autoPoll(1);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), true);
+ ConfigService policy = new ConfigService("", null, pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), true);
assertTrue(policy.isOffline());
assertEquals(0, this.server.getRequestCount());
@@ -337,13 +335,13 @@ void testInitWaitTimeIgnoredWhenCacheIsNotExpired() throws Exception {
ConfigCache cache = new SingleValueCache(Helpers.cacheValueFromConfigJson(String.format(TEST_JSON, "test")));
PollingMode pollingMode = PollingModes.autoPoll(60, 1);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
long start = System.currentTimeMillis();
assertFalse(policy.getSettings().get().settings().isEmpty());
@@ -360,13 +358,13 @@ void testInitWaitTimeReturnCached() throws Exception {
ConfigCache cache = new SingleValueCache(Helpers.cacheValueFromConfigJsonAndTime(String.format(TEST_JSON, "test"), Constants.DISTANT_PAST));
PollingMode pollingMode = PollingModes.autoPoll(60, 1);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService policy = new ConfigService("", pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, pollingMode, cache, logger, fetcher, new ConfigCatHooks(), false);
long start = System.currentTimeMillis();
assertEquals("test", policy.getSettings().get().settings().get("fakeKey").getSettingsValue().getStringValue());
diff --git a/src/test/java/com/configcat/ConfigCatClientIntegrationTest.java b/src/test/java/com/configcat/ConfigCatClientIntegrationTest.java
index 3cef102..db815da 100644
--- a/src/test/java/com/configcat/ConfigCatClientIntegrationTest.java
+++ b/src/test/java/com/configcat/ConfigCatClientIntegrationTest.java
@@ -1,6 +1,5 @@
package com.configcat;
-import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
@@ -192,7 +191,8 @@ void invalidateCacheFail() {
@Test
void getConfigurationJsonStringWithDefaultConfigTimeout() {
- ConfigCatClient cl = ConfigCatClient.get("configcat-sdk-1/TEST_KEY1-123456789012/1234567890123456789012", options -> options.httpClient(new OkHttpClient.Builder().readTimeout(2, TimeUnit.SECONDS).build()));
+ ConfigCatClient cl = ConfigCatClient.get("configcat-sdk-1/TEST_KEY1-123456789012/1234567890123456789012",
+ options -> options.httpOptions().readTimeoutMillis(2000));
// makes a call to a real url which would fail, null expected
String config = cl.getValue(String.class, "test", null);
diff --git a/src/test/java/com/configcat/ConfigCatClientTest.java b/src/test/java/com/configcat/ConfigCatClientTest.java
index 3574fc9..abd2426 100644
--- a/src/test/java/com/configcat/ConfigCatClientTest.java
+++ b/src/test/java/com/configcat/ConfigCatClientTest.java
@@ -1,7 +1,6 @@
package com.configcat;
import java9.util.concurrent.CompletableFuture;
-import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.Test;
@@ -92,7 +91,7 @@ void testSDKKeyValidation() throws IOException {
@Test
void getValueWithDefaultConfigTimeout() throws IOException {
- ConfigCatClient cl = ConfigCatClient.get(Helpers.SDK_KEY, options -> options.httpClient(new OkHttpClient.Builder().readTimeout(2, TimeUnit.SECONDS).build()));
+ ConfigCatClient cl = ConfigCatClient.get(Helpers.SDK_KEY, options -> options.httpOptions().readTimeoutMillis(2000));
// makes a call to a real url which would fail, default expected
boolean config = cl.getValue(Boolean.class, "key", true);
@@ -183,7 +182,7 @@ void getConfigurationReturnsPreviousCachedOnTimeout() throws IOException {
server.start();
ConfigCatClient cl = ConfigCatClient.get(Helpers.SDK_KEY, options -> {
- options.httpClient(new OkHttpClient.Builder().readTimeout(1, TimeUnit.SECONDS).build());
+ options.httpOptions().readTimeoutMillis(1000);
options.pollingMode(PollingModes.manualPoll());
options.baseUrl(server.url("/").toString());
});
@@ -249,7 +248,7 @@ void getValueReturnsDefaultOnExceptionRepeatedly() throws IOException {
server.start();
ConfigCatClient cl = ConfigCatClient.get(Helpers.SDK_KEY, options -> {
- options.httpClient(new OkHttpClient.Builder().readTimeout(1, TimeUnit.SECONDS).build());
+ options.httpOptions().readTimeoutMillis(1000);
options.pollingMode(PollingModes.manualPoll());
options.baseUrl(server.url("/").toString());
});
@@ -275,7 +274,7 @@ void forceRefreshWithTimeout() throws IOException {
server.start();
ConfigCatClient cl = ConfigCatClient.get(Helpers.SDK_KEY, options -> {
- options.httpClient(new OkHttpClient.Builder().readTimeout(1, TimeUnit.SECONDS).build());
+ options.httpOptions().readTimeoutMillis(1000);
options.pollingMode(PollingModes.manualPoll());
options.baseUrl(server.url("/").toString());
});
diff --git a/src/test/java/com/configcat/ConfigFetcherTest.java b/src/test/java/com/configcat/ConfigFetcherTest.java
index 5ce6c3b..ce405e9 100644
--- a/src/test/java/com/configcat/ConfigFetcherTest.java
+++ b/src/test/java/com/configcat/ConfigFetcherTest.java
@@ -2,7 +2,6 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
@@ -45,7 +44,7 @@ void fetchNotModified() throws InterruptedException, ExecutionException, IOExcep
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(TEST_JSON).setHeader("ETag", "fakeETag"));
this.server.enqueue(new MockResponse().setResponseCode(304));
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger,
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger,
"", this.server.url("/").toString(), false, PollingModes.manualPoll().getPollingIdentifier());
FetchResponse fResult = fetcher.fetchAsync(null).get();
@@ -68,10 +67,7 @@ void fetchNotModified() throws InterruptedException, ExecutionException, IOExcep
@Test
void fetchException() throws IOException, ExecutionException, InterruptedException {
-
- ConfigFetcher fetch = new ConfigFetcher(new OkHttpClient.Builder()
- .readTimeout(1, TimeUnit.SECONDS)
- .build(),
+ ConfigFetcher fetch = new ConfigFetcher(new ConfigCatClient.HttpOptions().readTimeoutMillis(1000),
logger,
"",
this.server.url("/").toString(),
@@ -98,10 +94,10 @@ void fetchedETagNotUpdatesCache() throws Exception {
ConfigCache cache = mock(ConfigCache.class);
when(cache.read(anyString())).thenReturn(gson.toJson(entry));
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger,
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger,
"", this.server.url("/").toString(), false, PollingModes.manualPoll().getPollingIdentifier());
- ConfigService policy = new ConfigService("", PollingModes.autoPoll(2), cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, PollingModes.autoPoll(2), cache, logger, fetcher, new ConfigCatHooks(), false);
assertEquals("fakeValue", policy.getSettings().get().settings().get("fakeKey").getSettingsValue().getStringValue());
verify(cache, never()).write(anyString(), eq(TEST_JSON));
@@ -115,10 +111,10 @@ void fetchedSameResponseNotUpdatesCache() throws Exception {
ConfigCache cache = mock(ConfigCache.class);
when(cache.read(anyString())).thenReturn(TEST_JSON);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger,
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger,
"", this.server.url("/").toString(), false, PollingModes.manualPoll().getPollingIdentifier());
- ConfigService policy = new ConfigService("", PollingModes.autoPoll(2), cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService policy = new ConfigService("", null, PollingModes.autoPoll(2), cache, logger, fetcher, new ConfigCatHooks(), false);
assertEquals("fakeValue", policy.getSettings().get().settings().get("fakeKey").getSettingsValue().getStringValue());
verify(cache, never()).write(anyString(), eq(TEST_JSON));
@@ -134,7 +130,7 @@ void fetchSuccess() throws Exception {
doThrow(new Exception()).when(cache).read(anyString());
doThrow(new Exception()).when(cache).write(anyString(), anyString());
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger,
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger,
"", this.server.url("/").toString(), false, PollingModes.manualPoll().getPollingIdentifier());
FetchResponse response = fetcher.fetchAsync(null).get();
@@ -156,7 +152,7 @@ private static Stream emptyFetchTestData() {
void fetchEmpty(String body) throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(body));
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
@@ -172,9 +168,7 @@ void fetchEmpty(String body) throws Exception {
@Test
void testIntegration() throws IOException, ExecutionException, InterruptedException {
- ConfigFetcher fetch = new ConfigFetcher(new OkHttpClient.Builder()
- .readTimeout(1, TimeUnit.SECONDS)
- .build(),
+ ConfigFetcher fetch = new ConfigFetcher(new ConfigCatClient.HttpOptions().readTimeoutMillis(1000),
logger,
"PKDVCLf-Hq-h-kCzMp-L7Q/PaDVCFk9EpmD6sLpGLltTA",
"https://cdn-global.configcat.com",
@@ -189,14 +183,14 @@ void testIntegration() throws IOException, ExecutionException, InterruptedExcept
}
@Test
- public void fetchedFail403ContainsCFRAY() throws Exception {
+ void fetchedFail403ContainsCFRAY() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(403).setBody(TEST_JSON).setHeader("ETag", "fakeETag").setHeader("CF-RAY", "12345"));
Logger mockLogger = mock(Logger.class);
ConfigCatLogger localLogger = new ConfigCatLogger(mockLogger, LogLevel.DEBUG, null, null);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
localLogger,
"",
this.server.url("/").toString(),
@@ -214,14 +208,14 @@ public void fetchedFail403ContainsCFRAY() throws Exception {
}
@Test
- public void fetchedNotModified304ContainsCFRAY() throws Exception {
+ void fetchedNotModified304ContainsCFRAY() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(304).setHeader("CF-RAY", "12345"));
Logger mockLogger = mock(Logger.class);
ConfigCatLogger localLogger = new ConfigCatLogger(mockLogger, LogLevel.DEBUG, null, null);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
localLogger,
"",
this.server.url("/").toString(),
@@ -239,14 +233,14 @@ public void fetchedNotModified304ContainsCFRAY() throws Exception {
}
@Test
- public void fetchedReceivedInvalidBodyContainsCFRAY() throws Exception {
+ void fetchedReceivedInvalidBodyContainsCFRAY() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setHeader("CF-RAY", "12345").setBody("test"));
Logger mockLogger = mock(Logger.class);
ConfigCatLogger localLogger = new ConfigCatLogger(mockLogger, LogLevel.DEBUG, null, null);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
localLogger,
"",
this.server.url("/").toString(),
@@ -263,4 +257,45 @@ public void fetchedReceivedInvalidBodyContainsCFRAY() throws Exception {
fetcher.close();
}
+
+ @Test
+ void ensureStateMonitorWorks() throws IOException {
+ this.server.enqueue(new MockResponse().setResponseCode(200).setBody(TEST_JSON));
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger,
+ "", this.server.url("/").toString(), false, PollingModes.manualPoll().getPollingIdentifier());
+
+ TestStateMonitor monitor = new TestStateMonitor();
+ ConfigService service = new ConfigService("", monitor, PollingModes.autoPoll(), new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
+
+ assertFalse(service.isOffline());
+
+ monitor.setState(false);
+ monitor.notifyListeners();
+
+ assertTrue(service.isOffline());
+
+ monitor.setState(true);
+ monitor.notifyListeners();
+
+ assertFalse(service.isOffline());
+
+ service.setOffline();
+ assertTrue(service.isOffline());
+
+ monitor.setState(false);
+ monitor.notifyListeners();
+
+ assertTrue(service.isOffline());
+
+ monitor.setState(true);
+ monitor.notifyListeners();
+
+ assertTrue(service.isOffline());
+
+ service.setOnline();
+
+ assertFalse(service.isOffline());
+
+ service.close();
+ }
}
diff --git a/src/test/java/com/configcat/DataGovernanceTest.java b/src/test/java/com/configcat/DataGovernanceTest.java
index 43a98ac..87662fc 100644
--- a/src/test/java/com/configcat/DataGovernanceTest.java
+++ b/src/test/java/com/configcat/DataGovernanceTest.java
@@ -1,6 +1,5 @@
package com.configcat;
-import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.Test;
@@ -245,6 +244,6 @@ private MockWebServer createServer() throws IOException {
}
private ConfigFetcher createFetcher(String url, boolean isCustomUrl) {
- return new ConfigFetcher(new OkHttpClient.Builder().build(), logger, "", url, isCustomUrl, "m");
+ return new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger, "", url, isCustomUrl, "m");
}
}
diff --git a/src/test/java/com/configcat/LazyLoadingTest.java b/src/test/java/com/configcat/LazyLoadingTest.java
index 5318a7e..7654a90 100644
--- a/src/test/java/com/configcat/LazyLoadingTest.java
+++ b/src/test/java/com/configcat/LazyLoadingTest.java
@@ -1,6 +1,5 @@
package com.configcat;
-import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
@@ -28,8 +27,8 @@ public void setUp() throws IOException {
PollingMode mode = PollingModes
.lazyLoad(5);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
- this.policy = new ConfigService("", mode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
+ this.policy = new ConfigService("", null, mode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
}
@AfterEach
@@ -57,8 +56,8 @@ void get() throws InterruptedException, ExecutionException {
void getCacheFails() throws InterruptedException, ExecutionException {
PollingMode mode = PollingModes
.lazyLoad(5);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
- ConfigService lPolicy = new ConfigService("", mode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
+ ConfigService lPolicy = new ConfigService("", null, mode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test")));
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test2")).setBodyDelay(3, TimeUnit.SECONDS));
@@ -96,8 +95,8 @@ void testCacheExpirationRespectedInTTLCalc() throws InterruptedException, Execut
PollingMode mode = PollingModes
.lazyLoad(1);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
- ConfigService service = new ConfigService("", mode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
+ ConfigService service = new ConfigService("", null, mode, cache, logger, fetcher, new ConfigCatHooks(), false);
assertFalse(service.getSettings().get().settings().isEmpty());
assertFalse(service.getSettings().get().settings().isEmpty());
@@ -120,8 +119,8 @@ void testCacheExpirationRespectedInTTLCalc304() throws InterruptedException, Exe
PollingMode mode = PollingModes
.lazyLoad(1);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
- ConfigService service = new ConfigService("", mode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
+ ConfigService service = new ConfigService("", null, mode, cache, logger, fetcher, new ConfigCatHooks(), false);
assertFalse(service.getSettings().get().settings().isEmpty());
assertFalse(service.getSettings().get().settings().isEmpty());
@@ -142,13 +141,13 @@ void testOnlineOffline() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test")));
PollingMode pollingMode = PollingModes.lazyLoad(1);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService service = new ConfigService("", pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService service = new ConfigService("", null, pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
assertFalse(service.getSettings().get().settings().isEmpty());
assertEquals(1, this.server.getRequestCount());
@@ -176,13 +175,13 @@ void testInitOffline() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test")));
PollingMode pollingMode = PollingModes.lazyLoad(1);
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService service = new ConfigService("", pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), true);
+ ConfigService service = new ConfigService("", null, pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), true);
assertTrue(service.getSettings().get().settings().isEmpty());
assertEquals(0, this.server.getRequestCount());
diff --git a/src/test/java/com/configcat/ManualPollingTest.java b/src/test/java/com/configcat/ManualPollingTest.java
index 79b47d4..b8913ba 100644
--- a/src/test/java/com/configcat/ManualPollingTest.java
+++ b/src/test/java/com/configcat/ManualPollingTest.java
@@ -1,6 +1,5 @@
package com.configcat;
-import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
@@ -26,8 +25,8 @@ public void setUp() throws IOException {
this.server.start();
PollingMode mode = PollingModes.manualPoll();
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
- this.policy = new ConfigService("", mode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
+ this.policy = new ConfigService("", null, mode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
}
@AfterEach
@@ -53,8 +52,8 @@ void get() throws InterruptedException, ExecutionException {
@Test
void getCacheFails() throws InterruptedException, ExecutionException, IOException {
PollingMode mode = PollingModes.manualPoll();
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
- ConfigService lPolicy = new ConfigService("", mode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
+ ConfigService lPolicy = new ConfigService("", null, mode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test")));
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test2")).setBodyDelay(2, TimeUnit.SECONDS));
@@ -91,8 +90,8 @@ void testCache() throws InterruptedException, ExecutionException, IOException {
InMemoryCache cache = new InMemoryCache();
PollingMode mode = PollingModes.manualPoll();
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
- ConfigService service = new ConfigService("", mode, cache, logger, fetcher, new ConfigCatHooks(), false);
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
+ ConfigService service = new ConfigService("", null, mode, cache, logger, fetcher, new ConfigCatHooks(), false);
service.refresh().get();
assertEquals("test", service.getSettings().get().settings().get("fakeKey").getSettingsValue().getStringValue());
@@ -119,13 +118,13 @@ void testOnlineOffline() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test")));
PollingMode pollingMode = PollingModes.manualPoll();
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService service = new ConfigService("", pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
+ ConfigService service = new ConfigService("", null, pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), false);
assertFalse(service.isOffline());
assertTrue(service.refresh().get().isSuccess());
@@ -152,13 +151,13 @@ void testInitOffline() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test")));
PollingMode pollingMode = PollingModes.manualPoll();
- ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient(),
+ ConfigFetcher fetcher = new ConfigFetcher(new ConfigCatClient.HttpOptions(),
logger,
"",
this.server.url("/").toString(),
false,
pollingMode.getPollingIdentifier());
- ConfigService service = new ConfigService("", pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), true);
+ ConfigService service = new ConfigService("", null, pollingMode, new NullConfigCache(), logger, fetcher, new ConfigCatHooks(), true);
assertTrue(service.isOffline());
assertFalse(service.refresh().get().isSuccess());
diff --git a/src/test/java/com/configcat/TestStateMonitor.java b/src/test/java/com/configcat/TestStateMonitor.java
new file mode 100644
index 0000000..a34cba7
--- /dev/null
+++ b/src/test/java/com/configcat/TestStateMonitor.java
@@ -0,0 +1,55 @@
+package com.configcat;
+
+import java9.util.function.Consumer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class TestStateMonitor implements StateMonitor {
+ private final List> stateChangeListeners = new ArrayList<>();
+ private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
+ private final AtomicBoolean state = new AtomicBoolean(true);
+
+ public void setState(boolean state) {
+ this.state.set(state);
+ }
+
+ public void notifyListeners() {
+ lock.readLock().lock();
+ try {
+ for (Consumer listener : this.stateChangeListeners) {
+ listener.accept(this.state.get());
+ }
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void addStateChangeListener(Consumer listener) {
+ lock.writeLock().lock();
+ try {
+ this.stateChangeListeners.add(listener);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public boolean isNetworkAvailable() {
+ return this.state.get();
+ }
+
+ @Override
+ public void close() throws IOException {
+ lock.writeLock().lock();
+ try {
+ this.stateChangeListeners.clear();
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+}
diff --git a/src/test/java/com/configcat/VariationIdTests.java b/src/test/java/com/configcat/VariationIdTests.java
index 044eccc..0b2df91 100644
--- a/src/test/java/com/configcat/VariationIdTests.java
+++ b/src/test/java/com/configcat/VariationIdTests.java
@@ -1,6 +1,5 @@
package com.configcat;
-import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
@@ -27,7 +26,6 @@ public void setUp() throws IOException {
this.server.start();
this.client = ConfigCatClient.get(Helpers.SDK_KEY, options -> {
- options.httpClient(new OkHttpClient.Builder().build());
options.pollingMode(PollingModes.lazyLoad(2));
options.baseUrl(this.server.url("/").toString());
});