I have tried it on android 7.0, it works, but on android 5,6 it does not work, i could not find any clue on what maybe going wrong.
The URL redirect to payment portal and at the end it calls Android.postMessage("success") from Javascript, but it does not get called in native this problem only persist in android 5 and maybe on android 6 too.
My minimum SDK level is 21.
class PaymentActivity : BaseActivity<ActivityPaymentBinding, IPaymentMvvm.ViewModel>(), IPaymentMvvm.View {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityComponent.inject(this)
setAndBindContentView(savedInstanceState, R.layout.activity_payment)
viewModel.initVM()
binding.web.webViewClient = Web(viewModel)
binding.web.settings.javaScriptEnabled = true
binding.web.settings.loadWithOverviewMode = true
binding.web.settings.useWideViewPort = true
binding.web.addJavascriptInterface(JSBridge(this, this), "Android")
val url = intent.extras.getString("url")
if (url == null)
finish()
binding.web.loadUrl(url)
}
override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
android.R.id.home ->
finish()
}
return super.onOptionsItemSelected(menuItem)
}
class Web(val viewModel: IPaymentMvvm.ViewModel) : WebViewClient() {
init {
viewModel.setProgress(true)
}
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
view?.loadUrl(request?.url.toString())
return true
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
viewModel.setProgress(false)
}
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
super.onReceivedError(view, request, error)
viewModel.setProgress(false)
Timber.e(error.toString())
}
}
}
class JSBridge(val context: Context, val activity: Activity) {
#JavascriptInterface
fun postMessage(message: String) {
// here we return true if we handled the post.
Timber.i(message)
context.toast(message)
val intent = Intent(context, OrderThanksActivity::class.java)
intent.putExtra(Enums.contactDetail.contactNumber.name, activity.intent.getStringExtra(Enums.contactDetail.contactNumber.name))
intent.putExtra(Enums.contactDetail.phoneNumber.name, activity.intent.getStringExtra(Enums.contactDetail.phoneNumber.name))
intent.putExtra(Enums.contactDetail.cellNumber.name, activity.intent.getStringExtra(Enums.contactDetail.cellNumber.name))
activity.startActivity(intent)
activity.finish()
}
}
It was the problem of JavaScript script from web, which threw an error in a script running in web view before my interface function even get called, but the weird behavior was that script only got terminated on Android 5,6 while Android 7 web view does not terminate execution and execute the whole script regarding error that's why the interface function got called in Android 7.
Related
Problem description
When debugging the web portion of my app either in chrome://inspect/#devices or directly in android studio I can see that I'm getting swamped with the message "Notifying Native Hook".
When using chrome://inspect/#devices to inspect the web page used in the app I see that the message has the source VM298:5 (quite sure that I've seen it with another number as well after VM). When clicking on the source I find the following function:
(function () {
if (document.readyState === "complete") {
var foundMessageBridge = false
function notifyMessageBridge() {
console.log("Notifying Native Hook")
if (window.__KlarnaNativeHook != null) {
console.log("Klarna Native Hook was notified")
window.__KlarnaNativeHook.setNativeReady()
foundMessageBridge = true
} else {
window.setTimeout(notifyMessageBridge, 500)
}
}
notifyMessageBridge()
}
window.addEventListener('load', () => {
let interval = null
let notifyMessageBridge = () => {
if (window.__KlarnaNativeHook != null) {
window.__KlarnaNativeHook.setNativeReady()
clearInterval(interval)
}
}
interval = setInterval(notifyMessageBridge, 500)
})
}())
Which finally led me to that it has something to do with Klarna. Since I'm getting swamped with this I suppose that my app is not getting into the if (window.__KlarnaNativeHook != null).
After some debugging I noticed the following:
When I start the interaction with Klarna my app enters if (window.__KlarnaNativeHook != null). But only once and the spamming resumes afterwards.
If I remove the line (activity as MainActivity).klarnaHybridSDK.addWebView(myWebView) the message swamping stops. Which I suppose is not that surprising.
Apart from my app's web page empty untitled web pages are generated which only prints out "Notifying Native Hook" in the console and nothing else, no code, nothing.
Apart from the swamping the app is working fine with Klarna.
I'm not sure if this is a bug in the SDK or an error of mine.
Any suggestions on how to stop the spamming "Notifying Native Hook"?
(I know how to filter it, but 120 messages/min * (my app's webpage + the untitleds generated) and doing it every time is getting tiresome)
Code
MainActivity
class MainActivity : AppCompatActivity(), KlarnaHybridSDKCallback {
...
lateinit var klarnaHybridSDK : KlarnaHybridSDK
override fun onCreate(savedInstanceState: Bundle?) {
klarnaHybridSDK = KlarnaHybridSDK("myapp://", this)
...
}
override fun didHideFullscreenContent(webView: WebView, completion: OnCompletion) {
Timber.i("Klarna didHideFullscreenContent called")
completion.run()
}
override fun didShowFullscreenContent(webView: WebView, completion: OnCompletion) {
Timber.i("Klarna didShowFullscreenContent called")
completion.run()
}
override fun onErrorOccurred(webView: WebView, error: KlarnaMobileSDKError) {
Timber.i("Klarna onErrorOccurred called")
Timber.i("Klarna Error: $error")
}
override fun willHideFullscreenContent(webView: WebView, completion: OnCompletion) {
Timber.i("Klarna willHideFullscreenContent called")
completion.run()
}
override fun willShowFullscreenContent(webView: WebView, completion: OnCompletion) {
Timber.i("Klarna willShowFullscreenContent called")
completion.run()
}
}
Fragment Class
class myFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
...
/*WEBVIEW*/
myWebView.settings.javaScriptEnabled = true
WebView.setWebContentsDebuggingEnabled(true)
val context = this.activity?.applicationContext
myWebView.addJavascriptInterface(
WebAppInterface(
myWebView, context
), "Android")
myWebView.settings.loadWithOverviewMode = true
myWebView.settings.useWideViewPort = true
myWebView.settings.domStorageEnabled = true
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP) {
myWebView.settings.mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
}
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){
myWebView.settings.cacheMode = WebSettings.LOAD_NO_CACHE
if(this.activity?.applicationInfo?.flags != 0 && ApplicationInfo.FLAG_DEBUGGABLE != 0) {
WebView.setWebContentsDebuggingEnabled(true)
Timber.i("setWebContentsDebuggingEnabled")
}
}
ticketWebView.webViewClient = object: WebViewClient(){
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)
...
if (view != null) {
(activity as MainActivity).klarnaHybridSDK.newPageLoad(view)
}
...
}
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
...
}
override fun onReceivedHttpError(view: WebView?, request: WebResourceRequest?, errorResponse: WebResourceResponse?) {
super.onReceivedHttpError(view, request, errorResponse)
...
//Added to prevent Klarna's address search failing to cause the app to show the error screen.
if (request?.url.toString().contains("klarna.com/eu/address")) {
Timber.i("${receivedError}")
Timber.i("Ignoring error since it's simply Klarna's address search failing")
}
...
}
#TargetApi(21)
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val url = request?.url
...
return (activity as MainActivity).klarnaHybridSDK.shouldFollowNavigation(url.toString())
}
/*To handle cases where the target API < 21*/
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
return (activity as MainActivity).klarnaHybridSDK.shouldFollowNavigation(url.toString())
}
}
val url = "myAppUrl.php"
val postData = "data=" + URLEncoder.encode(data, "UTF-8").toString()
myWebView.postUrl(url, postData.toByteArray())
return root
}
...
}
Steps to reproduce
Started the app
Opened chrome://inspect/#devices in Chrome.
Located the relevant web page
Pressed "Inspect"
Expected behavior
Not getting swamped with the message in my console.
Device and version:
Device: Samsung A10
OS version: Android 9
Klarna In-App SDK version: com.klarna.mobile:sdk:2.0.16
Firstly, thanks for reporting this issue. Fortunately, this problem will be fixed with a future release.
To give you more insight, this currently is an issue when the SDK tries to notify Klarna components in a WebView with no content; thus resulting in a page called ‘untitled’ with excessive number of logs.
Temporarily, this can be avoided by adding the WebView to the Hybrid SDK when a page is loaded.
Edit: #Myalakin We would like to inform you that this issue has been fixed by the latest releases of our SDK.
I am a learning android developer trying to make my website into a simple App
While i was able to resolve most of my issue with this lovely community I am stuck at one part where input type file is not working any matter what I try. Please advice how to get input type file to work with kotlin
<input type="file">
my code till now is
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val progressBar = findViewById<ProgressBar>(R.id.MyProgress)
val myWebView: WebView = findViewById(R.id.webview)
myWebView.settings.javaScriptEnabled = true
myWebView.settings.setDomStorageEnabled(true)
myWebView.setWebChromeClient(WebChromeClient())
myWebView.webViewClient = WebViewClient()
myWebView.settings.setAppCacheEnabled(true)
myWebView.settings.setCacheMode(WebSettings.LOAD_DEFAULT)
myWebView.settings.setAllowFileAccess(true);
myWebView.settings.getAllowFileAccess();
myWebView.loadUrl("https://example.com")
myWebView.setWebViewClient(object : WebViewClient() {
override fun onReceivedSslError(
view: WebView,
handler: SslErrorHandler,
error: SslError
) {
handler.proceed()
}
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
progressBar.visibility = View.VISIBLE
}
override fun onPageFinished(view: WebView, url: String) {
progressBar.visibility = View.INVISIBLE
}
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
if (Uri.parse(url).host == "example.com") {
// This is my web site, so do not override; let my WebView load the page
return false
}
// Otherwise, the link is not for a page on my site, so launch another Activity that handles URLs
Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
startActivity(this)
}
return true
}
})
}
}
<input type="file">
I worked on a project that needed file upload via the WebView. This is how we solved it. Hope it gets you in the right direction. Also note that onActivityResult is deprecated by now and could probably replaced by registerForActivityResult. See this post for more details on that.
MainActivity.kt
companion object {
/**
* Request code for onActivityResult for when the user wants to pick an image
* in the WebFragment
*/
const val REQUEST_SELECT_FILE_IN_WEB_FRAGMENT = 328
}
/**
* Used by [WebFragment] to upload photos to
* the WebView. To do this, we need to catch onActivityResult after the user
* selected the photo and pass it onto the WebView using this ValueCallback.
*/
private var uploadMessageReceiver: ValueCallback<Array<Uri>>? = null
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_SELECT_FILE_IN_WEB_FRAGMENT) {
// This request was triggered from a webpage in WebFragment to select/upload a picture.
// tell the uploadMessageReceiver about the selected image data and set it to null again.
if (uploadMessageReceiver == null) return
uploadMessageReceiver?.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data))
uploadMessageReceiver = null
}
}
// implemenation of the interface defined in WebFragment
override fun getMessageReceiver(): ValueCallback<Array<Uri>>? {
return uploadMessageReceiver
}
// implemenation of the interface defined in WebFragment
override fun setMessageReceiver(callback: ValueCallback<Array<Uri>>?) {
uploadMessageReceiver = callback
}
WebFragment.kt
private lateinit var uploadReceiver: UploadMessageReceiver
override fun onAttach(context: Context) {
super.onAttach(context)
when (context) {
is UploadMessageReceiver -> uploadReceiver = context
else -> throw IllegalArgumentException("Attached context must implement UploadMessageReceiver to allow image uploading in this WebView.")
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
with(binding.webView) {
webViewClient = WebViewClient()
webChromeClient = BvbWebChromeClient()
settings.javaScriptEnabled = true
settings.allowFileAccess = true
settings.domStorageEnabled = true
settings.databaseEnabled = true
settings.setAppCacheEnabled(true)
if (savedInstanceState == null) {
loadUrl(arguments.url)
}
}
}
/**
* Let the attached Activity implement this interface to catch the
* onActivityResult after the user selected an image to upload (see [BvbWebChromeClient])
*/
interface UploadMessageReceiver {
fun getMessageReceiver(): ValueCallback<Array<Uri>>?
fun setMessageReceiver(callback: ValueCallback<Array<Uri>>?)
}
/**
* This WebChromeClient is needed to allow the user to select an image that
* he/she wants to upload to a web page.
*/
inner class BvbWebChromeClient : WebChromeClient() {
/**
* Used by the HTML Feedback form to upload an image.
* Works tightly with [MainActivity.uploadMessageReceiver]
*/
override fun onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams): Boolean {
// make sure there is no existing message
val uploadMessage = uploadReceiver.getMessageReceiver()
if (uploadMessage != null) {
uploadMessage.onReceiveValue(null)
uploadReceiver.setMessageReceiver(null)
}
uploadReceiver.setMessageReceiver(filePathCallback)
val intent = fileChooserParams.createIntent()
try {
requireActivity().startActivityForResult(intent, MainActivity.REQUEST_SELECT_FILE_IN_WEB_FRAGMENT)
} catch (e: ActivityNotFoundException) {
uploadReceiver.setMessageReceiver(null)
Toast.makeText(requireActivity(), "Cannot open file chooser", Toast.LENGTH_LONG).show()
return false
}
return true
}
}
I have a WebView in my app. It works properly but at Api 20, WebView does not show the content.
I checked the logs and see these errors
I/chromium: [INFO:CONSOLE(7)] "The key "shrink-to-fit" is not recognized and ignored.", source: https://...
I/chromium: [INFO:CONSOLE(43)] "Uncaught SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode", source: https://... (43)
I overviewed some answers and they generally suggest to use
webView.settings.useWideViewPort = true
webView.settings.loadWithOverviewMode = true
I have already implement these settings but still same error happens. WebView shows noting
Here is my WebView Settings
/**
* Initialize the WebView
*/
#SuppressLint("SetJavaScriptEnabled")
private fun initWebView() {
webView.webViewClient = object : WebViewClient() {
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
return super.shouldOverrideUrlLoading(view, request)
}
override fun onPageStarted(
view: WebView, url: String, favicon: Bitmap?) {
showProgressDialog()
}
override fun onPageFinished(view: WebView, url: String) {
hideProgressDialog()
}
#RequiresApi(api = Build.VERSION_CODES.M)
override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) {
// Your code to do
hideProgressDialog()
}
override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) {
// Handle the error
hideProgressDialog()
}
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler?, error: SslError) {
// ignore ssl error
if (handler != null) {
handler.proceed()
} else {
super.onReceivedSslError(view, null, error)
}
}
}
webView.isHorizontalScrollBarEnabled = false
webView.settings.javaScriptEnabled = true
webView.settings.useWideViewPort = true
webView.setInitialScale(1)
webView.settings.loadWithOverviewMode = true
context?.let { webView.setBackgroundColor(ContextCompat.getColor(it, R.color.colorDarkBlue)) }
}
/**
* Load url
*/
private fun loadPage(url: String) {
webView.loadUrl(url)
}
Someone have a idea? I will be appreciate for any help
I have a back application with a cshtml file that is basically used to get authentified.
Now, in my Android app in Kotlin, I'm using a WebView and a JavascriptInterface to call the URL that I need and to call my method in the back.
There are the useful code lines:
Login activity:
val myWebView: WebView = findViewById(R.id.connexionWebview)
myWebView.setWebViewClient(object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
view.loadUrl(url)
return false
}
})
myWebView.settings.javaScriptEnabled = true
myWebView.addJavascriptInterface(AuthenticatedHandler(this), "AndroidAuthenticatedManager")
myWebView.loadUrl("HiddenURL")
AuthenticatedHandler.kt:
class AuthenticatedHandler(private val mContext: Context): AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
#JavascriptInterface
fun onAuthenticated(token: String, email: String, userId: Int) {
val intent = Intent(this, Home::class.java)
val extras = Bundle()
extras.putString("token", token)
startActivity(intent)
}
}
}`
In my back, Authenticated.cshtml:
AndroidAuthenticatedManager.onAuthenticated('#token.AccessToken', '#email', #userId);
And this is the error that I got:
I/chromium: [INFO:CONSOLE(14)] "Uncaught TypeError: AndroidAuthenticatedManager.onAuthenticated is not a function", source: http://HidenURL:5000/Account/Authenticated (14)
What do you think about it? I tried several little changes and searched about it on internet but nothing fixes it...
Thanks for your reading,
Tanguy.
I think #JavascriptInterface fun onAuthenticated() should be outside of override fun onCreate(), not inside of it.
Solution must work on API >20
This solution does not work. I assume it works only on lower versions of the Android API. I'm testing on Android 8.0.
I've tried using Retrofit as soon as the page is loaded, instead of getting the html via WebViewClient. But the user is not logged in in the Retrofit request. And I'm doing the same request 2 times then.
#SuppressLint("JavascriptInterface")
private fun initializeWebView(url : String?) {
binding.webView.loadUrl(url)
binding.webView.settings.javaScriptEnabled = true
binding.webView.settings.useWideViewPort = true
binding.webView.requestFocus(View.FOCUS_DOWN)
binding.webView.addJavascriptInterface(MyJavaScriptInterface(context!!), "HtmlViewer");
binding.webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
return false
}
override fun onPageFinished(view: WebView?, url: String?) {
// super.onPageFinished(view, url)
//tried adding a sleep, but doesn't work:
Thread.sleep(6000)
//showHTML method is not being called:
binding.webView.loadUrl("javascript:window.HtmlViewer.showHTML" +
"('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');")
// Prints out: html: null
binding.webView.evaluateJavascript(
"javascript:window.HtmlViewer.showHTML" +
"('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');"
) { html ->
Log.d("HTML", html)
// code here
}
}
}
}
class MyJavaScriptInterface(val context: Context) {
fun showHTML(html: String) {
Log.d("",""+html)
AlertDialog.Builder(context).setTitle("HTML").setMessage(html)
.setPositiveButton(android.R.string.ok, null).setCancelable(false).create().show() }
}