Вопрос или проблема
В моем Android приложении, созданном с использованием Jetpack Compose и Kotlin, я использую Android Webview для загрузки URL-адреса проверки Mollie для платежей. URL (https://www.mollie.com/checkout/select-method/SOMECODESPECIFICTOPAYMENT) отображает содержимое, похожее на это
Сначала все работало, и Android Webview правильно загружал URL-адреса Mollie, но теперь пользователи на живом приложении сталкиваются с проблемой отображения пустой страницы, т.е. Webview загружает URL, но содержимое не отображается.
Ниже приведен BookingPaymentScreen, который загружает URL
@RequiresApi(Build.VERSION_CODES.O)
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun BookingPaymentScreen(
navController: NavController,
sharedViewModel: SharedViewModel,
addBookingViewModel: AddBookingViewModel,
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val result = navController.previousBackStackEntry?.savedStateHandle?.get<Boolean>("isFromRetry")
val resultUrl = navController.previousBackStackEntry?.savedStateHandle?.get<String>("url")
val errorToastState = remember { mutableStateOf(false) }
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = { activityResult ->
addBookingViewModel.retryCallMade = false
val bookingDetail = BookingDetail(
bookingId = addBookingViewModel.bookingFinalUiState.bookingId,
ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken,
url = addBookingViewModel.bookingFinalUiState.paymentUrl
)
navController.currentBackStackEntry?.savedStateHandle?.set(
key = "bookingDetail", value = bookingDetail
)
navController.navigate(BOOKING_DETAIL_ROUTE)
})
BackHandler {
goBackBookingPaymentScreen(addBookingViewModel, navController)
}
// Убедитесь, что логика повторной попытки выполняется только один раз и после того, как компонент отображен
LaunchedEffect(result) {
if (result == true && !addBookingViewModel.retryCallMade) {
addBookingViewModel.onEvent(AddBookingUiEvent.onRetryPayment)
addBookingViewModel.retryCallMade = true
sharedViewModel.logAnalyticsEvent(AnalyticsEvents.mlBookingsRetryPaymentBtnClicked)
}
}
Surface(
modifier = Modifier
.background(
color = PallaAppTheme.colors.primary
)
.fillMaxHeight()
.statusBarsPadding()
) {
if (errorToastState.value) {
Toast.makeText(context, "Приложение не установлено или не настроено", Toast.LENGTH_LONG).show()
errorToastState.value = false
}
if (addBookingViewModel.checkoutLoading) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(AppVariables.AppPading)
.background(PallaAppTheme.AppBackground), contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = PallaAppTheme.colors.secondary,
strokeWidth = 2.dp
)
}
} else {
AndroidView(modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.navigationBarsPadding(),
factory = { context ->
WebView(context).apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
webViewClient = object : WebViewClient() {
override fun onPageCommitVisible(view: WebView?, url: String?) {
super.onPageCommitVisible(view, url)
}
override fun onPageStarted(
view: WebView?, url: String?, favicon: Bitmap?
) {
super.onPageStarted(view, url, favicon)
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
}
override fun shouldOverrideUrlLoading(
view: WebView?, request: WebResourceRequest?
): Boolean {
Log.d("shouldOverrideUrlLoading", request?.url.toString())
return handleOverrideUrlLoading(webView = view,
url = request?.url.toString(),
addBookingViewModel,
navController,
context,
launcher,
onBankingAppNotFound = {
errorToastState.value = true
})
}
@Deprecated("Устарело в Java")
override fun shouldOverrideUrlLoading(
view: WebView?, url: String?
): Boolean {
Log.d("shouldOverrideUrlLoading", "$url")
return handleOverrideUrlLoading(webView = view,
url = url,
addBookingViewModel,
navController,
context,
launcher,
onBankingAppNotFound = {
errorToastState.value = true
})
}
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
Log.d("onReceivedError", error.toString())
val error1 = error
val abc = "fddg"
super.onReceivedError(view, request, error)
}
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
Log.d("onReceivedSslError", error.toString())
// игнорировать ssl ошибку
if (handler != null) {
handler.proceed()
} else {
super.onReceivedSslError(view, null, error)
}
}
}
// WebChromeClient для логов консоли JavaScript
webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
Log.d("WebView Console", "${consoleMessage.message()} -- из строки " +
"${consoleMessage.lineNumber()} из ${consoleMessage.sourceId()}")
return super.onConsoleMessage(consoleMessage)
}
}
var url = addBookingViewModel.bookingFinalUiState.paymentUrl
if (url.isEmpty()) {
url = resultUrl ?: ""
}
loadUrl(url)
}
},
update = { webView ->
// Обновите WebView, если это необходимо
//CookieManager.getInstance()?.setAcceptThirdPartyCookies(webView, true)
})
}
}
}
fun goBackBookingPaymentScreen(
addBookingViewModel: AddBookingViewModel, navController: NavController
) {
addBookingViewModel.retryCallMade = false
val bookingDetail = BookingDetail(
bookingId = addBookingViewModel.bookingFinalUiState.bookingId,
ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken
)
navController.currentBackStackEntry?.savedStateHandle?.set(
key = "bookingDetail", value = bookingDetail
)
navController.navigate(BOOKING_DETAIL_ROUTE)
}
fun isDeepLink(url: String?): Boolean {
if ((url?.startsWith("nl") == true && url.contains("payloadUri"))) {
return true
}
if (url?.startsWith("http") == false && !url.startsWith("https") && url.contains("://")) {
return true
}
return false
}
//СТАРАЯ ФУНКЦИЯ С ГЛУБОКИМ СССЫЛКИ
private fun handleOverrideUrlLoading(
webView: WebView?,
url: String?,
addBookingViewModel: AddBookingViewModel,
navController: NavController,
context: Context,
launcher: ManagedActivityResultLauncher<Intent, ActivityResult>,
onBankingAppNotFound: () -> Unit
): Boolean {
if (url != null && url.startsWith(AppDeepLinks.COURT_BOOKING)) {
addBookingViewModel.retryCallMade = false
val bookingDetail = BookingDetail(
bookingId = addBookingViewModel.bookingFinalUiState.bookingId,
ownerToken = addBookingViewModel.bookingFinalUiState.ownerToken,
url = addBookingViewModel.bookingFinalUiState.paymentUrl
)
navController.currentBackStackEntry?.savedStateHandle?.set(
key = "bookingDetail", value = bookingDetail
)
navController.navigate(BOOKING_DETAIL_ROUTE)
return true
}
if (isIntentLink(url)) {
handleBankingAppIntent(url ?: "", context, launcher, onBankingAppNotFound = {
onBankingAppNotFound()
})
return true
}
if (isDeepLink(url)) {
try {
val intent = Intent(ACTION_VIEW, Uri.parse(url))
//context.startActivity(intent)
launcher.launch(intent)
} catch (e: ActivityNotFoundException) {
// Доступны только приложения браузеров или браузер является значением по умолчанию.
// Поэтому вы можете открыть URL-адрес напрямую в своем приложении, например,
e.printStackTrace()
onBankingAppNotFound()
}
return true
}
url?.let {
webView?.loadUrl(it)
return true
}
return true
}
fun isIntentLink(url: String?): Boolean {
if ((url?.startsWith("intent://") == true)) {
return true
}
return false
}
/*// В некоторых случаях Mollie создает неправильный Intent Android Chrome (с маленькой буквы)
// https://developer.chrome.com/multidevice/android/intents
private fun String.fixedIntentName(): String = replace("#intent;", "#Intent;")*/
private fun handleBankingAppIntent(
url: String, context: Context,
launcher: ManagedActivityResultLauncher<Intent, ActivityResult>,
onBankingAppNotFound: () -> Unit
) {
try {
val intent = Intent.parseUri(
url, Intent.URI_INTENT_SCHEME
)
try {
// context.startActivity(intent)
launcher.launch(intent)
} catch (e: ActivityNotFoundException) {
// Доступны только приложения браузеров или браузер является значением по умолчанию.
// Поэтому вы можете открыть URL-адрес напрямую в своем приложении, например,
e.printStackTrace()
onBankingAppNotFound()
}
} catch (e: URISyntaxException) {
e.printStackTrace()
onBankingAppNotFound()
}
}
Я пробовал настройки Webview, такие как начальный масштаб, масштабирование, прокрутка, статическая высота/ширина и разные строки пользовательского агента, но ничего не работает. Такие же URL-адреса платежей Mollie работают в приложении iOS.
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
CookieManager.getInstance().setAcceptThirdPartyCookies(this, true)
settings.userAgentString="Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36"
Любая помощь будет оценена.
Ответ или решение
Проблема с отображением URL-адреса Mollie в Android WebView
Введение
В данном руководстве мы рассматриваем проблему, с которой столкнулись разработчики приложений на Android, использующих WebView для загрузки страниц Молли (Mollie) для обработки платежей. Хотя первоначально всё работало корректно, пользователи вашей живой версии приложения сейчас сталкиваются с ситуацией, когда веб-страница просто не загружается. Это может быть вызвано несколькими факторами, включая настройки WebView, ошибки на стороне сервера или изменения в URL-адресе. В этой статье мы детально разберём вашу ситуацию и предложим возможные решения.
Описание проблемы
В вашей реализации используется Android WebView для загрузки URL адреса платежной системы Mollie, который формируется следующим образом:
https://www.mollie.com/checkout/select-method/SOMECODESPECIFICTOPAYMENT
Несмотря на то, что URL корректно загружается, пользователи видят пустую страницу. Важно отметить, что этот URL успешно работает в приложении iOS, что указывает на потенциальные проблемы, специфичные для Android WebView.
Возможные причины проблемы
- Блокировка JavaScript: Если JavaScript не будет включен, страницы, требующие выполнения скриптов, могут не загружаться должным образом.
- Настройки безопасности: Некоторые настройки, такие как
mixedContentMode
иsetAcceptThirdPartyCookies
, могут быть неправильно настроены, что приведёт к блокировке контента. - Проблемы с кэшированием: Временные файлы и кэш WebView могут содержать старые данные, которые могут создавать конфликты.
- Проблемы на сервере: Это может быть вызвано изменениями на стороне Mollie в API или политике загрузки контента.
- Разные версии WebView: Разные устройства могут использовать разные версии WebView, что может сказаться на совместимости.
Рекомендации и решения
-
Проверьте настройки WebView:
-
Убедитесь, что JavaScript включен:
settings.javaScriptEnabled = true
-
Убедитесь, что включено принятие сторонних файлов cookie:
CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true)
-
Проверьте настройки для Mixed Content:
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
-
-
Сбросьте кэш WebView:
Очищайте кэш WebView перед загрузкой нового URL, чтобы избежать загрузки устаревших данных:webView.clearCache(true)
-
Логирование ошибок:
Добавьте более обширное логирование ошибок, чтобы выяснить, какая именно ошибка возникает при получении страниц:override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) { Log.e("WebView Error", "Error loading page: ${error?.description}") }
-
Тестирование User-Agent:
Попробуйте изменить строку User-Agent, чтобы убедиться, что сервер Mollie не блокирует ваш WebView:settings.userAgentString = "Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36"
-
Обновление WebView:
Убедитесь, что у вас установлена последняя версия WebView. Иногда проблемы могут устраняться через обновления приложения. -
Документация от Mollie:
Обратитесь к официальной документации Mollie и службы поддержки, чтобы убедиться, что нет изменений на стороне сервера, о которых вы не знаете.
Заключение
Проблема с отображением URL-адреса Mollie в Android WebView может быть вызвана множеством факторов. Применение вышеуказанных решений должно помочь в диагностике и исправлении ситуации. Если проблема сохраняется, вы можете рассмотреть возможность использования внешнего браузера для обработки платежей, вдобавок к вашему текущему решению. Обязательно продолжайте отслеживать производительность вашего приложения и собирайте отзывы пользователей для дальнейших улучшений.
SEO Оптимизация
- Ключевые слова: Android WebView, Mollie, платежная система, Kotlin, JavaScript, проблемы с отображением.
- Заголовки и подзаголовки используют простые и понятные формулировки.
- Приведение примеров кода для лучшего понимания тем.