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.
Related
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
}
}
}
}
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 am implementing Social Login Feature with Instagram.
It seems like I need to open WebView and then get access token.
this is Instagram document
What I am trying is showing dialog which has WebView in it. And then if the user sign in / authorise, the url contains access-token. And I want to get it into my app and keep it.
Here's what I implemented.
SignInFragment.kt
override fun signInInstagram() {
Timber.d("signInInstagram()")
if (activity!!.isFinishing) {
authDialog = AuthenticationDialog(activity!!, this)
authDialog.setCancelable(true)
authDialog.show()
} else {
Timber.d("No Activity")
Toast.makeText(activity, "No Activity", Toast.LENGTH_SHORT).show()
}
}
class AuthenticationDialog(context: Context, var listener: AuthenticationListener) : Dialog(context) {
private var webView: WebView? = null
// private final String url = "https://naver.com";
private val url = "${Constants.INSTAGRAM_URL}oauth/authorize/" +
"?client_id=${Constants.CLIENT_ID}" +
"&redirect_uri=${Constants.REDIRECT_URI}" +
"&response_type=token" +
"&display=touch&scope=public_content"
init {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
Timber.d("start")
}
}
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
this.setContentView(R.layout.dialog_insta_auth)
Timber.d("url:${url}")
initializeWebView()
}
private fun initializeWebView() {
webView = findViewById(R.id.web_view)
webView!!.loadUrl(url)
webView!!.webViewClient = object : WebViewClient() {
var authComplete = false
var access_token: String? = null
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 (url.contains("#access_token=") && !authComplete) {
val uri = Uri.parse(url)
access_token = uri.encodedFragment
Timber.d("access token: ${access_token}")
// get the whole token after the '=' sign
access_token = access_token!!.substring(access_token!!.lastIndexOf("=") + 1)
Log.i("", "CODE : " + access_token!!)
authComplete = true
listener.onCodeReceived(access_token!!)
dismiss()
} else if (url.contains("?error")) {
Toast.makeText(context, "Error Occured", Toast.LENGTH_SHORT).show()
dismiss()
}
}
}
}
}
public class Constants {
companion object {
// Instagram
const val INSTAGRAM_URL = "https://api.instagram.com/"
const val CLIENT_ID = "xxxxxxxxxxxxxxxx"
const val REDIRECT_URI = "https://www.instagram.com/"
}
}
However, the problem is I get ERROR because the context seems incorrect. signInInstagram() is in a SignInFragment. And SignInFragment belongs to MainActivity. So, I thought activity!! must work. But it doesn't. I tried with applicationContext and baseContext but they are not working either.
If I remove activity!!.isFinishing. I get this error:
Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter savedInstanceState
How can I solve 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() }
}