Skip to content
Merged
29 changes: 29 additions & 0 deletions config/detekt/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ID>CommentSpacing:com.qonversion.android.sdk.internal.QIdentityManagerTest.kt:171</ID>
<ID>ComplexMethod:ScreenPresenter.kt$ScreenPresenter$override fun onWebViewMessageReceived(message: String)</ID>
<ID>ComplexMethod:ScreenPresenter.kt$ScreenPresenter$private fun mapProductInfo(product: QProduct): Map&lt;String, Any?&gt;</ID>
<ID>ComplexMethod:ScreenServiceImpl.kt$ScreenServiceImpl$private suspend fun executeWithFallback( requestProvider: () -&gt; Request, fallbackProvider: suspend () -&gt; NoCodeScreen?, errorContext: String, methodName: String ): NoCodeScreen</ID>
<ID>ComplexMethod:errors.kt$internal fun BillingError.toQonversionError(): QonversionError</ID>
<ID>ConstructorParameterNaming:Environment.kt$Environment$@Json(name = "app_version") val app_version: String</ID>
<ID>EmptyCatchBlock:AdvertisingProvider.kt$AdvertisingProvider.AdvertisingConnection${ }</ID>
Expand All @@ -25,6 +26,8 @@
<ID>FinalNewline:com.qonversion.android.sdk.internal.storage.PurchasesCacheTest.kt:1</ID>
<ID>FinalNewline:com.qonversion.android.sdk.internal.storage.util.kt:1</ID>
<ID>FinalNewline:com.qonversion.android.sdk.utils.kt:1</ID>
<ID>FinalNewline:io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt:1</ID>
<ID>FinalNewline:io.qonversion.nocodes.internal.utils.ErrorUtilsTest.kt:1</ID>
<ID>ImplicitDefaultLocale:HomeFragment.kt$HomeFragment$String.format( "%s %s", getStr(R.string.buy_for), inApp.prettyPrice )</ID>
<ID>LargeClass:QProductCenterManager.kt$QProductCenterManager : PurchasesListenerUserStateProvider</ID>
<ID>LongParameterList:IBillingClientWrapper.kt$IBillingClientWrapper$( activity: Activity, product: QProduct, offerId: String?, applyOffer: Boolean? = true, updatePurchaseInfo: UpdatePurchaseInfo?, onFailed: (error: BillingError) -&gt; Unit )</ID>
Expand Down Expand Up @@ -79,6 +82,7 @@
<ID>MatchingDeclarationName:util.kt$Util</ID>
<ID>MaxLineLength:ApiErrorMapper.kt$ApiErrorMapper$20201 -&gt; "For more details please check our guide [Troubleshooting](https://documentation.qonversion.io/docs/troubleshooting)"</ID>
<ID>MaxLineLength:ApiErrorMapper.kt$ApiErrorMapper$20203 -&gt; "Possible reasons for this error are fraud purchases and incorrect configuration of the project key in the Qonversion Dashboard"</ID>
<ID>MaxLineLength:FallbackServiceTest.kt$FallbackServiceTest$assertNotNull(FallbackService::class.java.getDeclaredMethod("isFallbackFileAvailable", String::class.java, Context::class.java))</ID>
<ID>MaxLineLength:GooglePurchaseConverter.kt$GooglePurchaseConverter$override</ID>
<ID>MaxLineLength:LaunchResultCacheWrapperTest.kt$LaunchResultCacheWrapperTest$cacheWrapper = LaunchResultCacheWrapper(mockMoshi, mockPrefsCache, mockCacheConfigProvider, mockQFallbacksService)</ID>
<ID>MaxLineLength:OutagerIntegrationTest.kt$OutagerIntegrationTest$"lgeigljfpmeoddkcebkcepjc.AO-J1Oy305qZj99jXTPEVBN8UZGoYAtjDLj4uTjRQvUFaG0vie-nr6VBlN0qnNDMU8eJR-sI7o3CwQyMOEHKl8eJsoQ86KSFzxKBR07PSpHLI_o7agXhNKY"</ID>
Expand Down Expand Up @@ -146,14 +150,18 @@
<ID>MaximumLineLength:com.qonversion.android.sdk.internal.storage.util.kt:138</ID>
<ID>MaximumLineLength:com.qonversion.android.sdk.internal.storage.util.kt:140</ID>
<ID>MaximumLineLength:io.qonversion.nocodes.internal.screen.controller.ScreenControllerImpl.kt:54</ID>
<ID>MaximumLineLength:io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt:43</ID>
<ID>MaximumLineLength:io.qonversion.nocodes.internal.screen.view.ScreenFragment.kt:197</ID>
<ID>MaximumLineLength:io.qonversion.nocodes.internal.screen.view.ScreenPresenter.kt:64</ID>
<ID>MaximumLineLength:io.qonversion.sample.utils.kt:49</ID>
<ID>NestedBlockDepth:ActionMapper.kt$ActionMapper$override fun fromMap(data: Map&lt;*, *&gt;): QAction</ID>
<ID>NestedBlockDepth:ApiErrorMapper.kt$ApiErrorMapper$fun &lt;T&gt; getErrorFromResponse(value: Response&lt;T&gt;): QonversionError</ID>
<ID>NestedBlockDepth:ScreenPresenter.kt$ScreenPresenter$private fun mapProductInfo(product: QProduct): Map&lt;String, Any?&gt;</ID>
<ID>NestedBlockDepth:ScreenServiceImpl.kt$ScreenServiceImpl$private suspend fun executeWithFallback( requestProvider: () -&gt; Request, fallbackProvider: suspend () -&gt; NoCodeScreen?, errorContext: String, methodName: String ): NoCodeScreen</ID>
<ID>NewLineAtEndOfFile:ApiHelperTest.kt$com.qonversion.android.sdk.internal.api.ApiHelperTest.kt</ID>
<ID>NewLineAtEndOfFile:AppRequestTest.kt$com.qonversion.android.sdk.internal.requests.AppRequestTest.kt</ID>
<ID>NewLineAtEndOfFile:ErrorUtilsTest.kt$io.qonversion.nocodes.internal.utils.ErrorUtilsTest.kt</ID>
<ID>NewLineAtEndOfFile:FallbackServiceTest.kt$io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt</ID>
<ID>NewLineAtEndOfFile:IncrementalDelayCalculatorTest.kt$com.qonversion.android.sdk.internal.IncrementalDelayCalculatorTest.kt</ID>
<ID>NewLineAtEndOfFile:InternalConfigTest.kt$com.qonversion.android.sdk.internal.InternalConfigTest.kt</ID>
<ID>NewLineAtEndOfFile:OsRequestTest.kt$com.qonversion.android.sdk.internal.requests.OsRequestTest.kt</ID>
Expand All @@ -169,6 +177,15 @@
<ID>NoConsecutiveBlankLines:com.qonversion.android.sdk.QonversionConfigTest.kt:144</ID>
<ID>NoConsecutiveBlankLines:com.qonversion.android.sdk.internal.QAttributionManagerTest.kt:141</ID>
<ID>NoConsecutiveBlankLines:com.qonversion.android.sdk.internal.requests.ProviderDataRequestTest.kt:18</ID>
<ID>NoTrailingSpaces:io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt:66</ID>
<ID>NoTrailingSpaces:io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt:69</ID>
<ID>NoTrailingSpaces:io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt:72</ID>
<ID>NoTrailingSpaces:io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt:77</ID>
<ID>NoTrailingSpaces:io.qonversion.nocodes.internal.utils.ErrorUtilsTest.kt:89</ID>
<ID>NoTrailingSpaces:io.qonversion.nocodes.internal.utils.FallbackUtils.kt:17</ID>
<ID>NoUnusedImports:io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt:14</ID>
<ID>NoUnusedImports:io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt:15</ID>
<ID>NoUnusedImports:io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt:4</ID>
<ID>NoWildcardImports:com.qonversion.android.sdk.dto.products.QProduct.kt:3</ID>
<ID>NoWildcardImports:com.qonversion.android.sdk.internal.AdvertisingProvider.kt:7</ID>
<ID>NoWildcardImports:com.qonversion.android.sdk.internal.EnvironmentProvider.kt:11</ID>
Expand All @@ -192,8 +209,11 @@
<ID>NoWildcardImports:com.qonversion.android.sdk.internal.storage.SharedPreferencesCacheTest.kt:5</ID>
<ID>NoWildcardImports:com.qonversion.android.sdk.internal.storage.util.kt:24</ID>
<ID>NoWildcardImports:com.qonversion.android.sdk.utils.kt:4</ID>
<ID>NoWildcardImports:io.qonversion.nocodes.internal.screen.service.FallbackServiceTest.kt:11</ID>
<ID>NoWildcardImports:io.qonversion.nocodes.internal.utils.ErrorUtilsTest.kt:4</ID>
<ID>NoWildcardImports:io.qonversion.sample.HomeFragment.kt:33</ID>
<ID>ReturnCount:BillingClientWrapper.kt$BillingClientWrapper$override fun makePurchase( activity: Activity, product: QProduct, offerId: String?, applyOffer: Boolean?, updatePurchaseInfo: UpdatePurchaseInfo?, onFailed: (error: BillingError) -&gt; Unit )</ID>
<ID>ReturnCount:ErrorUtils.kt$ErrorUtils$ fun isServerError(error: Exception): Boolean</ID>
<ID>ReturnCount:ExceptionHandler.kt$ExceptionHandler$private fun isQonversionException(exception: Throwable): Boolean</ID>
<ID>ReturnCount:QExceptionManager.kt$QExceptionManager$private fun getContentOfCrashReport(filename: String): CrashRequest.ExceptionInfo?</ID>
<ID>ReturnCount:QProductCenterManager.kt$QProductCenterManager$@Synchronized private fun executeProductsBlocks(loadStoreProductsError: QonversionError? = null)</ID>
Expand All @@ -213,25 +233,33 @@
<ID>SwallowedException:EnvironmentProvider.kt$EnvironmentProvider$catch (throwable: Throwable) { UNKNOWN }</ID>
<ID>SwallowedException:ExceptionHandler.kt$ExceptionHandler$catch (e: Exception) { "" }</ID>
<ID>SwallowedException:FacebookAttribution.kt$FacebookAttribution$catch (e: Exception) { null }</ID>
<ID>SwallowedException:FallbackService.kt$FallbackService.Companion$catch (e: Exception) { false }</ID>
<ID>SwallowedException:FallbackUtils.kt$FallbackUtils$catch (e: Exception) { false }</ID>
<ID>SwallowedException:PurchasesCache.kt$PurchasesCache$catch (e: IOException) { setOf() }</ID>
<ID>SwallowedException:ScreenFragment.kt$ScreenFragment$catch (e: ActivityNotFoundException) { logger.error("ScreenActivity -&gt; Couldn't find any Activity to handle the Intent with deeplink $url") delegate?.onActionFailedToExecute(action) }</ID>
<ID>SwallowedException:ScreenFragment.kt$ScreenFragment$catch (e: ActivityNotFoundException) { logger.error("ScreenActivity -&gt; Couldn't find any Activity to handle the Intent with url $url") delegate?.onActionFailedToExecute(action) }</ID>
<ID>SwallowedException:ScreenFragment.kt$ScreenFragment$catch (e: Exception) { delegate?.onActionFailedToExecute(action) }</ID>
<ID>SwallowedException:ScreenPresenter.kt$ScreenPresenter$catch (e: Exception) { logger.error("ScreenPresenter -&gt; failed to open the screen with id $screenId") }</ID>
<ID>SwallowedException:SharedPreferencesCache.kt$SharedPreferencesCache$catch (e: IOException) { null }</ID>
<ID>ThrowingExceptionsWithoutMessageOrCause:AdvertisingProvider.kt$AdvertisingProvider.AdvertisingConnection$IllegalStateException()</ID>
<ID>ThrowsCount:ScreenServiceImpl.kt$ScreenServiceImpl$private suspend fun executeWithFallback( requestProvider: () -&gt; Request, fallbackProvider: suspend () -&gt; NoCodeScreen?, errorContext: String, methodName: String ): NoCodeScreen</ID>
<ID>TooGenericExceptionCaught:AdvertisingProvider.kt$AdvertisingProvider$e: Exception</ID>
<ID>TooGenericExceptionCaught:EnvironmentProvider.kt$EnvironmentProvider$throwable: Throwable</ID>
<ID>TooGenericExceptionCaught:ExceptionHandler.kt$ExceptionHandler$cause: Exception</ID>
<ID>TooGenericExceptionCaught:ExceptionHandler.kt$ExceptionHandler$e: Exception</ID>
<ID>TooGenericExceptionCaught:FacebookAttribution.kt$FacebookAttribution$e: Exception</ID>
<ID>TooGenericExceptionCaught:FallbackService.kt$FallbackService.Companion$e: Exception</ID>
<ID>TooGenericExceptionCaught:FallbackServiceImpl.kt$FallbackServiceImpl$e: Exception</ID>
<ID>TooGenericExceptionCaught:FallbackUtils.kt$FallbackUtils$e: Exception</ID>
<ID>TooGenericExceptionCaught:JsonSerializer.kt$JsonSerializer$cause: NullPointerException</ID>
<ID>TooGenericExceptionCaught:NetworkClientImpl.kt$NetworkClientImpl$cause: Exception</ID>
<ID>TooGenericExceptionCaught:QExceptionManager.kt$QExceptionManager$cause: Exception</ID>
<ID>TooGenericExceptionCaught:QFallbacksService.kt$QFallbacksService$e: Exception</ID>
<ID>TooGenericExceptionCaught:ScreenControllerImpl.kt$ScreenControllerImpl$e: Exception</ID>
<ID>TooGenericExceptionCaught:ScreenFragment.kt$ScreenFragment$e: Exception</ID>
<ID>TooGenericExceptionCaught:ScreenPresenter.kt$ScreenPresenter$e: Exception</ID>
<ID>TooGenericExceptionCaught:ScreenServiceImpl.kt$ScreenServiceImpl$e: Exception</ID>
<ID>TooGenericExceptionCaught:ScreenServiceImpl.kt$ScreenServiceImpl$fallbackError: Exception</ID>
<ID>TooManyFunctions:Api.kt$Api</ID>
<ID>TooManyFunctions:AppComponent.kt$AppComponent</ID>
<ID>TooManyFunctions:BillingClientWrapper.kt$BillingClientWrapper : IBillingClientWrapper</ID>
Expand All @@ -252,6 +280,7 @@
<ID>TooManyFunctions:extensions.kt$com.qonversion.android.sdk.internal.extensions.kt</ID>
<ID>UnnecessaryAbstractClass:BaseClass.kt$BaseClass</ID>
<ID>UnnecessaryAbstractClass:RequestData.kt$RequestData</ID>
<ID>UnusedPrivateMember:FallbackServiceTest.kt$FallbackServiceTest$@Mock private lateinit var mockScreen: NoCodeScreen</ID>
<ID>UnusedPrivateMember:QonversionMappingAdapters.kt$QDateAdapter$@ToJson private fun toJson(date: Date): Long</ID>
<ID>UnusedPrivateMember:QonversionMappingAdapters.kt$QEligibilityAdapter$@ToJson private fun toJson( @Suppress("UNUSED_PARAMETER") eligibilities: Map&lt;String, QEligibility&gt; ): List&lt;ProductEligibility&gt;</ID>
<ID>UnusedPrivateMember:QonversionMappingAdapters.kt$QEligibilityStatusAdapter$@ToJson private fun toJson(enum: QIntroEligibilityStatus): String</ID>
Expand Down
7 changes: 7 additions & 0 deletions nocodes/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ android {
viewBinding true
buildConfig true
}
sourceSets {
main {
assets {
srcDirs 'src/main/assets', 'src/main/assets/fallbacks'
}
}
}
}

ext {
Expand Down
14 changes: 14 additions & 0 deletions nocodes/src/main/assets/fallbacks/nocodes_fallbacks.json

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion nocodes/src/main/java/io/qonversion/nocodes/NoCodesConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class NoCodesConfig internal constructor(
private var proxyUrl: String? = null
private var logLevel = LogLevel.Info
private var logTag = DEFAULT_LOG_TAG
private var customFallbackFileName: String? = null

/**
* Provide a delegate to be notified about the no-code screens events.
Expand Down Expand Up @@ -112,6 +113,18 @@ class NoCodesConfig internal constructor(
this.logTag = logTag
}

/**
* Set a custom fallback file name for offline scenarios.
Comment thread
SpertsyanKM marked this conversation as resolved.
* This file should be placed in the assets folder and will be used when network is unavailable.
* If not set, the default file name "nocodes_fallbacks.json" will be used.
*
* @param fileName the custom fallback file name.
Comment thread
SpertsyanKM marked this conversation as resolved.
* @return builder instance for chain calls.
*/
fun setCustomFallbackFileName(fileName: String): Builder = apply {
this.customFallbackFileName = fileName
}

/**
* Generate [NoCodesConfig] instance with all the provided configurations.
* This method also validates some of the provided data.
Expand All @@ -124,7 +137,7 @@ class NoCodesConfig internal constructor(
throw IllegalStateException("Project key is empty")
}

val primaryConfig = PrimaryConfig(projectKey)
val primaryConfig = PrimaryConfig(projectKey, customFallbackFileName = customFallbackFileName)
val networkConfig = NetworkConfig(proxyUrl)
val loggerConfig = LoggerConfig(logLevel, logTag)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.qonversion.nocodes.internal.common

object FallbackConstants {
const val DEFAULT_FILE_NAME = "nocodes_fallbacks.json"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.qonversion.nocodes.internal.common

object TimeoutConstants {
const val FALLBACK_AVAILABLE_TIMEOUT = 5_000L
const val DEFAULT_TIMEOUT = 20_000L
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,20 @@ internal class DependenciesAssembly(
val mappersAssembly = MappersAssemblyImpl()
val storageAssembly = StorageAssemblyImpl(application)
val networkAssembly =
NetworkAssemblyImpl(internalConfig, mappersAssembly, storageAssembly, miscAssembly)
val servicesAssembly = ServicesAssemblyImpl(mappersAssembly, networkAssembly, miscAssembly)
NetworkAssemblyImpl(application, internalConfig, mappersAssembly, storageAssembly, miscAssembly)
val servicesAssembly = ServicesAssemblyImpl(
Comment thread
SpertsyanKM marked this conversation as resolved.
application,
mappersAssembly,
networkAssembly,
miscAssembly,
internalConfig.primaryConfig.effectiveFallbackFileName
)
val controllersAssembly = ControllersAssemblyImpl(
application,
servicesAssembly,
miscAssembly,
mappersAssembly,
internalConfig,
application
internalConfig
)

instance = DependenciesAssembly(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import io.qonversion.nocodes.internal.screen.view.ScreenContract
import io.qonversion.nocodes.internal.screen.view.ScreenPresenter

internal class ControllersAssemblyImpl(
private val appContext: Context,
private val servicesAssembly: ServicesAssembly,
private val miscAssembly: MiscAssembly,
private val mappersAssembly: MappersAssembly,
private val internalConfig: InternalConfig,
private val appContext: Context
private val internalConfig: InternalConfig
) : ControllersAssembly {

override fun screenController(): ScreenController {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.qonversion.nocodes.internal.di.network

import android.content.Context
import io.qonversion.nocodes.internal.common.API_URL
import io.qonversion.nocodes.internal.di.mappers.MappersAssembly
import io.qonversion.nocodes.internal.di.misc.MiscAssembly
Expand All @@ -14,15 +15,20 @@ import io.qonversion.nocodes.internal.networkLayer.networkClient.NetworkClient
import io.qonversion.nocodes.internal.networkLayer.networkClient.NetworkClientImpl
import io.qonversion.nocodes.internal.networkLayer.requestConfigurator.RequestConfigurator
import io.qonversion.nocodes.internal.networkLayer.requestConfigurator.RequestConfiguratorImpl
import io.qonversion.nocodes.internal.utils.FallbackUtils

internal class NetworkAssemblyImpl(
private val context: Context,
private val internalConfig: InternalConfig,
private val mappersAssembly: MappersAssembly,
private val storageAssembly: StorageAssembly,
private val miscAssembly: MiscAssembly
) : NetworkAssembly {

override fun networkClient(): NetworkClient = NetworkClientImpl(miscAssembly.jsonSerializer())
override fun networkClient(): NetworkClient = NetworkClientImpl(
miscAssembly.jsonSerializer(),
isFallbackAvailable = isFallbackActuallyAvailable()
)

override fun requestConfigurator(): RequestConfigurator = RequestConfiguratorImpl(
headerBuilder(),
Expand Down Expand Up @@ -50,4 +56,11 @@ internal class NetworkAssemblyImpl(
retryPolicy
)
}

private fun isFallbackActuallyAvailable(): Boolean {
return FallbackUtils.isFallbackFileAvailable(
internalConfig.primaryConfig.effectiveFallbackFileName,
context
)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.qonversion.nocodes.internal.di.services

import io.qonversion.nocodes.internal.screen.service.FallbackService
import io.qonversion.nocodes.internal.screen.service.ScreenService

internal interface ServicesAssembly {

fun screenService(): ScreenService

fun fallbackService(): FallbackService?
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,43 @@
package io.qonversion.nocodes.internal.di.services

import android.content.Context
import io.qonversion.nocodes.internal.di.mappers.MappersAssembly
import io.qonversion.nocodes.internal.di.misc.MiscAssembly
import io.qonversion.nocodes.internal.di.network.NetworkAssembly
import io.qonversion.nocodes.internal.screen.service.FallbackService
import io.qonversion.nocodes.internal.screen.service.FallbackServiceImpl
import io.qonversion.nocodes.internal.screen.service.ScreenService
import io.qonversion.nocodes.internal.screen.service.ScreenServiceImpl
import io.qonversion.nocodes.internal.utils.FallbackUtils

internal class ServicesAssemblyImpl(
private val context: Context,
private val mappersAssembly: MappersAssembly,
private val networkAssembly: NetworkAssembly,
private val miscAssembly: MiscAssembly
private val miscAssembly: MiscAssembly,
private val effectiveFallbackFileName: String
) : ServicesAssembly {

override fun screenService(): ScreenService {
return ScreenServiceImpl(
networkAssembly.requestConfigurator(),
networkAssembly.exponentialApiInteractor(),
mappersAssembly.screenMapper(),
fallbackService(),
miscAssembly.logger()
)
}

override fun fallbackService(): FallbackService? {
// Check if fallback file is available
if (!FallbackUtils.isFallbackFileAvailable(effectiveFallbackFileName, context)) {
return null
}

return FallbackServiceImpl(
context,
effectiveFallbackFileName,
mappersAssembly.screenMapper(),
miscAssembly.logger()
)
}
Expand Down
Loading