Enable Users to Accept Offer
This section describes how to build an offer click experience. This section comprises the following topics:
1.0. Integrate Using IQL adClickRedirection API
Submit the user’s personal information to the adClick Redirection Endpoint V3 (GET) endpoint. This endpoint is received in the button_click_url
field of the adUnit
v2 response. The button_click_url
comes pre-populated with authentication information so no API KEY is required to call this endpoint. Therefore this API call can be directly made by the mobile clients to the IQL server without the intervention of the publisher’s backend.
Click here to read the documentation for the adClickRedirection
API.
Refer to the following code snippets to use the adClickRedirection
endpoint on the client side:
package com.zendrive.iqlref.ui.home.personalinformation.presentation
....
....
class PersonalInformationFragment : Fragment() {
private val viewModel: PersonalInformationViewModel by viewModels()
private lateinit var binding: FragmentPersonalInformationNewBinding
....
....
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
....
....
with(binding) {
viewModel.getAdUnitDetails { adUnitResponse ->
Timber.d("adUnitResponse: $adUnitResponse")
adUnitResponse?.let {
insurerName = it.adUnit.insurerName
if (it.adUnit.legal.termsAndConditionsUrl.isNullOrEmpty()
.not() && it.adUnit.legal.privacyPolicyUrl.isNullOrEmpty().not()
) {
termsAndCondition.makeVisible()
termsAndCondition.text = setSpanURL(it.adUnit)
termsAndCondition.enableMovementLinks()
}
it.adUnit.legal.disclaimer?.let {
if (it.generic.isNullOrEmpty().not()) {
disclaimer.setTextInTv(it.generic)
}
}
buttonClickUrl = it.adUnit.buttonClickUrl
viewModel.loadPersonalInformation()
}
}
buttonConfirm.setOnClickListener {
buttonClickUrl?.let { url ->
handleConfirmButtonClick(url)
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.description.collectLatest {
letsMakeSure.text = getString(
it,
insurerName
)
}
}
}
}
...
...
...
private fun handleConfirmButtonClick(buttonClickUrl: String) {
with(binding) {
val firstName = firstNameEditText.extractString()
val lastName = lastNameEditText.extractString()
val email = emailEditText.extractTrimmedString()
val dob = dobEditText.extractString()
val streetInfo = streetEditText.extractString()
val city = cityEditText.extractString()
val stateCode = stateEditText.extractString()
val zipCode = zipCodeEditText.extractString()
viewModel.savePersonalInformation(
firstName = firstName,
lastName = lastName,
email = email,
dob = dob,
streetInfo = streetInfo,
city = city,
stateCode = stateCode,
zipCode = zipCode
)
val request = viewModel.createPersonalInformationRequestPayload(
firstName = firstName,
lastName = lastName,
email = email,
dob = dob,
streetInfo = streetInfo,
city = city,
stateCode = stateCode,
zipCode = zipCode
)
if (request != null) {
val updatedButtonClickUrl =
viewModel.replacePayloadWithRequest(buttonClickUrl, request)
Timber.d("Appended Get Url $updatedButtonClickUrl")
if (isInternetConnected) {
InsurerWebsiteActivity.startInsurerWebsiteActivity(
activity = requireActivity(),
url = updatedButtonClickUrl
)
userNavigatedToInsurerPage = true
} else {
DialogHelper.showNoInternetDialog(
weakActivity = WeakReference(requireActivity()),
onPositiveActionClicked = {
it.dismiss()
}
)
}
}
}
}
....
....
....
}
Use the following code snippets to create the payload for the request:
package com.zendrive.iqlref.ui.home.personalinformation.presentation
import android.content.Context
....
....
class PersonalInformationViewModel : ViewModel() {
private val iQLApisRepository = ZWLCore.iqlApisRepository()
private val savePersonalInformationUseCase = ZWLCore.savePersonalInformationUseCase()
private val getPersonalInformationUseCase = ZWLCore.getPersonalInformationUseCase()
....
....
/**
* get ad unit details
*/
fun getAdUnitDetails(callback: (AdUnitResponse?) -> Unit) {
viewModelScope.launch(AppDispatchers.IO) {
val adDetails = iQLApisRepository.getAdUnitDetails()
viewModelScope.launch(Dispatchers.Main) {
callback(adDetails)
}
}
}
fun replacePayloadWithRequest(url: String, encodedBase64Url: String): String {
return url.replace(PAYLOAD_PLACEHOLDER, encodedBase64Url)
}
private fun toJsonString(personalInformation: PersonalInformation): String {
return personalInformation.asJSON()
}
private fun convertToBase64String(jsonStringPayload: String): String {
return jsonStringPayload.encodeUsingBase64()
}
fun createPersonalInformationRequestPayload(
firstName: String,
lastName: String,
email: String,
dob: String,
streetInfo: String,
city: String,
stateCode: String,
zipCode: String
): String? {
val personalInformation =
PersonalInformation(
firstName = firstName,
lastName = lastName,
email = email,
dateOfBirth = DateUtils.convertDateToOtherFormatFromGivenFormat(
fromFormat = DateUtils.MM_DD_YYYY,
toFormat = DateUtils.YYYY_MM_DD,
date = dob
),
address = Address(
street = streetInfo,
city = city,
stateCode = stateCode,
zipCode = zipCode
)
)
val jsonStringPayload = toJsonString(personalInformation)
val base64Payload = convertToBase64String(jsonStringPayload)
val urlEncoder = UrlUtils.encodeUrl(base64Payload)
Timber.d("json payload $jsonStringPayload")
Timber.d("base64Payload $base64Payload")
Timber.d("urlEncoder $urlEncoder")
return urlEncoder
}
}
As detailed in the code snippets given above, create a base64
encoded payload with the user information provided by the user. Replace the <payload
> string in the buttonClickURL
field received in the adUnitDetails
API with the generated payload and load it using Webview.
2.0. Enable Opening Of Offer In a Webview
Use the following code to enable the user to open an offer either in an in-app browser, or in webview mode:


2.1. Fallback for In-app Custom Tabs
If in-app custom tabs are not supported by your device browser, allow your users to use webview as the fallback mechanism to open the insurer’s webpage.
class InsurerWebsiteActivity : AppBaseActivity() {
private lateinit var binding: ActivityInsurerWebsiteBinding
private var shouldOverrideUrl = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityInsurerWebsiteBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
showProgress()
intent.getStringExtra(URL_OBJECT)?.let {
openUrlInWebView(it)
} ?: run {
Timber.e("button click url is either empty or null")
Toast.makeText(this, getString(R.string.no_web_link_found), Toast.LENGTH_SHORT).show()
hideProgress()
}
registerOnBackPressCallBack(enabled = true) {
showExitDialog()
}
}
private fun showProgress() {
binding.progressBar.makeVisible()
}
private fun hideProgress() {
binding.progressBar.makeGone()
}
private fun openUrlInWebView(url: String) {
lifecycleScope.launch {
with(binding) {
webView.settings.allowFileAccess = false
webView.settings.javaScriptEnabled = true
binding.webView.webChromeClient = WebChromeClient()
webView.webViewClient = object : WebViewClient() {
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
Timber.e(" Redirection API Error received: $error")
}
override fun onReceivedHttpError(
view: WebView?,
request: WebResourceRequest?,
errorResponse: WebResourceResponse?
) {
super.onReceivedHttpError(view, request, errorResponse)
Timber.d("Redirection API Error onReceivedHttpError: $errorResponse ${errorResponse?.reasonPhrase}")
}
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
super.onReceivedSslError(view, handler, error)
Timber.d("onReceivedSslError")
}
override fun shouldOverrideUrlLoading(
webView: WebView,
request: WebResourceRequest
): Boolean {
val uri = request.url
shouldOverrideUrl = true
return shouldOverrideUrlLoading(uri.toString(), webView)
}
private fun shouldOverrideUrlLoading(url: String, webView: WebView): Boolean {
Timber.d("shouldOverrideUrlLoading() URL : $url")
webView.loadUrl(url)
// Returning True means that application wants to leave the current WebView and handle the url itself, otherwise return false.
return true
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
shouldOverrideUrl = false
showProgress()
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
view?.let {
val webViewTitle = it.title
setPageTitle(webViewTitle)
if (shouldOverrideUrl.not()) {
hideProgress()
}
}
}
private fun setPageTitle(webViewTitle: String?) {
supportActionBar?.let {
webViewTitle?.let { webTitle ->
it.title = webTitle
}
}
}
}
webView.loadUrl(url)
}
}
}
override fun onSupportNavigateUp(): Boolean {
showExitDialog()
return true
}
private fun showExitDialog() {
val bottomSheetFragment = ExitInsurerWebsiteBottomSheetDialog.newInstance()
bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.tag)
}
companion object {
private const val URL_OBJECT = "url_object"
@JvmStatic
fun startInsurerWebsiteActivity(activity: FragmentActivity, url: String) {
Intent(activity, InsurerWebsiteActivity::class.java).apply {
putExtra(URL_OBJECT, url)
}.also {
activity.startActivity(it)
}
}
}
}
3.0. IQL Reference Application UX Design
Here is the overall design implementation for the IQL Reference Application:
4.0. Publisher Integration Testing Checklist
Use the following testing checklist to ensure that the user is enabled to accept an offer:
When an offer is presented, ensure that the user is successfully directed to the insurer's website. Validate that the in-app browser or webview experience is working smoothly. Check that the user is able to successfully submit any missing details.