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
}
}
Related
I've came across a weird issue with the Android Webview. Loading the https://www.jiji.ng site breaks all webviews in the application. When this site is loaded, no other site can be loaded in any webviews in the application again. The progress just stops and nothing happens. Removing the old Webview and creating a new one doesn't work either. The webview only starts working after killing and opening the application again. It only happens when the Webview has JS and dom storage enabled.
Here's the code of the activity in a sample application I've created to demonstrate this issue:
class MainActivity : AppCompatActivity() {
private val editText by lazy { findViewById<EditText>(R.id.input) }
private val webViewContainer by lazy { findViewById<FrameLayout>(R.id.webviewContainer) }
private val recreateWebviewButton by lazy { findViewById<Button>(R.id.recreateWebview)}
private val progress by lazy { findViewById<TextView>(R.id.progress) }
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
WebView.setWebContentsDebuggingEnabled(true)
webView = createWebview()
webViewContainer.addView(webView)
initEditText()
initRecreateWebviewButton()
}
private fun initRecreateWebviewButton() {
recreateWebviewButton.setOnClickListener {
val oldUrl = webView.url
webViewContainer.removeAllViews()
this#MainActivity.webView = createWebview()
webViewContainer.addView(webView)
webView.loadUrl(oldUrl ?: "about:blank")
}
}
private fun initEditText() {
editText.setOnEditorActionListener { _, i, _ ->
if (i == EditorInfo.IME_ACTION_GO) {
webView.loadUrl(editText.text.toString())
true
} else {
false
}
}
}
#SuppressLint("SetJavaScriptEnabled")
private fun createWebview(): WebView {
val webView = WebView(this).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
}
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
}
webView.webViewClient = WebViewClient()
webView.webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
super.onProgressChanged(view, newProgress)
onProgressChange(newProgress)
}
}
return webView
}
private fun onProgressChange(newProgress: Int) {
if (newProgress < MAX_PROGRESS) {
progress.isVisible = true
progress.text = newProgress.toString()
} else {
progress.isVisible = false
}
}
private companion object {
private const val MAX_PROGRESS = 100
}
}
The full application can be found here: https://github.com/kubak89/WebviewIssueExample
I suspect the issue is caused by some permanent internal state of the Webview engine being altered by the JS code, but I'd like to know if there's a workaround for this that could be implemented in Kotlin.
I have an activity where I setup an animated AnimatedVector on an ImageView then I am loading an url in a WebView, everythings good right here.
The issue is in onPageStarted webview client callback, I got a crash because binding.loader.drawable return null so the cast is impossible.
I can't figure it out why the drawable is null here !
Second issue is (if i comment the line in onPageStarted) in onPageFinished, the two visibility of my views I try to set does nothing at all, they are still visibles.
Spoiler : Of course the app crash right after when trying to get the drawable and cast it
Have you already face this issue ?
class ViewRecipeActivity : AppCompatActivity() {
private val binding by viewBinding(ActivityViewRecipeBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setupUI()
}
//region setup UI
private fun setupUI() {
setUpLoader()
setupWebView()
}
private fun setUpLoader() {
with(binding.loader) {
val drawable = AnimatedVectorDrawableCompat.create(this#ViewRecipeActivity, R.drawable.animated_loader)
setImageDrawable(drawable)
}
}
private fun setupWebView() {
val client = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
(binding.loader.drawable as Animatable).start() //Crash here because drawable is null
}
override fun onPageFinished(view: WebView?, url: String?) {
binding.loader.visibility = View.GONE
binding.loaderBackground.visibility = View.GONE
(binding.loader.drawable as Animatable).stop()
}
}
with(binding.recipeView) {
webViewClient = client
}
val recipeUrl = intent.extras?.getString(RECIPE_URL_EXTRA)
if(recipeUrl == null) {
Toast.makeText(this, "Something went wrong", Toast.LENGTH_SHORT).show()
} else {
binding.recipeView.loadUrl(recipeUrl)
}
}
//endregion
companion object {
const val RECIPE_URL_EXTRA = "recipe_url_extra"
}
}
I have a WebView I use for some simple web scraping, which works fine but when I put it in a function and try to return whatever it scraped the return part is called before the WebView even scraped. How can I make it wait before calling return, since a return inside onPageFinished isn't possible?
fun loadText() : String {
myWebView2 = findViewById(R.id.webView2)
var stringToBeReturned = ""
myWebView2.settings.javaScriptEnabled = true
myWebView2.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(myWebView2, url)
myWebView2.evaluateJavascript("(function() { return ('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>'); })();") { value ->
stringToBeReturned = value.substringAfter("on \\u003C/span>\\u003Ca href=\\\"").substringBefore('\\')
}
}
}
myWebView2.loadUrl("https://www.examplewebsite.com")
return stringToBeReturned
}
onPageFinished() is a callback, that gets called asynchronously (whenever the page has been loaded) after your function returns.
You should not have this callback in a function. Instead do the configuration in onCreate(), set the Callback and pass the resultString, once its available, wherever you need it:
override fun onCreate(savedInstanceState: Bundle?) {
val webView: WebView = findViewById(R.id.webView2)
webView.settings.javaScriptEnabled = true
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
// this function gets called as soon the page has been loaded
super.onPageFinished(view, url)
webView.evaluateJavascript("(function() { return ('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>'); })();") { value ->
val resultString = value.substringAfter("on \\u003C/span>\\u003Ca href=\\\"").substringBefore('\\')
// pass resultString wherever you need it
}
}
}
}
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 have link to Google Play that opens Chrome and automatically redirects to Play app on Android:
https://play.google.com/store/apps/details?id=com.raongames.growcastle
I wanted to redirect directly to Play app by rendering it on hidden WebView:
private fun openUrlForRedirection(linkToOffer: String) {
loading = true
webView.apply {
setWebChromeClient(RedirectWebChromeClient(context))
setWebViewClient(RedirectWebViewClient(::onRedirect, ::onRedirectionError))
getSettings().apply {
javaScriptEnabled = true
javaScriptCanOpenWindowsAutomatically = true
userAgentString = System.getProperty("http.agent")
loadUrl(linkToOffer)
}
}
}
private fun onRedirect(str: String) {
loading = false
if (quest.pkg !in str) {
installApp(quest.pkg)
} else {
openUrl(str)
}
}
private fun onRedirectionError() {
showAndLogError(UnknownError())
}
class RedirectWebChromeClient(val context: Context) : WebChromeClient() {
override fun onJsAlert(webView: WebView, str: String, str2: String, jsResult: JsResult): Boolean {
jsResult.cancel()
return true
}
override fun onJsConfirm(webView: WebView, str: String, str2: String, jsResult: JsResult): Boolean {
jsResult.cancel()
return true
}
override fun onJsPrompt(webView: WebView, str: String, str2: String, str3: String, jsPromptResult: JsPromptResult): Boolean {
jsPromptResult.cancel()
return true
}
}
class RedirectWebViewClient(
private val onRedirect: (String) -> Unit,
private val onError: () -> Unit
) : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
info("Finished")
}
override fun onReceivedError(webView: WebView, i: Int, str: String, str2: String) {
if (i != -10) {
onError()
}
}
#TargetApi(23)
override fun onReceivedError(webView: WebView, webResourceRequest: WebResourceRequest, webResourceError: WebResourceError) {
onReceivedError(webView, webResourceError.errorCode, webResourceError.description.toString(), webResourceRequest.url.toString())
}
#RequiresApi(api = 21)
override fun shouldOverrideUrlLoading(webView: WebView, webResourceRequest: WebResourceRequest): Boolean {
return shouldOverrideUrlLoading(webView, webResourceRequest.url.toString())
}
override fun shouldOverrideUrlLoading(webView: WebView, str: String): Boolean {
onRedirect(str)
return false
}
}
But instead of redirection, I have Google Play webpage with button on bottom to redirect.
Try like this
final String appPackageName = activity.getPackageName(); // getPackageName() from Context or Activity object
try {
//if you have playstore it's will navigate to playstore
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName)));
} catch (android.content.ActivityNotFoundException anfe) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=" + appPackageName)));
}
browser=(WebView)findViewById(R.id.webkit);
browser.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// here do what you need with url
//for example use Intent to start google play
//or simply call view.loadUrl(url); to prevent chrome from opening
return(true);
}
});
shouldOverrideUrlLoading() returns true to indicate that
it is handling the event.
Going via a webview is not recommended, and may even stop working at some point in the future. Going via an intent is definitely the recommended solution, and a much better experience for the user.
If you need a link with tracking, Google Play allows tracking of installs via the Play app using Firebase analytics.