onPageStarted/onPageFinished not updating view and crash - android

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"
}
}

Related

How to fix website breaking the Android Webview?

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.

How to create custom event with the shortest way?

There is CustomWebViewClient with override function onPageFinished. What is the shortest way to notify MainViewModel about the function triggered? I mean some event.
I suppose that can use StateFlow, something like this:
class MainViewModel : ViewModel() {
init {
val client = CustomWebViewClient()
viewModelScope.launch {
client.onPageFinished.collect {
// ...
}
}
}
}
class CustomWebViewClient() : WebViewClient() {
private val _onPageFinished = MutableStateFlow("")
val onPageFinished = _onPageFinished.asStateFlow()
override fun onPageFinished(view: WebView, url: String) {
_onPageFinished.update { "" }
}
}
But in this case need to transfer unnecessary empty string and will be occurs first call before onPageFinished called because MutableStateFlow has value. So appear required add some enum or class in order to do filter with when keyword.
Maybe is there more shortest way to do that?
You can add lambda parameter into CustomWebViewClient constructor that will get called once page is finished.
class MainViewModel : ViewModel() {
init {
val client = CustomWebViewClient({handle the event})
}
}
class CustomWebViewClient(onPageFinished: () -> Unit) : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
onPageFinished()
}
}
Please note that referencing anything from android.* package in a ViewModel is most often a big no-go.
If you want to use the MutablStateFlow approach, another option is to also override onPageStarted as well and do something like
class CustomWebViewClient(): WebViewClient() {
private val _onPageFinished = MutableStateFlow(false)
val onPageFinished = _onPageFinished.asStateFlow()
override fun onPageStarted(
view: WebView?,
url: String?,
favicon: Bitmap?,
) {
_onPageFinished.update { false }
}
override fun onPageFinished(view: WebView?, url: String?) {
_onPageFinished.update { true }
}
}
class MainViewModel : ViewModel() {
init {
val client = CustomWebViewClient()
viewModelScope.launch {
client.onPageFinished.collect {
if (it) {
// Do stuff when page is loaded
} else {
// Do stuff when page starts loading
}
}
}
}
}
Though ultimately using flows for this is kinda overkill and using the lambda approach suggested by Mieszko Koźma is probably more straight forward.

How to check if variable value is Changed in other thread in Kotlin?

I am trying to change my Variable inside my WebViewClient but my Function don't wait for page to load. I think It is because WebView has its own thread. If that is the case then how can I pause my function to wait for page. or is there any other method to keep the value in my Function?
private fun getNewUrl(url: String): String {
var newUrl = "null"
val webView = WebView(applicationContext)
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
Toast.makeText(applicationContext, "Page Loaded", Toast.LENGTH_SHORT).show()
newUrl = url.toString()
}
}
webView.loadUrl(url)
return newUrl
}
Yes. Because that call happens Asynchronously. It doesn't block the execution of following lines.
So you need to have your listener class called when that result is loaded & use that result.
For example:
Create an interface:
interface OnPageLoadedListener {
fun onLoaded(url: String)
}
Pass it as an argument to your function:
private fun getNewUrl(url: String, onPageLoadedListener: OnPageLoadedListener) {
var newUrl: String
val webView = WebView(applicationContext)
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
Toast.makeText(applicationContext, "Page Loaded", Toast.LENGTH_SHORT).show()
pageUrl = url.toString()
newUrl = url.toString()
onPageLoadedListener.onLoaded(newUrl)
}
}
webView.loadUrl(url)
}
Call your function:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getNewUrl("https://www.google.com/", object : OnPageLoadedListener {
override fun onLoaded(url: String) {
Toast.makeText(applicationContext, url, Toast.LENGTH_SHORT).show()
}
})
}

Android : Very large html(>25k lines) to pdf is not generated

I have an html that I want to be converted to a pdf.
The html is thousands of lines. I have observed that for less than 25k(more or less) lines of html code the generation to pdf is happening in about 2-3 seconds. When the html passes the above threshold then the html is never generated and the programm runs forever(I waited for 10 mins). Right now excuse me that I can't provide to you the exact number of lines of the above threshold because the html is produced randomly. I checked that the html is correct and I paste it to an html viewer and it worked.
At first for the generation of the pdf I used the classic one pdfConverter : https://github.com/blink22/react-native-html-to-pdf/blob/master/android/src/main/java/android/print/PdfConverter.java .
I modified the code in order to see what happened and I implement all the functions of the WebviewClient. Here is my modified code :
class PdfConverter private constructor() : Runnable {
private var mContext: Context? = null
private var mHtmlString: String? = null
private var mPdfFile: File? = null
private var mPdfPrintAttrs: PrintAttributes? = null
private var mIsCurrentlyConverting = false
private var mWebView: WebView? = null
var pdfcreator_observer :PdfCreator? = null
override fun run() {
mWebView = WebView(mContext as Context)
mWebView!!.webViewClient = object : WebViewClient() {
override fun onReceivedError (view: WebView,
request: WebResourceRequest,
error: WebResourceError
){
Log.d("michav/1","michav/onReceivedError")
}
override fun onReceivedHttpError (view: WebView,
request: WebResourceRequest,
errorResponse: WebResourceResponse
){
Log.d("michav/1","michav/onReceivedHttpError")
}
override fun onReceivedSslError(view: WebView,
handler: SslErrorHandler,
error: SslError
){
Log.d("michav/1","michav/onReceivedSslError")
}
override fun onRenderProcessGone(view: WebView, detail:RenderProcessGoneDetail):Boolean{
Log.d("michav/1", "michav/onRenderProcessGone")
return true
}
override fun doUpdateVisitedHistory( view: WebView, url:String, isReload:Boolean){
Log.d("michav/1", "michav/doUpdateVisitedHistory")
}
override fun onFormResubmission(view:WebView, dontResend:Message , resend:Message ){
Log.d("michav/1", "michav/onFormResubmission")
}
override fun onLoadResource(view:WebView, url:String){
Log.d("michav/1", "michav/onLoadResource")
}
override fun onPageCommitVisible(view:WebView, url:String){
Log.d("michav/1", "michav/onPageCommitVisible")
}
override fun onPageStarted(view:WebView, url:String, favicon:Bitmap ){
Log.d("michav/1", "michav/onPageStarted")
}
override fun onReceivedClientCertRequest(view:WebView, request:ClientCertRequest){
Log.d("michav/1", "michav/onReceivedClientCertRequest")
}
override fun onReceivedHttpAuthRequest(view:WebView, handler:HttpAuthHandler, host:String, realm:String){
Log.d("michav/1", "michav/onReceivedHttpAuthRequest")
}
override fun onReceivedLoginRequest(view:WebView, realm:String, account:String, args:String){
Log.d("michav/1", "michav/onReceivedLoginRequest")
}
override fun onSafeBrowsingHit(view:WebView, request:WebResourceRequest, threatType:Int, callback:SafeBrowsingResponse){
Log.d("michav/1", "michav/onSafeBrowsingHit")
}
override fun onScaleChanged(view:WebView, oldScale:Float, newScale:Float){
Log.d("michav/1", "michav/onScaleChanged")
}
override fun onTooManyRedirects(view:WebView, cancelMsg:Message, continueMsg:Message){
Log.d("michav/1", "michav/onTooManyRedirects")
}
override fun onUnhandledKeyEvent(view:WebView, event:KeyEvent){
Log.d("michav/1", "michav/onUnhandledKeyEvent")
}
override fun shouldInterceptRequest(view:WebView, request:WebResourceRequest):WebResourceResponse{
Log.d("michav/1", "michav/shouldInterceptRequest")
return WebResourceResponse("","",(1) as InputStream)
}
override fun shouldInterceptRequest(view:WebView, url:String):WebResourceResponse{
Log.d("michav/1", "michav/shouldInterceptRequest")
return WebResourceResponse("","",(1) as InputStream)
}
override fun shouldOverrideKeyEvent(view:WebView, event:KeyEvent):Boolean{
Log.d("michav/1", "michav/shouldOverrideKeyEvent")
return true
}
override fun shouldOverrideUrlLoading(view:WebView, request:WebResourceRequest):Boolean{
Log.d("michav/1", "michav/shouldOverrideUrlLoading")
return true
}
override fun shouldOverrideUrlLoading(view:WebView, url:String):Boolean{
Log.d("michav/1", "michav/shouldOverrideUrlLoading")
return true
}
override fun onPageFinished(view: WebView, url: String) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) throw RuntimeException(
"call requires API level 19"
) else {
val documentAdapter =
mWebView!!.createPrintDocumentAdapter()
documentAdapter.onLayout(
null,
pdfPrintAttrs,
null,
object : LayoutResultCallback() {},
null
)
documentAdapter.onWrite(
arrayOf(PageRange.ALL_PAGES),
outputFileDescriptor,
null,
object : WriteResultCallback() {
override fun onWriteFinished(pages: Array<PageRange>) {
destroy()
pdfcreator_observer?.update_from_pdfconverter()
}
})
}
}
}
mWebView!!.loadData(mHtmlString, "text/HTML", "UTF-8")
}
var pdfPrintAttrs: PrintAttributes?
get() = if (mPdfPrintAttrs != null) mPdfPrintAttrs else defaultPrintAttrs
set(printAttrs) {
mPdfPrintAttrs = printAttrs
}
fun convert(
context: Context?,
htmlString: String?,
file: File?
) {
requireNotNull(context) { "context can't be null" }
requireNotNull(htmlString) { "htmlString can't be null" }
requireNotNull(file) { "file can't be null" }
if (mIsCurrentlyConverting) return
mContext = context
mHtmlString = htmlString
mPdfFile = file
mIsCurrentlyConverting = true
runOnUiThread(this)
}
private val outputFileDescriptor: ParcelFileDescriptor?
private get() {
try {
mPdfFile!!.createNewFile()
return ParcelFileDescriptor.open(
mPdfFile,
ParcelFileDescriptor.MODE_TRUNCATE or ParcelFileDescriptor.MODE_READ_WRITE
)
} catch (e: Exception) {
Log.d(TAG, "Failed to open ParcelFileDescriptor", e)
}
return null
}
private val defaultPrintAttrs: PrintAttributes?
private get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) null else PrintAttributes.Builder()
.setMediaSize(PrintAttributes.MediaSize.NA_GOVT_LETTER)
.setResolution(Resolution("RESOLUTION_ID", "RESOLUTION_ID", 600, 600))
.setMinMargins(PrintAttributes.Margins.NO_MARGINS)
.build()
private fun runOnUiThread(runnable: Runnable) {
val handler = Handler(mContext!!.mainLooper)
handler.post(this)
}
private fun destroy() {
mContext = null
mHtmlString = null
mPdfFile = null
mPdfPrintAttrs = null
mIsCurrentlyConverting = false
mWebView = null
}
companion object {
private const val TAG = "PdfConverter"
private var sInstance: PdfConverter? = null
#get:Synchronized
val instance: PdfConverter?
get() {
if (sInstance == null) sInstance =
PdfConverter()
return sInstance
}
}
}
I call the above code with the following code
fun createPdfFromHtml(htmlstring: String) {
val directory = File(directory_whole_path)
if (!directory.exists()) {
directory.mkdir()
Toast.makeText(
m_context,
"The directory $directory_whole_path created",
Toast.LENGTH_SHORT
).show()
}
var converter: PdfConverter? = PdfConverter.instance
val file = File(
directory_whole_path,
nameofpdf
)
converter?.pdfcreator_observer = this
converter?.convert(m_context, htmlstring, file)
mFilepdf = file
}
None of the debugging logs in pdfConverter is called. I try to know what is happening and when I have the large html the code runs forever. Do you suggest to me to change the html conversion procedure ?
EDIT
I changed the code as the first answer :
val directory = File(directory_whole_path)
if (!directory.exists()) {
directory.mkdir()
Toast.makeText(
m_context,
"The directory $directory_whole_path created",
Toast.LENGTH_SHORT
).show()
}
var this_to_pass =this
GlobalScope.launch(Dispatchers.Default ){
var converter: PdfConverter? = PdfConverter.instance
val file = File(
directory_whole_path,
nameofpdf
)
converter?.pdfcreator_observer = this_to_pass
converter?.convert(m_context, htmlstring, file)
mFilepdf = file
}
But also nothing worked ... still the problem occurs ... when the pdf is a little bit larger than 25k lines of html code the programm runs forever. For this attempt I also put in comment all the webviewclient overrides except the on pagefinished.
Also in the "run" of Android Studio the below is displayed :
W/chromium: [WARNING:navigation_controller_impl.cc(2579)] Refusing to load URL as it exceeds 2097152 characters.
Your conversion code seems to be running on the UI thread, and that's a problem. Please make sure you move the work to a background thread. There are a ton of possibilities for that: kotlin coroutines, IntentService, HandlerThread, AsyncTask, etc etc
Since your converter is a runnable, the easiest way would be to create a HandlerThread, and instead of calling your converter from the main thread
val handler = Handler(mContext!!.mainLooper)
handler.post(this)
call it from your newly created handlerThread
val handler = Handler(handlerThread.looper)
handler.post(this)
Also, be sure to call handlerThread.quit() in your activity/fragment's onDestroy!
I had to change the below line in the PdfConverter :
mWebView!!.loadData(mHtmlString, "text/HTML", "UTF-8")
to
mWebView!!.loadDataWithBaseURL(
"file:///android_asset/",
mHtmlString,
"text/html",
"UTF-8",
""
)
With this command I can get html-to-pdf-conversion without limit to the characters

No value passed for parameter 'function' in

I'm making a webview in android app and want to implement swipe to refresh gesture. but can't get through due to one error.
swipe.setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener())
what parameter should be pass to this function??
Here is my mainactivity.kt file
var mWebView : WebView? = null
abstract class MainActivity : AppCompatActivity() {
lateinit var swipe:SwipeRefreshLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
swipe = findViewById(R.id.swipe) as SwipeRefreshLayout
**swipe.setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener())**
run {
val onRefresh:Unit
run({ Loadweb() })
}
}
fun Loadweb() {
mWebView = findViewById<View>(R.id.webView) as WebView
mWebView!!.webViewClient = object : WebViewClient () {
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
view?.loadUrl(url)
return true
}
}
val webSettings = mWebView!!.getSettings()
webSettings.setJavaScriptEnabled(true)
webSettings.setUseWideViewPort(true)
mWebView!!.loadUrl("http://allnumber.info/")
}
override fun onBackPressed() {
if (mWebView!!.canGoBack()){
mWebView!!.goBack()
}
else {
super.onBackPressed()
}
}}
This is the error message
No value passed for parameter 'function'
Try this code
private lateinit var mHandler: Handler
private lateinit var mRunnable:Runnable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize the handler instance
mHandler = Handler()
swipe = findViewById(R.id.swipe) as SwipeRefreshLayout
swipe.setOnRefreshListener{
mRunnable = Runnable {
Loadweb()
swipe_refresh_layout.isRefreshing = false
}
// Execute the task after specified time
mHandler.postDelayed(
mRunnable,
(randomInRange(1,5)*1000).toLong() // Delay 1 to 5 seconds
)
}
}
}
i think you should pass
(object:SwipeRefreshLayout.OnRefreshListener(){
//override methods
})

Categories

Resources