diff --git a/demo/src/main/kotlin/dev/hotwire/turbo/demo/main/MainActivity.kt b/demo/src/main/kotlin/dev/hotwire/turbo/demo/main/MainActivity.kt index 4d1122fd..122ab44a 100644 --- a/demo/src/main/kotlin/dev/hotwire/turbo/demo/main/MainActivity.kt +++ b/demo/src/main/kotlin/dev/hotwire/turbo/demo/main/MainActivity.kt @@ -1,8 +1,11 @@ package dev.hotwire.turbo.demo.main import android.os.Bundle +import android.webkit.WebView import androidx.appcompat.app.AppCompatActivity +import dev.hotwire.turbo.BuildConfig import dev.hotwire.turbo.activities.TurboActivity +import dev.hotwire.turbo.config.Turbo import dev.hotwire.turbo.delegates.TurboActivityDelegate import dev.hotwire.turbo.demo.R @@ -14,5 +17,13 @@ class MainActivity : AppCompatActivity(), TurboActivity { setContentView(R.layout.activity_main) delegate = TurboActivityDelegate(this, R.id.main_nav_host) + configApp() + } + + private fun configApp() { + if (BuildConfig.DEBUG) { + Turbo.config.debugLoggingEnabled = true + WebView.setWebContentsDebuggingEnabled(true) + } } } diff --git a/demo/src/main/kotlin/dev/hotwire/turbo/demo/main/MainSessionNavHostFragment.kt b/demo/src/main/kotlin/dev/hotwire/turbo/demo/main/MainSessionNavHostFragment.kt index 0b7ac31b..50d02ef8 100644 --- a/demo/src/main/kotlin/dev/hotwire/turbo/demo/main/MainSessionNavHostFragment.kt +++ b/demo/src/main/kotlin/dev/hotwire/turbo/demo/main/MainSessionNavHostFragment.kt @@ -1,9 +1,7 @@ package dev.hotwire.turbo.demo.main -import android.webkit.WebView import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import dev.hotwire.turbo.BuildConfig import dev.hotwire.turbo.config.TurboPathConfiguration import dev.hotwire.turbo.demo.features.imageviewer.ImageViewerFragment import dev.hotwire.turbo.demo.features.numbers.NumberBottomSheetFragment @@ -13,6 +11,7 @@ import dev.hotwire.turbo.demo.features.web.WebFragment import dev.hotwire.turbo.demo.features.web.WebHomeFragment import dev.hotwire.turbo.demo.features.web.WebModalFragment import dev.hotwire.turbo.demo.util.HOME_URL +import dev.hotwire.turbo.demo.util.customUserAgent import dev.hotwire.turbo.demo.util.initDayNightTheme import dev.hotwire.turbo.session.TurboSessionNavHostFragment import kotlin.reflect.KClass @@ -44,16 +43,7 @@ class MainSessionNavHostFragment : TurboSessionNavHostFragment() { override fun onSessionCreated() { super.onSessionCreated() - session.webView.settings.userAgentString = customUserAgent(session.webView) + session.webView.settings.userAgentString = session.webView.customUserAgent session.webView.initDayNightTheme() - - if (BuildConfig.DEBUG) { - session.setDebugLoggingEnabled(true) - WebView.setWebContentsDebuggingEnabled(true) - } - } - - private fun customUserAgent(webView: WebView): String { - return "Turbo Native Android ${webView.settings.userAgentString}" } } diff --git a/demo/src/main/kotlin/dev/hotwire/turbo/demo/util/Extensions.kt b/demo/src/main/kotlin/dev/hotwire/turbo/demo/util/Extensions.kt index 4a830e9c..a0da63e4 100644 --- a/demo/src/main/kotlin/dev/hotwire/turbo/demo/util/Extensions.kt +++ b/demo/src/main/kotlin/dev/hotwire/turbo/demo/util/Extensions.kt @@ -8,6 +8,7 @@ import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat import androidx.webkit.WebSettingsCompat import androidx.webkit.WebViewFeature +import dev.hotwire.turbo.config.Turbo import dev.hotwire.turbo.config.TurboPathConfigurationProperties import dev.hotwire.turbo.demo.R @@ -34,6 +35,12 @@ fun WebView.initDayNightTheme() { } } +val WebView.customUserAgent: String + get() { + val turboSubstring = Turbo.userAgentSubstring() + return "$turboSubstring; ${settings.userAgentString}" + } + private fun isNightModeEnabled(context: Context): Boolean { val currentNightMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK return currentNightMode == Configuration.UI_MODE_NIGHT_YES diff --git a/docs/ADVANCED-OPTIONS.md b/docs/ADVANCED-OPTIONS.md index e4e2d731..5e27ec28 100644 --- a/docs/ADVANCED-OPTIONS.md +++ b/docs/ADVANCED-OPTIONS.md @@ -99,20 +99,11 @@ You may encounter situations where a truly single-`Activity` app may not be feas In such cases, you need to create an additional `Activity` that also implements the `TurboActivity` interface. You will need to be sure to register each `Activity` by calling [`TurboSessionNavHostFragment.registeredActivities()`](../turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSessionNavHostFragment.kt) so that you can navigate between them. ## Enable Debug Logging -During development, you may want to see what `turbo-android` is doing behind the scenes. To enable debug logging, override the `onSessionCreated()` method in your `NavHostFragment` and call `session.setDebugLoggingEnabled(true)`. Debug logging should always be disabled in your production app. For example: +During development, you may want to see what `turbo-android` is doing behind the scenes. To enable debug logging, call `Turbo.config.debugLoggingEnabled = true`. Debug logging should always be disabled in your production app. For example: ```kotlin -class MainSessionNavHostFragment : TurboSessionNavHostFragment() { - - // ... - - override open fun onSessionCreated() { - super.onSessionCreated() - - if (BuildConfig.DEBUG) { - session.setDebugLoggingEnabled(true) - } - } +if (BuildConfig.DEBUG) { + Turbo.config.debugLoggingEnabled = true } ``` diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/config/Turbo.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/config/Turbo.kt new file mode 100644 index 00000000..eed69c1a --- /dev/null +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/config/Turbo.kt @@ -0,0 +1,13 @@ +package dev.hotwire.turbo.config + +object Turbo { + val config: TurboConfig = TurboConfig() + + /** + * Provides a standard substring to be included in your WebView's user agent + * to identify itself as a Turbo Native app. + */ + fun userAgentSubstring(): String { + return "Turbo Native Android" + } +} diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboConfig.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboConfig.kt new file mode 100644 index 00000000..146139f7 --- /dev/null +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboConfig.kt @@ -0,0 +1,16 @@ +package dev.hotwire.turbo.config + +import dev.hotwire.turbo.http.TurboHttpClient + +class TurboConfig internal constructor() { + /** + * Enables/disables debug logging. This should be disabled in production environments. + * Disabled by default. + * + */ + var debugLoggingEnabled = false + set(value) { + field = value + TurboHttpClient.reset() + } +} diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboPathConfigurationRule.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboPathConfigurationRule.kt index 7242a142..035efe43 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboPathConfigurationRule.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/config/TurboPathConfigurationRule.kt @@ -3,6 +3,7 @@ package dev.hotwire.turbo.config import dev.hotwire.turbo.BuildConfig import dev.hotwire.turbo.util.TurboLog import com.google.gson.annotations.SerializedName +import dev.hotwire.turbo.util.logError import java.util.regex.PatternSyntaxException internal data class TurboPathConfigurationRule( @@ -17,7 +18,7 @@ internal data class TurboPathConfigurationRule( private fun numberOfMatches(path: String, patternRegex: String): Int = try { Regex(patternRegex, RegexOption.IGNORE_CASE).find(path)?.groups?.size ?: 0 } catch (e: PatternSyntaxException) { - TurboLog.e("PathConfiguration pattern error: ${e.description}") + logError("pathConfigurationPatternError", e) if (BuildConfig.DEBUG) throw e else 0 } } diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboCameraCaptureDelegate.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboCameraCaptureDelegate.kt index d9da1686..e354db30 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboCameraCaptureDelegate.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboCameraCaptureDelegate.kt @@ -7,6 +7,7 @@ import android.provider.MediaStore import android.webkit.WebChromeClient.FileChooserParams import dev.hotwire.turbo.util.TurboFileProvider import dev.hotwire.turbo.util.TurboLog +import dev.hotwire.turbo.util.logError import java.io.File import java.io.IOException @@ -48,7 +49,7 @@ internal class TurboCameraCaptureDelegate(val context: Context) { val directory: File = TurboFileProvider.directory(context) return File.createTempFile("Capture_", ".jpg", directory) } catch (e: IOException) { - TurboLog.e("${e.message}") + logError("createTempFileError", e) null } } diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboFileChooserDelegate.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboFileChooserDelegate.kt index 5c335627..fd184f5e 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboFileChooserDelegate.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/delegates/TurboFileChooserDelegate.kt @@ -13,6 +13,7 @@ import dev.hotwire.turbo.util.TURBO_REQUEST_CODE_FILES import dev.hotwire.turbo.util.TurboFileProvider import dev.hotwire.turbo.util.TurboLog import dev.hotwire.turbo.util.dispatcherProvider +import dev.hotwire.turbo.util.logError import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -72,7 +73,7 @@ internal class TurboFileChooserDelegate(val session: TurboSession) : CoroutineSc destination.activityResultLauncher(TURBO_REQUEST_CODE_FILES)?.launch(intent) true } catch (e: Exception) { - TurboLog.e("${e.message}") + logError("startIntentError", e) false } } diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/http/TurboHttpClient.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/http/TurboHttpClient.kt index 4b0be832..7197ddaa 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/http/TurboHttpClient.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/http/TurboHttpClient.kt @@ -1,7 +1,9 @@ package dev.hotwire.turbo.http import android.content.Context +import dev.hotwire.turbo.config.Turbo import dev.hotwire.turbo.util.TurboLog +import dev.hotwire.turbo.util.logError import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor @@ -28,7 +30,7 @@ object TurboHttpClient { try { cache?.evictAll() } catch (e: IOException) { - TurboLog.e(e.toString()) + logError("invalidateCacheError", e) } } @@ -56,7 +58,7 @@ object TurboHttpClient { builder.cache(it) } - if (TurboLog.enableDebugLogging) { + if (Turbo.config.debugLoggingEnabled) { builder.addInterceptor(loggingInterceptor) } diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/http/TurboHttpRepository.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/http/TurboHttpRepository.kt index 04b652ed..4d78cf72 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/http/TurboHttpRepository.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/http/TurboHttpRepository.kt @@ -5,6 +5,7 @@ import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import dev.hotwire.turbo.util.TurboLog import dev.hotwire.turbo.util.dispatcherProvider +import dev.hotwire.turbo.util.logError import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore @@ -105,7 +106,7 @@ internal class TurboHttpRepository(private val coroutineScope: CoroutineScope) { } catch (e: IOException) { throw e } catch (e: Exception) { - TurboLog.e("Request error: ${e.message}") + logError("httpRequestError", e) null } } @@ -211,7 +212,7 @@ internal class TurboHttpRepository(private val coroutineScope: CoroutineScope) { return try { response?.body?.byteStream() } catch (e: Exception) { - TurboLog.e("Byte stream error: ${e.message}") + logError("byteStreamError", e) null } } diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt index dbc11dd7..6284bf31 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt @@ -125,17 +125,6 @@ class TurboSession internal constructor( isColdBooting = false } - /** - * Enables/disables debug logging. This should be disabled in production environments. - * Disabled by default. - * - * @param enabled Whether to enable debug logging. - */ - fun setDebugLoggingEnabled(enabled: Boolean) { - TurboLog.enableDebugLogging = enabled - TurboHttpClient.reset() - } - // Internal internal fun visit(visit: TurboVisit) { diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/util/TurboLog.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/util/TurboLog.kt index ffc3ad92..adf9bed8 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/util/TurboLog.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/util/TurboLog.kt @@ -1,19 +1,21 @@ package dev.hotwire.turbo.util import android.util.Log +import dev.hotwire.turbo.config.Turbo internal object TurboLog { private const val DEFAULT_TAG = "TurboLog" - internal var enableDebugLogging = false - fun d(msg: String) = log(Log.DEBUG, DEFAULT_TAG, msg) + private val debugEnabled get() = Turbo.config.debugLoggingEnabled - fun e(msg: String) = log(Log.ERROR, DEFAULT_TAG, msg) + internal fun d(msg: String) = log(Log.DEBUG, msg) - private fun log(logLevel: Int, tag: String, msg: String) { + internal fun e(msg: String) = log(Log.ERROR, msg) + + private fun log(logLevel: Int, msg: String) { when (logLevel) { - Log.DEBUG -> if (enableDebugLogging) Log.d(tag, msg) - Log.ERROR -> Log.e(tag, msg) + Log.DEBUG -> if (debugEnabled) Log.d(DEFAULT_TAG, msg) + Log.ERROR -> Log.e(DEFAULT_TAG, msg) } } } @@ -24,3 +26,7 @@ internal fun logEvent(event: String, attributes: List>) { } TurboLog.d("$event ".padEnd(35, '.') + " $description") } + +internal fun logError(event: String, error: Exception) { + TurboLog.e("$event: ${error.stackTraceToString()}") +} diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/util/TurboUriHelper.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/util/TurboUriHelper.kt index e8b523e7..c6ee6f6e 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/util/TurboUriHelper.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/util/TurboUriHelper.kt @@ -32,7 +32,7 @@ internal class TurboUriHelper(val context: Context) { } file } catch (e: Exception) { - TurboLog.e("${e.message}") + logError("writeFileError", e) null } } @@ -123,7 +123,7 @@ internal class TurboUriHelper(val context: Context) { !outputFilePath.startsWith(destinationDirectoryPath) } catch (e: Exception) { - TurboLog.e("${e.message}") + logError("canonicalPathError", e) false } } @@ -136,7 +136,7 @@ internal class TurboUriHelper(val context: Context) { return try { canonicalPath.contains(context.packageName) } catch (e: IOException) { - TurboLog.e("${e.message}") + logError("canonicalPathError", e) false } }