Skip to content

Commit

Permalink
Add return type
Browse files Browse the repository at this point in the history
  • Loading branch information
FranAguilera committed Feb 12, 2025
1 parent c6a6fa7 commit 5e6f1a7
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 40 deletions.
50 changes: 38 additions & 12 deletions platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/Capture.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@ import io.bitdrift.capture.providers.FieldProvider
import io.bitdrift.capture.providers.SystemDateProvider
import io.bitdrift.capture.providers.session.SessionStrategy
import okhttp3.HttpUrl
import org.jetbrains.annotations.VisibleForTesting
import java.util.concurrent.atomic.AtomicReference
import kotlin.time.Duration

internal sealed class LoggerState {
/**
* Represents the different Logger States
*/
sealed class LoggerState {
/**
* The logger has not yet been started.
*/
data object NotStarted : LoggerState()
internal data object NotStarted : LoggerState()

/**
* The logger is in the process of being started. Subsequent attempts to start the logger will be ignored.
Expand All @@ -37,13 +41,18 @@ internal sealed class LoggerState {
* The logger has been successfully started and is ready for use. Subsequent attempts to start the logger will be ignored.
*/
class Started(
val logger: LoggerImpl,
internal val logger: ILogger,
) : LoggerState()

/**
* An attempt to start the logger was made but failed. Subsequent attempts to start the logger will be ignored.
*/
data object StartFailure : LoggerState()
data class StartFailure(
/**
* Provides the reason for the start failure
*/
val sdkNotStartedError: SdkNotStartedError,
) : LoggerState()
}

/**
Expand Down Expand Up @@ -116,7 +125,7 @@ object Capture {
fieldProviders: List<FieldProvider> = listOf(),
dateProvider: DateProvider? = null,
apiUrl: HttpUrl = defaultCaptureApiUrl,
) {
): LoggerState =
start(
apiKey,
sessionStrategy,
Expand All @@ -126,7 +135,6 @@ object Capture {
apiUrl,
CaptureJniLibrary,
)
}

@Synchronized
@JvmStatic
Expand All @@ -139,18 +147,27 @@ object Capture {
dateProvider: DateProvider? = null,
apiUrl: HttpUrl = defaultCaptureApiUrl,
bridge: IBridge,
) {
): LoggerState {
// Note that we need to use @Synchronized to prevent multiple loggers from being initialized,
// while subsequent logger access relies on volatile reads.

// There's nothing we can do if we don't have yet access to the application context.
if (!ContextHolder.isInitialized) {
val errorMessage =
"Attempted to initialize Capture before having a valid ApplicationContext"
Log.w(
"capture",
"Attempted to initialize Capture before androidx.startup.Initializers " +
"are run. Aborting logger initialization.",
errorMessage,
)
return
val startFailureMissingAppContext = buildStartFailure(errorMessage)
default.set(startFailureMissingAppContext)
return startFailureMissingAppContext
}

if (apiKey.isEmpty()) {
val apiKeyError = buildStartFailure("API key is empty")
default.set(apiKeyError)
return apiKeyError
}

// Ideally we would use `getAndUpdate` in here but it's available for API 24 and up only.
Expand All @@ -169,11 +186,12 @@ object Capture {
default.set(LoggerState.Started(logger))
} catch (e: Throwable) {
Log.w("capture", "Failed to start Capture", e)
default.set(LoggerState.StartFailure)
default.set(LoggerState.StartFailure(SdkNotStartedError()))
}
} else {
Log.w("capture", "Multiple attempts to start Capture")
}
return default.get()
}

/**
Expand Down Expand Up @@ -227,7 +245,7 @@ object Capture {
mainThreadHandler.run { completion(it) }
}
} ?: run {
mainThreadHandler.run { completion(Err(SdkNotStartedError)) }
mainThreadHandler.run { completion(Err(SdkNotStartedError())) }
}
}

Expand Down Expand Up @@ -460,5 +478,13 @@ object Capture {
internal fun resetShared() {
default.set(LoggerState.NotStarted)
}

private fun buildStartFailure(errorMessage: String): LoggerState.StartFailure =
LoggerState.StartFailure(SdkNotStartedError(errorMessage))
}

@VisibleForTesting
internal fun resetLoggerStateForTest() {
default.set(LoggerState.NotStarted)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ internal class LoggerImpl(
* `SharedPreferences`.
*/
deviceCodeService.createTemporaryDeviceCode(deviceId, completion)
} ?: completion(Err(SdkNotStartedError))
} ?: completion(Err(SdkNotStartedError()))
}

private fun appExitSaveCurrentSessionId(sessionId: String? = null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ sealed class ApiError(
/**
* Represents a failed operation due to the SDK not being started
*/
data object SdkNotStartedError : Error("SDK not started")
data class SdkNotStartedError(
override val message: String = "SDK not started",
) : Error(message)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.bitdrift.capture.network.HttpResponseInfo
import io.bitdrift.capture.network.HttpUrlPath
import io.bitdrift.capture.providers.session.SessionStrategy
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -26,18 +27,28 @@ import org.robolectric.annotation.Config
@Config(sdk = [21])
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class CaptureTest {
@Before
fun setup() {
Capture.resetLoggerStateForTest()
}

// This Test needs to run first since the following tests need to initialize
// the ContextHolder before they can run.
@Test
fun aConfigureSkipsLoggerCreationWhenContextNotInitialized() {
assertThat(Capture.logger()).isNull()

Logger.start(
apiKey = "test1",
sessionStrategy = SessionStrategy.Fixed(),
dateProvider = null,
)

val loggerState =
Logger.start(
apiKey = "test1",
sessionStrategy = SessionStrategy.Fixed(),
dateProvider = null,
)
assertThat(loggerState is LoggerState.StartFailure).isTrue()
if (loggerState is LoggerState.StartFailure) {
assertThat(loggerState.sdkNotStartedError.message)
.isEqualTo("Attempted to initialize Capture before having a valid ApplicationContext")
}
assertThat(Capture.logger()).isNull()
}

Expand Down Expand Up @@ -69,26 +80,35 @@ class CaptureTest {
fun cIdempotentConfigure() {
val initializer = ContextHolder()
initializer.create(ApplicationProvider.getApplicationContext())

assertThat(ContextHolder.isInitialized).isTrue()
assertThat(Capture.logger()).isNull()

Logger.start(
apiKey = "test1",
sessionStrategy = SessionStrategy.Fixed(),
dateProvider = null,
)
val loggerStateStarted =
Logger.start(
apiKey = "test1",
sessionStrategy = SessionStrategy.Fixed(),
dateProvider = null,
)
assertLoggerStateStarted(loggerStateStarted)

val logger = Capture.logger()
assertThat(logger).isNotNull()
assertThat(Logger.deviceId).isNotNull()
val updatedLoggerState =
Logger.start(
apiKey = "test2",
sessionStrategy = SessionStrategy.Fixed(),
dateProvider = null,
)

Logger.start(
apiKey = "test2",
sessionStrategy = SessionStrategy.Fixed(),
dateProvider = null,
)
// Calling reconfigure a second time does not change the logger state
assertLoggerStateStarted(updatedLoggerState)
assertThat(updatedLoggerState).isEqualTo(loggerStateStarted)
}

// Calling reconfigure a second time does not change the static logger.
assertThat(logger).isEqualTo(Capture.logger())
private fun assertLoggerStateStarted(loggerState: LoggerState) {
assertThat(loggerState is LoggerState.Started).isTrue()
if (loggerState is LoggerState.Started) {
assertThat(loggerState.logger).isNotNull()
assertThat(loggerState.logger.deviceId).isNotNull()
assertThat(Capture.logger()).isEqualTo(loggerState.logger)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import io.bitdrift.capture.Capture;
import io.bitdrift.capture.Configuration;
import io.bitdrift.capture.LoggerState;
import io.bitdrift.capture.providers.FieldProvider;
import io.bitdrift.capture.providers.session.SessionStrategy;
import io.bitdrift.capture.replay.SessionReplayConfiguration;
Expand All @@ -20,7 +21,7 @@
import okhttp3.HttpUrl;

public class BitdriftInit {
public static void initBitdriftCaptureInJava(HttpUrl apiUrl, String apiKey) {
public static LoggerState initBitdriftCaptureInJava(HttpUrl apiUrl, String apiKey) {
String userID = UUID.randomUUID().toString();
List<FieldProvider> fieldProviders = new ArrayList<>();
fieldProviders.add(() -> {
Expand All @@ -29,7 +30,7 @@ public static void initBitdriftCaptureInJava(HttpUrl apiUrl, String apiKey) {
return fields;
});

Capture.Logger.start(
return Capture.Logger.start(
apiKey,
new SessionStrategy.ActivityBased(),
new Configuration(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ import android.content.Context
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import io.bitdrift.capture.Capture
import io.bitdrift.capture.Capture.Logger.sessionUrl
import io.bitdrift.capture.LogLevel
import io.bitdrift.capture.LoggerState
import io.bitdrift.capture.events.span.Span
import io.bitdrift.capture.events.span.SpanResult
import io.bitdrift.capture.timber.CaptureTree
Expand Down Expand Up @@ -87,10 +89,14 @@ class GradleTestApp : Application() {
val stringApiUrl = prefs.getString("apiUrl", null)
val apiUrl = stringApiUrl?.toHttpUrlOrNull()
if (apiUrl == null) {
Log.e("GradleTestApp", "Failed to initialize bitdrift logger due to invalid API URL: $stringApiUrl")
val invalidUrlMessage = "Failed to initialize bitdrift logger due to invalid API URL: $stringApiUrl"
notifyError(invalidUrlMessage)
return
}
BitdriftInit.initBitdriftCaptureInJava(apiUrl, prefs.getString("apiKey", ""))
val loggerState = BitdriftInit.initBitdriftCaptureInJava(apiUrl, prefs.getString("apiKey", ""))
if(loggerState is LoggerState.StartFailure){
notifyError(loggerState.sdkNotStartedError.message)
}
// Timber
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
Expand All @@ -99,6 +105,11 @@ class GradleTestApp : Application() {
Timber.i("Bitdrift Logger initialized with session_url=$sessionUrl")
}

private fun notifyError(message:String){
Log.e("GradleTestApp", message)
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}

private fun trackAppLaunch() {
// ApplicationStartInfo
if (Build.VERSION.SDK_INT >= 35) {
Expand Down

0 comments on commit 5e6f1a7

Please sign in to comment.