From b2d6e6112778ca5eefe73cb9d0106bb7e18336e6 Mon Sep 17 00:00:00 2001 From: longvantruong Date: Thu, 22 May 2025 15:35:28 +0700 Subject: [PATCH 01/19] chore: custom sdk tcb --- android/build.gradle.kts | 69 +++++++++++-------- android/consumer-rules.pro | 8 ++- android/proguard-rules.pro | 10 ++- .../java/com/formbricks/android/Formbricks.kt | 39 +++++++++-- .../android/helper/FormbricksConfig.kt | 12 +++- .../android/manager/SurveyManager.kt | 14 +++- .../formbricks/android/manager/UserManager.kt | 4 ++ .../android/network/queue/UpdateQueue.kt | 2 + .../android/webview/FormbricksFragment.kt | 29 ++++++-- .../android/webview/FormbricksViewModel.kt | 16 ++++- .../android/webview/WebAppInterface.kt | 5 ++ gradle/libs.versions.toml | 32 +++++---- gradle/wrapper/gradle-wrapper.properties | 4 +- 13 files changed, 177 insertions(+), 67 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 9f0b6d1..ac0e281 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -2,12 +2,12 @@ import com.vanniktech.maven.publish.SonatypeHost plugins { id("com.android.library") - kotlin("android") - kotlin("kapt") - kotlin("plugin.serialization") version "2.1.0" + id("kotlin-android") + id("kotlin-kapt") + kotlin("plugin.serialization") version "1.7.20" id("org.jetbrains.dokka") version "1.9.10" id("jacoco") - id("com.vanniktech.maven.publish") version "0.31.0" + id("com.vanniktech.maven.publish") version "0.24.0" id("org.sonarqube") version "4.4.1.3373" } @@ -25,7 +25,7 @@ jacoco { android { namespace = "com.formbricks.android" - compileSdk = 35 + compileSdk = 34 defaultConfig { minSdk = 24 @@ -36,11 +36,11 @@ android { buildTypes { getByName("debug") { - enableAndroidTestCoverage = true +// enableAndroidTestCoverage = true isTestCoverageEnabled = true // For backward compatibility } release { - isMinifyEnabled = true + isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -48,17 +48,30 @@ android { } } - packaging { + + packagingOptions { resources { - excludes += "META-INF/library_release.kotlin_module" - excludes += "classes.dex" - excludes += "**.**" - pickFirsts += "**/DataBinderMapperImpl.java" - pickFirsts += "**/DataBinderMapperImpl.class" - pickFirsts += "**/formbrickssdk/DataBinderMapperImpl.java" - pickFirsts += "**/formbrickssdk/DataBinderMapperImpl.class" + excludes += setOf( + "META-INF/DEPENDENCIES", + "META-INF/LICENSE", + "META-INF/LICENSE.txt", + "META-INF/license.txt", + "META-INF/NOTICE", + "META-INF/NOTICE.txt", + "META-INF/notice.txt", + "META-INF/ASL2.0", + "META-INF/*.kotlin_module", + "classes.dex" + ) + pickFirsts += setOf( + "**/DataBinderMapperImpl.class", + "**/DataBinderMapperImpl.java", + "**/formbrickssdk/DataBinderMapperImpl.java", + "**/formbrickssdk/DataBinderMapperImpl.class" + ) } } + buildFeatures { dataBinding = true viewBinding = true @@ -94,36 +107,38 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) + implementation(enforcedPlatform("org.jetbrains.kotlin:kotlin-bom:1.7.20")) } mavenPublishing { - publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) +// publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + publishToMavenCentral() signAllPublications() coordinates(groupId, artifactId, version.toString()) pom { - name = "Formbricks Android SDK" - description = "Formbricks anroid SDK" - url = "https://github.com/formbricks/android" + name.set("Formbricks Android SDK") + description.set("Formbricks anroid SDK") + url.set("https://github.com/formbricks/android") licenses { license { - name = "MIT License" - url = "https://opensource.org/licenses/MIT" + name.set("MIT License") + url.set("https://opensource.org/licenses/MIT") } } developers { developer { - id = "formbricks" - name = "Formbricks" - email = "hola@formbricks.com" + id.set("formbricks") + name.set("Formbricks") + email.set("hola@formbricks.com") } } scm { - connection = "scm:git:git://github.com/formbricks/android.git" - developerConnection = "scm:git:ssh://github.com:formbricks/android.git" - url = "https://github.com/formbricks/android" + connection.set("scm:git:git://github.com/formbricks/android.git") + developerConnection.set("scm:git:ssh://github.com:formbricks/android.git") + url.set("https://github.com/formbricks/android") } } } diff --git a/android/consumer-rules.pro b/android/consumer-rules.pro index 3b6eeaa..8b5f955 100644 --- a/android/consumer-rules.pro +++ b/android/consumer-rules.pro @@ -2,4 +2,10 @@ -keep class com.formbricks.android.Formbricks { *; } -keep class com.formbricks.android.helper.FormbricksConfig { *; } -keep class com.formbricks.android.model.error.SDKError { *; } --keep interface com.formbricks.android.FormbricksCallback { *; } \ No newline at end of file +-keep interface com.formbricks.android.FormbricksCallback { *; } +-keep class com.android.org.conscrypt.** { *; } +-keep class javax.annotation.** { *; } +-keep class org.apache.harmony.xnet.provider.jsse.** { *; } +# Please add these rules to your existing keep rules in order to suppress warnings. +# This is generated automatically by the Android Gradle plugin. +-dontwarn java.lang.invoke.StringConcatFactory \ No newline at end of file diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro index 71cda75..b506b8f 100644 --- a/android/proguard-rules.pro +++ b/android/proguard-rules.pro @@ -35,6 +35,7 @@ -keep class com.formbricks.android.Formbricks { *; } -keep class com.formbricks.android.helper.FormbricksConfig { *; } -keep class com.formbricks.android.model.error.SDKError { *; } +-keep interface com.formbricks.formbrickssdk.FormbricksCallback { *; } # Keep StringConcatFactory and related classes -keep class java.lang.invoke.StringConcatFactory { *; } @@ -44,4 +45,11 @@ -dontwarn java.lang.invoke.StringConcatFactory # Keep DataBinding classes --keep class androidx.databinding.** { *; } \ No newline at end of file +-keep class androidx.databinding.** { *; } + +-keep class com.android.org.conscrypt.** { *; } +-keep class javax.annotation.** { *; } +-keep class org.apache.harmony.xnet.provider.jsse.** { *; } +# Please add these rules to your existing keep rules in order to suppress warnings. +# This is generated automatically by the Android Gradle plugin. +-dontwarn java.lang.invoke.StringConcatFactory \ No newline at end of file diff --git a/android/src/main/java/com/formbricks/android/Formbricks.kt b/android/src/main/java/com/formbricks/android/Formbricks.kt index 10c540c..ed56767 100644 --- a/android/src/main/java/com/formbricks/android/Formbricks.kt +++ b/android/src/main/java/com/formbricks/android/Formbricks.kt @@ -10,10 +10,21 @@ import com.formbricks.android.helper.FormbricksConfig import com.formbricks.android.logger.Logger import com.formbricks.android.manager.SurveyManager import com.formbricks.android.manager.UserManager +import com.formbricks.android.model.enums.SuccessType import com.formbricks.android.model.error.SDKError import com.formbricks.android.webview.FormbricksFragment import java.lang.RuntimeException +@Keep +interface FormbricksCallback { + fun onSurveyStarted() + fun onSurveyFinished() + fun onSurveyClosed() + fun onPageCommitVisible() + fun onError(error: Exception) + fun onSuccess(successType: SuccessType) +} + @Keep object Formbricks { internal lateinit var applicationContext: Context @@ -22,9 +33,12 @@ object Formbricks { internal lateinit var appUrl: String internal var language: String = "default" internal var loggingEnabled: Boolean = true + internal var autoDismissErrors: Boolean = true private var fragmentManager: FragmentManager? = null internal var isInitialized = false + var callback: FormbricksCallback? = null + /** * Initializes the Formbricks SDK with the given [Context] config [FormbricksConfig]. * This method is mandatory to be called, and should be only once per application lifecycle. @@ -48,6 +62,7 @@ object Formbricks { fun setup(context: Context, config: FormbricksConfig, forceRefresh: Boolean = false) { if (isInitialized && !forceRefresh) { val error = SDKError.sdkIsAlreadyInitialized + callback?.onError(error) Logger.e(error) return } @@ -58,7 +73,7 @@ object Formbricks { environmentId = config.environmentId loggingEnabled = config.loggingEnabled fragmentManager = config.fragmentManager - + autoDismissErrors = config.autoDismissErrors config.userId?.let { UserManager.set(it) } config.attributes?.let { UserManager.setAttributes(it) } config.attributes?.get("language")?.let { UserManager.setLanguage(it) } @@ -79,15 +94,17 @@ object Formbricks { * ``` * */ - fun setUserId(userId: String) { + fun setUserId(userId: String, allowOverrideUserId: Boolean) { if (!isInitialized) { val error = SDKError.sdkIsNotInitialized + callback?.onError(error) Logger.e(error) return } - if(UserManager.userId != null) { + if (UserManager.userId != null && !allowOverrideUserId) { val error = RuntimeException("A userId is already set ${UserManager.userId} - please call logout first before setting a new one") + callback?.onError(error) Logger.e(error) return } @@ -107,6 +124,7 @@ object Formbricks { fun setAttribute(attribute: String, key: String) { if (!isInitialized) { val error = SDKError.sdkIsNotInitialized + callback?.onError(error) Logger.e(error) return } @@ -125,6 +143,7 @@ object Formbricks { fun setAttributes(attributes: Map) { if (!isInitialized) { val error = SDKError.sdkIsNotInitialized + callback?.onError(error) Logger.e(error) return } @@ -143,6 +162,7 @@ object Formbricks { fun setLanguage(language: String) { if (!isInitialized) { val error = SDKError.sdkIsNotInitialized + callback?.onError(error) Logger.e(error) return } @@ -159,20 +179,22 @@ object Formbricks { * ``` * */ - fun track(action: String) { + fun track(action: String, hiddenFields: Map? = null) { if (!isInitialized) { val error = SDKError.sdkIsNotInitialized + callback?.onError(error) Logger.e(error) return } if (!isInternetAvailable()) { val error = SDKError.connectionIsNotAvailable + callback?.onError(error) Logger.e(error) return } - SurveyManager.track(action) + SurveyManager.track(action = action, hiddenFields = hiddenFields) } /** @@ -187,10 +209,12 @@ object Formbricks { fun logout() { if (!isInitialized) { val error = SDKError.sdkIsNotInitialized + callback?.onError(error) Logger.e(error) return } + callback?.onSuccess(SuccessType.LOGOUT_SUCCESS) UserManager.logout() } @@ -209,15 +233,16 @@ object Formbricks { } /// Assembles the survey fragment and presents it - internal fun showSurvey(id: String) { + internal fun showSurvey(id: String, hiddenFields: Map? = null) { if (fragmentManager == null) { val error = SDKError.fragmentManagerIsNotSet + callback?.onError(error) Logger.e(error) return } fragmentManager?.let { - FormbricksFragment.show(it, surveyId = id) + FormbricksFragment.show(it, surveyId = id, hiddenFields = hiddenFields) } } diff --git a/android/src/main/java/com/formbricks/android/helper/FormbricksConfig.kt b/android/src/main/java/com/formbricks/android/helper/FormbricksConfig.kt index fff2a09..b33bd48 100644 --- a/android/src/main/java/com/formbricks/android/helper/FormbricksConfig.kt +++ b/android/src/main/java/com/formbricks/android/helper/FormbricksConfig.kt @@ -15,13 +15,15 @@ class FormbricksConfig private constructor( val userId: String?, val attributes: Map?, val loggingEnabled: Boolean, - val fragmentManager: FragmentManager? + val fragmentManager: FragmentManager?, + val autoDismissErrors: Boolean = true, ) { class Builder(private val appUrl: String, private val environmentId: String) { private var userId: String? = null private var attributes: MutableMap = mutableMapOf() private var loggingEnabled = false private var fragmentManager: FragmentManager? = null + private var autoDismissErrors = true fun setUserId(userId: String): Builder { this.userId = userId @@ -48,6 +50,11 @@ class FormbricksConfig private constructor( return this } + fun setAutoDismissErrors(autoDismissErrors: Boolean): Builder { + this.autoDismissErrors = autoDismissErrors + return this + } + fun build(): FormbricksConfig { return FormbricksConfig( appUrl = appUrl, @@ -55,7 +62,8 @@ class FormbricksConfig private constructor( userId = userId, attributes = attributes, loggingEnabled = loggingEnabled, - fragmentManager = fragmentManager + fragmentManager = fragmentManager, + autoDismissErrors = autoDismissErrors ) } } diff --git a/android/src/main/java/com/formbricks/android/manager/SurveyManager.kt b/android/src/main/java/com/formbricks/android/manager/SurveyManager.kt index 6813f41..2ef0386 100644 --- a/android/src/main/java/com/formbricks/android/manager/SurveyManager.kt +++ b/android/src/main/java/com/formbricks/android/manager/SurveyManager.kt @@ -6,6 +6,7 @@ import com.formbricks.android.api.FormbricksApi import com.formbricks.android.extensions.expiresAt import com.formbricks.android.extensions.guard import com.formbricks.android.logger.Logger +import com.formbricks.android.model.enums.SuccessType import com.formbricks.android.model.environment.EnvironmentDataHolder import com.formbricks.android.model.environment.SegmentFilterResource import com.formbricks.android.model.environment.SegmentFilterResourceDeserializer @@ -72,6 +73,7 @@ object SurveyManager { try { gson.fromJson(json, EnvironmentDataHolder::class.java) } catch (e: Exception) { + Formbricks.callback?.onError(e) Logger.e(RuntimeException("Unable to retrieve environment data from the local storage.")) null } @@ -129,9 +131,11 @@ object SurveyManager { startRefreshTimer(environmentDataHolder?.expiresAt()) filterSurveys() hasApiError = false + Formbricks.callback?.onSuccess(SuccessType.GET_ENVIRONMENT_SUCCESS) } catch (e: Exception) { hasApiError = true val error = SDKError.unableToRefreshEnvironment + Formbricks.callback?.onError(error) Logger.e(error) startErrorTimer() } @@ -142,7 +146,7 @@ object SurveyManager { * Checks if there are any surveys to display, based in the track action, and if so, displays the first one. * Handles the display percentage and the delay of the survey. */ - fun track(action: String) { + fun track(action: String, hiddenFields: Map? = null) { val actionClasses = environmentDataHolder?.data?.data?.actionClasses ?: listOf() val codeActionClasses = actionClasses.filter { it.type == "code" } val actionClass = codeActionClasses.firstOrNull { it.key == action } @@ -156,6 +160,7 @@ object SurveyManager { if (firstSurveyWithActionClass == null) { val error = SDKError.surveyNotFoundError Logger.e(error) + Formbricks.callback?.onError(error) return } @@ -166,6 +171,7 @@ object SurveyManager { if (languageCode == null) { val error = RuntimeException("Survey “${firstSurveyWithActionClass.name}” is not available in language “$currentLanguage”. Skipping.") + Formbricks.callback?.onError(error) Logger.e(error) return } @@ -182,7 +188,7 @@ object SurveyManager { stopDisplayTimer() displayTimer.schedule(object : TimerTask() { override fun run() { - Formbricks.showSurvey(it) + Formbricks.showSurvey(it, hiddenFields = hiddenFields) } }, Date(System.currentTimeMillis() + timeout.toLong() * 1000)) @@ -190,6 +196,7 @@ object SurveyManager { } else { val error = SDKError.surveyNotDisplayedError Logger.e(error) + Formbricks.callback?.onError(error) } } @@ -204,6 +211,7 @@ object SurveyManager { fun postResponse(surveyId: String?) { val id = surveyId.guard { val error = SDKError.missingSurveyId + Formbricks.callback?.onError(error) Logger.e(error) return } @@ -217,6 +225,7 @@ object SurveyManager { fun onNewDisplay(surveyId: String?) { val id = surveyId.guard { val error = SDKError.missingSurveyId + Formbricks.callback?.onError(error) Logger.e(error) return } @@ -284,6 +293,7 @@ object SurveyManager { else -> { val error = SDKError.invalidDisplayOption + Formbricks.callback?.onError(error) Logger.e(error) false } diff --git a/android/src/main/java/com/formbricks/android/manager/UserManager.kt b/android/src/main/java/com/formbricks/android/manager/UserManager.kt index 91fd319..5ba8b7d 100644 --- a/android/src/main/java/com/formbricks/android/manager/UserManager.kt +++ b/android/src/main/java/com/formbricks/android/manager/UserManager.kt @@ -8,6 +8,7 @@ import com.formbricks.android.extensions.expiresAt import com.formbricks.android.extensions.guard import com.formbricks.android.extensions.lastDisplayAt import com.formbricks.android.logger.Logger +import com.formbricks.android.model.enums.SuccessType import com.formbricks.android.model.error.SDKError import com.formbricks.android.model.user.Display import com.formbricks.android.network.queue.UpdateQueue @@ -146,8 +147,10 @@ object UserManager { UpdateQueue.reset() SurveyManager.filterSurveys() startSyncTimer() + Formbricks.callback?.onSuccess(SuccessType.SET_USER_SUCCESS) } catch (e: Exception) { val error = SDKError.unableToPostResponse + Formbricks.callback?.onError(error) Logger.e(error) } } @@ -161,6 +164,7 @@ object UserManager { if (!isUserIdDefined) { val error = SDKError.noUserIdSetError + Formbricks.callback?.onError(error) Logger.e(error) } diff --git a/android/src/main/java/com/formbricks/android/network/queue/UpdateQueue.kt b/android/src/main/java/com/formbricks/android/network/queue/UpdateQueue.kt index 0e115cd..74c3dfd 100644 --- a/android/src/main/java/com/formbricks/android/network/queue/UpdateQueue.kt +++ b/android/src/main/java/com/formbricks/android/network/queue/UpdateQueue.kt @@ -1,5 +1,6 @@ package com.formbricks.android.network.queue +import com.formbricks.android.Formbricks import com.formbricks.android.logger.Logger import com.formbricks.android.manager.UserManager import com.formbricks.android.model.error.SDKError @@ -68,6 +69,7 @@ object UpdateQueue { ?: UserManager.userId if (effectiveUserId == null) { val error = SDKError.noUserIdSetError + Formbricks.callback?.onError(error) Logger.e(error) return } diff --git a/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt b/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt index 2c954e2..62e522a 100644 --- a/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt +++ b/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt @@ -25,6 +25,7 @@ import android.widget.FrameLayout import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.FragmentManager import androidx.fragment.app.viewModels +import com.formbricks.android.Formbricks import com.formbricks.android.R import com.formbricks.android.databinding.FragmentFormbricksBinding import com.formbricks.android.logger.Logger @@ -38,7 +39,7 @@ import java.io.ByteArrayOutputStream import java.io.InputStream -class FormbricksFragment : BottomSheetDialogFragment() { +class FormbricksFragment(val hiddenFields: Map? = null) : BottomSheetDialogFragment() { private lateinit var binding: FragmentFormbricksBinding private lateinit var surveyId: String @@ -47,11 +48,13 @@ class FormbricksFragment : BottomSheetDialogFragment() { private var webAppInterface = WebAppInterface(object : WebAppInterface.WebAppCallback { override fun onClose() { Handler(Looper.getMainLooper()).post { + Formbricks.callback?.onSurveyClosed() dismiss() } } override fun onDisplayCreated() { + Formbricks.callback?.onSurveyStarted() SurveyManager.onNewDisplay(surveyId) } @@ -144,6 +147,12 @@ class FormbricksFragment : BottomSheetDialogFragment() { @SuppressLint("SetJavaScriptEnabled") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + if (surveyId.isNullOrEmpty()) { + Formbricks.callback?.onError(SDKError.missingSurveyId) + dismissAllowingStateLoss() + return + } + dialog?.window?.setDimAmount(0.0f) binding.formbricksWebview.setBackgroundColor(Color.TRANSPARENT) binding.formbricksWebview.let { @@ -151,9 +160,10 @@ class FormbricksFragment : BottomSheetDialogFragment() { override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { consoleMessage?.let { cm -> if (cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) { - val error = SDKError.surveyDisplayFetchError - Logger.e(error) - dismiss() + Formbricks.callback?.onError(SDKError.surveyDisplayFetchError) + if (Formbricks.autoDismissErrors) { + dismiss() + } } val log = "[CONSOLE:${cm.messageLevel()}] \"${cm.message()}\", source: ${cm.sourceId()} (${cm.lineNumber()})" Logger.d(log) @@ -181,6 +191,7 @@ class FormbricksFragment : BottomSheetDialogFragment() { override fun onPageCommitVisible(view: WebView?, url: String?) { dialog?.window?.setDimAmount(0.5f) + Formbricks.callback?.onPageCommitVisible() super.onPageCommitVisible(view, url) } } @@ -196,7 +207,7 @@ class FormbricksFragment : BottomSheetDialogFragment() { it.addJavascriptInterface(webAppInterface, WebAppInterface.INTERFACE_NAME) } - viewModel.loadHtml(surveyId) + viewModel.loadHtml(surveyId = surveyId, hiddenFields = hiddenFields) } private fun getFileName(uri: Uri): String? { @@ -234,8 +245,12 @@ class FormbricksFragment : BottomSheetDialogFragment() { companion object { private val TAG: String by lazy { FormbricksFragment::class.java.simpleName } - fun show(childFragmentManager: FragmentManager, surveyId: String) { - val fragment = FormbricksFragment() + fun show( + childFragmentManager: FragmentManager, + surveyId: String, + hiddenFields: Map? = null + ) { + val fragment = FormbricksFragment(hiddenFields) fragment.surveyId = surveyId fragment.show(childFragmentManager, TAG) } diff --git a/android/src/main/java/com/formbricks/android/webview/FormbricksViewModel.kt b/android/src/main/java/com/formbricks/android/webview/FormbricksViewModel.kt index 2d901af..42c705c 100644 --- a/android/src/main/java/com/formbricks/android/webview/FormbricksViewModel.kt +++ b/android/src/main/java/com/formbricks/android/webview/FormbricksViewModel.kt @@ -12,6 +12,7 @@ import com.formbricks.android.model.environment.EnvironmentDataHolder import com.formbricks.android.model.environment.getProjectStylingJson import com.formbricks.android.model.environment.getStyling import com.formbricks.android.model.environment.getSurveyJson +import com.google.gson.Gson import com.google.gson.JsonObject /** @@ -111,14 +112,22 @@ class FormbricksViewModel : ViewModel() { """ - fun loadHtml(surveyId: String) { + fun loadHtml(surveyId: String, hiddenFields: Map? = null) { val environment = SurveyManager.environmentDataHolder.guard { return } - val json = getJson(environment, surveyId) + val json = getJson( + environmentDataHolder = environment, + surveyId = surveyId, + hiddenFields = hiddenFields + ) val htmlString = htmlTemplate.replace("{{WEBVIEW_DATA}}", json) html.postValue(htmlString) } - private fun getJson(environmentDataHolder: EnvironmentDataHolder, surveyId: String): String { + private fun getJson( + environmentDataHolder: EnvironmentDataHolder, + surveyId: String, + hiddenFields: Map? = null + ): String { val jsonObject = JsonObject() environmentDataHolder.getSurveyJson(surveyId).let { jsonObject.add("survey", it) } jsonObject.addProperty("isBrandingEnabled", true) @@ -136,6 +145,7 @@ class FormbricksViewModel : ViewModel() { } else { jsonObject.addProperty("languageCode", "default") } + hiddenFields?.let { jsonObject.add("hiddenFieldsRecord", Gson().toJsonTree(it)) } val hasCustomStyling = environmentDataHolder.data?.data?.surveys?.first { it.id == surveyId }?.styling != null val enabled = environmentDataHolder.data?.data?.project?.styling?.allowStyleOverwrite ?: false diff --git a/android/src/main/java/com/formbricks/android/webview/WebAppInterface.kt b/android/src/main/java/com/formbricks/android/webview/WebAppInterface.kt index e59ab3f..bd00f96 100644 --- a/android/src/main/java/com/formbricks/android/webview/WebAppInterface.kt +++ b/android/src/main/java/com/formbricks/android/webview/WebAppInterface.kt @@ -1,6 +1,7 @@ package com.formbricks.android.webview import android.webkit.JavascriptInterface +import com.formbricks.android.Formbricks import com.formbricks.android.logger.Logger import com.formbricks.android.model.javascript.JsMessageData import com.formbricks.android.model.javascript.EventType @@ -35,12 +36,16 @@ class WebAppInterface(private val callback: WebAppCallback?) { EventType.ON_SURVEY_LIBRARY_LOAD_ERROR -> { callback?.onSurveyLibraryLoadError() } } } catch (e: Exception) { + Formbricks.callback?.onError(e) Logger.e(RuntimeException(e.message)) } catch (e: JsonParseException) { + Formbricks.callback?.onError(e) Logger.e(RuntimeException("Failed to parse JSON message: $data")) } catch (e: IllegalArgumentException) { + Formbricks.callback?.onError(e) Logger.e(RuntimeException("Invalid message format: $data")) } catch (e: Exception) { + Formbricks.callback?.onError(e) Logger.e(RuntimeException("Unexpected error processing message: $data")) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 08f2ef2..feec125 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,27 +1,29 @@ [versions] -agp = "8.9.2" -kotlin = "2.1.0" -coreKtx = "1.16.0" +agp = "7.2.2" +kotlin = "1.7.20" +coreKtx = "1.8.0" -junit = "4.13.2" -junitVersion = "1.2.1" -espressoCore = "3.6.1" +lifecycleRuntimeKtx = "2.6.1" -appcompat = "1.7.0" -material = "1.12.0" +junit = "1.1.2" +junitVersion = "1.1.3" +espressoCore = "3.3.0" -androidx-annotation = "1.9.1" +appcompat = "1.2.0" +material = "1.6.1" -kotlinx-serialization-json = "1.8.0" +androidx-annotation = "1.8.0" -retrofit = "2.11.0" +kotlinx-serialization-json = "1.4.1" + +retrofit = "2.9.0" okhttp3 = "4.11.0" gson = "2.10.1" legacySupportV4 = "1.0.0" -lifecycleLivedataKtx = "2.8.7" -lifecycleViewmodelKtx = "2.8.7" -fragmentKtx = "1.8.6" -databindingCommon = "8.9.2" +lifecycleLivedataKtx = "2.6.1" +lifecycleViewmodelKtx = "2.6.1" +fragmentKtx = "1.3.0" +databindingCommon = "4.1.3" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 47b6027..f657e86 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Apr 26 10:15:13 IST 2025 +#Mon Feb 10 09:17:42 CET 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From e3a1321eb803a46a4c19201704cc5337f6252e08 Mon Sep 17 00:00:00 2001 From: longvantruong Date: Thu, 22 May 2025 17:31:01 +0700 Subject: [PATCH 02/19] chore: custom sdk tcb --- android/build.gradle.kts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index ac0e281..974b3ef 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -9,6 +9,7 @@ plugins { id("jacoco") id("com.vanniktech.maven.publish") version "0.24.0" id("org.sonarqube") version "4.4.1.3373" + id("maven-publish") } // Import JaCoCo configuration @@ -107,7 +108,7 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) - implementation(enforcedPlatform("org.jetbrains.kotlin:kotlin-bom:1.7.20")) + implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.7.20")) } mavenPublishing { @@ -200,4 +201,10 @@ sonar { tasks.sonar { dependsOn("jacocoAndroidTestReport") +} + +afterEvaluate { + tasks.withType().configureEach { + enabled = false + } } \ No newline at end of file From 524b4db75766786d65fb20928e198b09d24458a1 Mon Sep 17 00:00:00 2001 From: longvantruong Date: Thu, 22 May 2025 17:33:45 +0700 Subject: [PATCH 03/19] chore: custom sdk tcb --- android/src/main/java/com/formbricks/android/Formbricks.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/formbricks/android/Formbricks.kt b/android/src/main/java/com/formbricks/android/Formbricks.kt index ed56767..e6db7df 100644 --- a/android/src/main/java/com/formbricks/android/Formbricks.kt +++ b/android/src/main/java/com/formbricks/android/Formbricks.kt @@ -94,7 +94,7 @@ object Formbricks { * ``` * */ - fun setUserId(userId: String, allowOverrideUserId: Boolean) { + fun setUserId(userId: String, allowOverrideUserId: Boolean = false) { if (!isInitialized) { val error = SDKError.sdkIsNotInitialized callback?.onError(error) From 67b1528566ca56acb3a90ee280cf4e3a1d7dbb49 Mon Sep 17 00:00:00 2001 From: longvantruong Date: Fri, 23 May 2025 16:45:21 +0700 Subject: [PATCH 04/19] chore: custom sdk tcb --- .../java/com/formbricks/android/webview/FormbricksFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt b/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt index 62e522a..d007dd0 100644 --- a/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt +++ b/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt @@ -73,6 +73,7 @@ class FormbricksFragment(val hiddenFields: Map? = null) : BottomShe override fun onSurveyLibraryLoadError() { val error = SDKError.unableToLoadFormbicksJs + Formbricks.callback?.onError(error) Logger.e(error) dismiss() } From 059e0675297a650f342da7592a0a72970ce5eb1c Mon Sep 17 00:00:00 2001 From: longvantruong Date: Sat, 24 May 2025 10:52:14 +0700 Subject: [PATCH 05/19] chore: custom sdk tcb --- android/proguard-rules.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro index b506b8f..d610270 100644 --- a/android/proguard-rules.pro +++ b/android/proguard-rules.pro @@ -35,7 +35,7 @@ -keep class com.formbricks.android.Formbricks { *; } -keep class com.formbricks.android.helper.FormbricksConfig { *; } -keep class com.formbricks.android.model.error.SDKError { *; } --keep interface com.formbricks.formbrickssdk.FormbricksCallback { *; } +-keep interface com.formbricks.android.FormbricksCallback { *; } # Keep StringConcatFactory and related classes -keep class java.lang.invoke.StringConcatFactory { *; } From 873799d0803a7b150d36265aa83f0cc0054d4083 Mon Sep 17 00:00:00 2001 From: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com> Date: Tue, 27 May 2025 14:16:21 +0530 Subject: [PATCH 06/19] fix: blackduck issues (#22) * fixes blackduck issues * fix: dokka version --- android/build.gradle.kts | 6 +----- gradle/libs.versions.toml | 14 ++++++-------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 9f0b6d1..27cbc17 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -5,15 +5,12 @@ plugins { kotlin("android") kotlin("kapt") kotlin("plugin.serialization") version "2.1.0" - id("org.jetbrains.dokka") version "1.9.10" + id("org.jetbrains.dokka") version "1.9.20" id("jacoco") id("com.vanniktech.maven.publish") version "0.31.0" id("org.sonarqube") version "4.4.1.3373" } -// Import JaCoCo configuration -// apply(from = "../jacoco.gradle.kts") - version = "1.0.2" val groupId = "com.formbricks" val artifactId = "android" @@ -86,7 +83,6 @@ dependencies { implementation(libs.material) implementation(libs.kotlinx.serialization.json) - implementation(libs.androidx.legacy.support.v4) implementation(libs.androidx.lifecycle.livedata.ktx) implementation(libs.androidx.lifecycle.viewmodel.ktx) implementation(libs.androidx.fragment.ktx) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 08f2ef2..35174a5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,13 +14,12 @@ androidx-annotation = "1.9.1" kotlinx-serialization-json = "1.8.0" -retrofit = "2.11.0" -okhttp3 = "4.11.0" -gson = "2.10.1" -legacySupportV4 = "1.0.0" -lifecycleLivedataKtx = "2.8.7" -lifecycleViewmodelKtx = "2.8.7" -fragmentKtx = "1.8.6" +retrofit = "3.0.0" +okhttp3 = "4.12.0" +gson = "2.13.1" +lifecycleLivedataKtx = "2.9.0" +lifecycleViewmodelKtx = "2.9.0" +fragmentKtx = "1.8.7" databindingCommon = "8.9.2" [libraries] @@ -39,7 +38,6 @@ okhttp3-logging-interceptor = { module = "com.squareup.okhttp3:logging-intercept gson = { module = "com.google.code.gson:gson", version.ref = "gson" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } -androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupportV4" } androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" } androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" } From 5016f9b8820f1e9a7de67f200678d10f8036a047 Mon Sep 17 00:00:00 2001 From: Matti Nannt Date: Tue, 27 May 2025 12:32:03 +0200 Subject: [PATCH 07/19] fix: Workflow does not contain permissions (#20) Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/publish-to-maven-central.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish-to-maven-central.yml b/.github/workflows/publish-to-maven-central.yml index b5c6d8a..a155f8d 100644 --- a/.github/workflows/publish-to-maven-central.yml +++ b/.github/workflows/publish-to-maven-central.yml @@ -1,6 +1,8 @@ # .github/workflows/publish-to-maven-central.yml name: Publish to Maven Central +permissions: + contents: read on: release: types: [released] From 3a41df5d6aa360425e45acf7dff385c819fad454 Mon Sep 17 00:00:00 2001 From: longvantruong Date: Wed, 28 May 2025 10:02:01 +0700 Subject: [PATCH 08/19] chore: handle back press survey --- .../java/com/formbricks/android/Formbricks.kt | 3 ++ .../android/helper/FormbricksConfig.kt | 10 ++++++- .../android/webview/FormbricksFragment.kt | 30 +++++++++++++++++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/formbricks/android/Formbricks.kt b/android/src/main/java/com/formbricks/android/Formbricks.kt index e6db7df..68e6dc3 100644 --- a/android/src/main/java/com/formbricks/android/Formbricks.kt +++ b/android/src/main/java/com/formbricks/android/Formbricks.kt @@ -21,6 +21,7 @@ interface FormbricksCallback { fun onSurveyFinished() fun onSurveyClosed() fun onPageCommitVisible() + fun onSurveyDismissByBack() fun onError(error: Exception) fun onSuccess(successType: SuccessType) } @@ -34,6 +35,7 @@ object Formbricks { internal var language: String = "default" internal var loggingEnabled: Boolean = true internal var autoDismissErrors: Boolean = true + internal var isBackPressEnable: Boolean = false private var fragmentManager: FragmentManager? = null internal var isInitialized = false @@ -74,6 +76,7 @@ object Formbricks { loggingEnabled = config.loggingEnabled fragmentManager = config.fragmentManager autoDismissErrors = config.autoDismissErrors + isBackPressEnable = config.isBackPressEnable config.userId?.let { UserManager.set(it) } config.attributes?.let { UserManager.setAttributes(it) } config.attributes?.get("language")?.let { UserManager.setLanguage(it) } diff --git a/android/src/main/java/com/formbricks/android/helper/FormbricksConfig.kt b/android/src/main/java/com/formbricks/android/helper/FormbricksConfig.kt index b33bd48..86aa376 100644 --- a/android/src/main/java/com/formbricks/android/helper/FormbricksConfig.kt +++ b/android/src/main/java/com/formbricks/android/helper/FormbricksConfig.kt @@ -17,6 +17,7 @@ class FormbricksConfig private constructor( val loggingEnabled: Boolean, val fragmentManager: FragmentManager?, val autoDismissErrors: Boolean = true, + val isBackPressEnable: Boolean = false, ) { class Builder(private val appUrl: String, private val environmentId: String) { private var userId: String? = null @@ -24,6 +25,7 @@ class FormbricksConfig private constructor( private var loggingEnabled = false private var fragmentManager: FragmentManager? = null private var autoDismissErrors = true + private var isBackPressEnable = false fun setUserId(userId: String): Builder { this.userId = userId @@ -55,6 +57,11 @@ class FormbricksConfig private constructor( return this } + fun setIsBackPressEnable(isBackPressEnable: Boolean): Builder{ + this.isBackPressEnable = isBackPressEnable + return this + } + fun build(): FormbricksConfig { return FormbricksConfig( appUrl = appUrl, @@ -63,7 +70,8 @@ class FormbricksConfig private constructor( attributes = attributes, loggingEnabled = loggingEnabled, fragmentManager = fragmentManager, - autoDismissErrors = autoDismissErrors + autoDismissErrors = autoDismissErrors, + isBackPressEnable = isBackPressEnable ) } } diff --git a/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt b/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt index d007dd0..082a8d5 100644 --- a/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt +++ b/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt @@ -11,6 +11,7 @@ import android.os.Handler import android.os.Looper import android.provider.OpenableColumns import android.util.Base64 +import android.view.KeyEvent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -49,7 +50,7 @@ class FormbricksFragment(val hiddenFields: Map? = null) : BottomShe override fun onClose() { Handler(Looper.getMainLooper()).post { Formbricks.callback?.onSurveyClosed() - dismiss() + dismissAllowingStateLoss() } } @@ -75,7 +76,7 @@ class FormbricksFragment(val hiddenFields: Map? = null) : BottomShe val error = SDKError.unableToLoadFormbicksJs Formbricks.callback?.onError(error) Logger.e(error) - dismiss() + dismissAllowingStateLoss() } }) @@ -163,7 +164,7 @@ class FormbricksFragment(val hiddenFields: Map? = null) : BottomShe if (cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) { Formbricks.callback?.onError(SDKError.surveyDisplayFetchError) if (Formbricks.autoDismissErrors) { - dismiss() + dismissAllowingStateLoss() } } val log = "[CONSOLE:${cm.messageLevel()}] \"${cm.message()}\", source: ${cm.sourceId()} (${cm.lineNumber()})" @@ -209,6 +210,21 @@ class FormbricksFragment(val hiddenFields: Map? = null) : BottomShe } viewModel.loadHtml(surveyId = surveyId, hiddenFields = hiddenFields) + handleBackPressIfEnable() + } + + private fun handleBackPressIfEnable() { + if (Formbricks.autoDismissErrors) { + dialog?.setOnKeyListener { _, keyCode, event -> + if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { + dismissAllowingStateLoss() + Formbricks.callback?.onSurveyDismissByBack() + true + } else { + false + } + } + } } private fun getFileName(uri: Uri): String? { @@ -243,6 +259,14 @@ class FormbricksFragment(val hiddenFields: Map? = null) : BottomShe } } + override fun onDestroyView() { + super.onDestroyView() + binding.formbricksWebview.removeJavascriptInterface(WebAppInterface.INTERFACE_NAME) + binding.formbricksWebview.webChromeClient = null + (binding.formbricksWebview.parent as? ViewGroup)?.removeView(binding.formbricksWebview) + binding.formbricksWebview.destroy() + } + companion object { private val TAG: String by lazy { FormbricksFragment::class.java.simpleName } From 444d9d9de2d925a7d2d6d28bd7c51fba7645c504 Mon Sep 17 00:00:00 2001 From: longvantruong Date: Wed, 28 May 2025 10:35:44 +0700 Subject: [PATCH 09/19] chore: handle back press survey --- .../java/com/formbricks/android/webview/FormbricksFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt b/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt index 082a8d5..3285725 100644 --- a/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt +++ b/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt @@ -214,7 +214,7 @@ class FormbricksFragment(val hiddenFields: Map? = null) : BottomShe } private fun handleBackPressIfEnable() { - if (Formbricks.autoDismissErrors) { + if (Formbricks.isBackPressEnable) { dialog?.setOnKeyListener { _, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { dismissAllowingStateLoss() From d1f11eb21ba5d4889dfe098bc41ce4ac9fbd106e Mon Sep 17 00:00:00 2001 From: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com> Date: Tue, 3 Jun 2025 16:19:44 +0530 Subject: [PATCH 10/19] fix: fixes the pixel fold issue (#24) * fixes the pixel fold issue * adds errors * fixes log --- .../android/model/error/SDKError.kt | 3 + .../android/webview/FormbricksFragment.kt | 85 +++++++++++++------ .../android/webview/FormbricksViewModel.kt | 16 ++-- 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/android/src/main/java/com/formbricks/android/model/error/SDKError.kt b/android/src/main/java/com/formbricks/android/model/error/SDKError.kt index 8cfa9d5..bc32c2b 100644 --- a/android/src/main/java/com/formbricks/android/model/error/SDKError.kt +++ b/android/src/main/java/com/formbricks/android/model/error/SDKError.kt @@ -24,4 +24,7 @@ object SDKError { val surveyNotFoundError = RuntimeException("No survey found matching the action class.") val noUserIdSetError = RuntimeException("No userId is set, please set a userId first using the setUserId function") + val couldNotCreateDisplayError = RuntimeException("Something went wrong while creating a display. Please try again later") + val couldNotCreateResponseError = RuntimeException("Something went wrong while creating a response. Please try again later") + val somethingWentWrongError = RuntimeException("Something went wrong. Please try again later") } diff --git a/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt b/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt index 2c954e2..80b0b07 100644 --- a/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt +++ b/android/src/main/java/com/formbricks/android/webview/FormbricksFragment.kt @@ -37,26 +37,35 @@ import com.google.gson.JsonObject import java.io.ByteArrayOutputStream import java.io.InputStream - class FormbricksFragment : BottomSheetDialogFragment() { - private lateinit var binding: FragmentFormbricksBinding private lateinit var surveyId: String private val viewModel: FormbricksViewModel by viewModels() + private var isDismissing = false private var webAppInterface = WebAppInterface(object : WebAppInterface.WebAppCallback { override fun onClose() { Handler(Looper.getMainLooper()).post { - dismiss() + safeDismiss() } } override fun onDisplayCreated() { - SurveyManager.onNewDisplay(surveyId) + try { + SurveyManager.onNewDisplay(surveyId) + } catch (e: Exception) { + val error = SDKError.couldNotCreateDisplayError + Logger.e(error) + } } override fun onResponseCreated() { - SurveyManager.postResponse(surveyId) + try { + SurveyManager.postResponse(surveyId) + } catch (e: Exception) { + val error = SDKError.couldNotCreateResponseError + Logger.e(error) + } } override fun onFilePick(data: FileUploadData) { @@ -71,7 +80,7 @@ class FormbricksFragment : BottomSheetDialogFragment() { override fun onSurveyLibraryLoadError() { val error = SDKError.unableToLoadFormbicksJs Logger.e(error) - dismiss() + safeDismiss() } }) @@ -110,6 +119,13 @@ class FormbricksFragment : BottomSheetDialogFragment() { } } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let { + surveyId = it.getString(ARG_SURVEY_ID) ?: throw IllegalArgumentException("Survey ID is required") + } + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = FragmentFormbricksBinding.inflate(inflater).apply { lifecycleOwner = viewLifecycleOwner @@ -147,14 +163,17 @@ class FormbricksFragment : BottomSheetDialogFragment() { dialog?.window?.setDimAmount(0.0f) binding.formbricksWebview.setBackgroundColor(Color.TRANSPARENT) binding.formbricksWebview.let { + // First configure the WebView + it.settings.apply { + javaScriptEnabled = true + domStorageEnabled = true + loadWithOverviewMode = true + useWideViewPort = true + } + it.webChromeClient = object : WebChromeClient() { override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { consoleMessage?.let { cm -> - if (cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) { - val error = SDKError.surveyDisplayFetchError - Logger.e(error) - dismiss() - } val log = "[CONSOLE:${cm.messageLevel()}] \"${cm.message()}\", source: ${cm.sourceId()} (${cm.lineNumber()})" Logger.d(log) } @@ -162,13 +181,6 @@ class FormbricksFragment : BottomSheetDialogFragment() { } } - it.settings.apply { - javaScriptEnabled = true - domStorageEnabled = true - loadWithOverviewMode = true - useWideViewPort = true - } - it.webViewClient = object : WebViewClient() { override fun onReceivedError( view: WebView?, @@ -192,11 +204,9 @@ class FormbricksFragment : BottomSheetDialogFragment() { } it.setInitialScale(1) - it.addJavascriptInterface(webAppInterface, WebAppInterface.INTERFACE_NAME) + viewModel.loadHtml(surveyId) } - - viewModel.loadHtml(surveyId) } private fun getFileName(uri: Uri): String? { @@ -231,15 +241,40 @@ class FormbricksFragment : BottomSheetDialogFragment() { } } + private fun safeDismiss() { + if (isDismissing) return + isDismissing = true + + try { + if (isAdded && !isStateSaved) { + dismiss() + } else { + // If we can't dismiss safely, just finish the activity + activity?.finish() + } + } catch (e: Exception) { + val error = SDKError.somethingWentWrongError + Logger.e(error) + activity?.finish() + } + } + + override fun onDestroy() { + super.onDestroy() + isDismissing = false + } + companion object { private val TAG: String by lazy { FormbricksFragment::class.java.simpleName } + private const val ARG_SURVEY_ID = "survey_id" fun show(childFragmentManager: FragmentManager, surveyId: String) { - val fragment = FormbricksFragment() - fragment.surveyId = surveyId + val fragment = FormbricksFragment().apply { + arguments = Bundle().apply { + putString(ARG_SURVEY_ID, surveyId) + } + } fragment.show(childFragmentManager, TAG) } - - private const val CLOSING_TIMEOUT_IN_SECONDS = 5L } } \ No newline at end of file diff --git a/android/src/main/java/com/formbricks/android/webview/FormbricksViewModel.kt b/android/src/main/java/com/formbricks/android/webview/FormbricksViewModel.kt index 2d901af..e635e7d 100644 --- a/android/src/main/java/com/formbricks/android/webview/FormbricksViewModel.kt +++ b/android/src/main/java/com/formbricks/android/webview/FormbricksViewModel.kt @@ -34,11 +34,11 @@ class FormbricksViewModel : ViewModel() { -
+