native.showPopup() does not return to my app when...

Joshua, 

This is a little bizarre. I used the return false method you pointed me towards in the CL sample as well as the API page but on Android it seems that return false has absolutely no control over webView. Please note that the API doc and the sample code both use the older native.showWebPopup and I’m trying to use the native.newWebView(). Not sure if this might be a problem but it seems like native.newWebView() is totally ignoring that return false and loading the linked page.

By the way, just for experimentation, I changed my link from a mailto link to a regular URL link to another local html file. Its an almost empty file with a simple Back link in it pointing to my original html file. 

Anyways, the listener is below in case you can spot a mistake. Thanks for your help.

 local function webListener(event) local url = event.url print("showWebPopup callback ", url) local shouldLoad = true if string.find( url, "back.html" ) ~= nil then -- Close the web popup shouldLoad = false else if webView.canGoBack then btnBack.alpha = 1 else btnBack.alpha = 0 end end print(shouldLoad) return shouldLoad end

Printed a lot from the webView listener to the console and observed something interesting. After I return false in one cycle, the webView listener stops responding on Android. Seems like the return false is causing a crash of sorts with no errors. After this point none of the links work because the listener stops responding. 

I also noted the native.webView API page does not have the return true in the example. http://docs.coronalabs.com/api/library/native/newWebView.html

wondering if we’re not supposed to use return true / false with webView. 

As a follow-up, since I’m now using a regular URL and trapping this in my listener, I thought webView:stop should work. It does work %90 of the time (rough estimate) and sometimes it slips. I’m thinking the time it takes for my string.find() to scan the URL and trigger webView:stop() is sometimes too much and the page loading begins just like that since the webView is not waiting for the listener to return true… 

Something is definitely fishy here. Thoughts?

Hmm, seems like the issue was not return true/false but webView:stop()

Once I trap the back.html and keep it from loading using webView:stop() none of the other links work and I don’t get the listener fire anymore. Not sure why or what I might be doing wrong. 

Ok. Built the same code on IOS, webView:stop() nicely stops back.html from loading and next time I tap another link that one loads well. No problems. Same behavior on Mac Simulator. On Android however, webView listener gets hosed the second I use webView:stop(). Seems like a bug to me. What do you think? Thanks for all your help.

Okay… I just had someone here school me on how this really works.

If you are using a WebPopup, then you override the URL request by returning false.

If you are using a WebView, then you can only override the request by calling the stop() function.  The Lua listener does not accept a return value.  The reason the WebView’s Lua listener has a different behavior is because it is also used to provide other events such as a “loaded” event to let you know when the page has finished loading.

Regarding calling stop() on Android still displaying an error page, this is exactly the behavior that I expected and what I was warning you about up above.  What you’re trying to do will only work on iOS.  Unfortunately, I don’t think there is a good solution on Android.  The problem with Google’s WebView is that we can’t reliably catch the URL request until the request has already been sent.  This means that is already too late to stop it.  The best we can do is block the response when you return false for a WebPopup or call stop() on a WebView.  Now, this works fine when the URL is to a real web page, but for an invalid URL to a web page, you’ll always get an error page displayed.  This is an unfortunate quirk with Google’s WebView.  While it’s fair to call it a bug, it’s unfortunately a bug that we’re stuck with and I don’t a reliable resolution to.

Now, let me ask you this.  Are you using the WebView to display a local HTML file as a means of display a nice GUI to the end-user?

That is, you’re WebView is not intended to “navigate” to other web pages on the Internet?  If so, then it sounds like what you need is a new API that allows you to display an HtmlView which does not support any navigation at all.  That is something we can definitely implement reliably on all platforms because we can block all navigation, but still provide feedback of what was tapped on.  I’m just trying to think outside of the box here of what might be a good solution to this problem.  If this is what you’re after, then I can write it up as a feature request.

Hi Joshua, 

Thank you so much for taking the time to learn more about this issue and report back to share your findings. This is matching exactly what I found. I replaced the error inducing badly formed URL call at some point with a call to another local html and I found that webView:stop could not catch it. Then I tried stopping real web sites from loading and I could stop them. This matches exactly with your comments. I understand the issue with Google bug so will try to live within our means.

My use case is an information screen I created for my app. I am using local html & webView combination because I am using formatting, image inserts (logos etc) and links to external reference material. So as nice as your idea sounds I am afraid I need to keep with the webView as it is and find a way to make it work. Thank you very much for thinking outside the box though. 

I think I’ll simply hide a www.google.com call in the link that I want to trap and use webView:stop(). I think that might work reasonably well. Will report back. 

Regards & thanks.

EDIT : Confirmed. This approach is working very well on Android. My link in html looks like this : Please contact us at info@appynerds.com and behind the scenes the link setup is not a mailto but a real URL pointing to a contact us page on my website. I decided to use this instead of Google in case at some point this link still slips through webView:stop() and ends up showing. This way it will still be showing something relevant. Anyways, since its a slow loading normal website I can trap it and stop it loading using webView:stop() and redirect my app flow to native.showPopup(“mail”, options). Once I’m done sending the mail app flow returns back to my local html. All good. 

EDIT2: I realize the email app chosen on Android can have a say in the outcome as well… When I hit the email link which triggers native.showPopup(“mail”, options) my Android device tells me which email app I would like to use. Email (built-in app) and Gmail are the options available to me but I imagine other devices may come with other options (ie Samsung Mail app etc). When I use built in email and send my message I am returned nicely to my webView. When I use Gmail it still returns to my app but to the initial view, as if my app is restarted…

EDIT3: On Android 2.3.x after I return from built in email app the keyboard doesn’t disappear. I’ll have to put some code in there to get rid of it… I think I really dislike Android… 

>> Confirmed. This approach is working very well on Android.

Just curious… does this work well when your Android device has no Internet connection?

>> When I use Gmail it still returns to my app but to the initial view, as if my app is restarted…

It sounds like the Android OS terminated your app while you were in the Gmail app.  There should be some kind of indication of this in the Android log, which you can view via “adb logcat” or “ddms”.  This typically happens if the app in the foreground (in this case, gmail) needs more memory than what is available on the device, forcing the OS to terminate other apps in the background.  By default, Corona releases as much memory as possible when being suspended to avoid this issue, such as releasing all images/textures from memory.  This is especially important for the older cheaper devices which have little RAM available.  I’ve never seen this happen with Gmail on my devices, but perhaps your email has a lot more files in it than mine which is pushing it to require more memory?  I’m not sure.  Any case, you can confirm that this is the case via the Android log.  If it is the case, then unfortunately I don’t think there is anything we can do about it.

>> On Android 2.3.x after I return from built in email app the keyboard doesn’t disappear.

That’s pretty typical on Android.  It’s actually not a big deal because, unlike iOS, end-user have the ability to clear the onscreen keyboard themselves on Android by pressing the Back button or a dismiss button on the virtual keyboard (typically only shown on tablet keyboards).

Hi Joshua, 

Thanks for your follow-up. Quick responses below : 

 

>> Just curious… does this work well when your Android device has no Internet connection?

 

Just tested this scenario. I get the mail app and my message is put in the outbox and then I’m returned to my app where I end up in the Android’s “page could not be found display” for the real internet page I tried to go to and trap in my listener. Thanks for the hint. I will try and see if I can redirect to my initial local html in these cases. 

 

>> When I use Gmail it still returns to my app but to the initial view, as if my app is restarted…

 

>> It sounds like the Android OS terminated your app while you were in the Gmail app.  … This is especially important for the older cheaper devices which have little RAM available.  … in any case, you can confirm that this is the case via the Android log.  If it is the case, then unfortunately I don’t think there is anything we can do about it.

 

Hit the nail right on the head again. I observed this issue on my old Samsung Galaxy S running Android 2.3.5. It is my low end tester device where I look at the worst case scenario. I think this is understandable and I can explain it if anyone complains. Nothing to do.

 

>> That’s pretty typical on Android.  It’s actually not a big deal because,…

 

Yup. No biggie. I just placed a " native.setKeyboardFocus( nil )" in my onApplicationResume listener and we’re all good. Keyboard nicely gets tucked away when I return to my app.

I just took a quick look at our WebView code on Android.  Your Lua listener will receive an error code if it fails to load the URL.

   http://docs.coronalabs.com/api/event/urlRequest/errorCode.html

So, what you should do is check if “event.errorCode” is *not* nil as shown in the link above.  If you did receive an error code, then you can either reload your webpage with your local HTML file… or call the webView:back() function.

I hope this helps.

Hi Joshua, 

Thank you very much for looking into what my options could be. I tried your suggestion and find that I can detect the error but still end in that page. Perhaps its once again the speed at which this thing is going through. My listener triggers are a little interesting though. See below output from my logcat and my commentary (marked as ^^ ). Sorry for the length of this.

I/Corona  (29621): Event type & URL is     other file:///data/user/11/com.appynerds.xxx/app_data/infoFinal.html

^^ above, first time the listener is called for initial load request. Event type is other.

I/Corona  (29621): Event type & URL is     loaded file:///data/user/11/com.appynerds.xxx/app_data/infoFinal.html

^^ above, second time the listener is called for initial loading of my local html. Event type is loaded.

I/Corona  (29621): Event type & URL is     link http://appynerds.com/index.php/contact

^^ above, I tap on the fake link pointing to my contact page. This is what I want to trap. Now the internet is off so this is supposed to return an error. Event type is link.

I/Corona  (29621): Event type & URL is      http://appynerds.com/index.php/contact

^^ above, fourth time the listener is called. event type is blank. URL is the last link tapped now coming back with an error. So far so good.

I/Corona  (29621): error code     -2

^^ further down in my listener I check for error and print the code. Still the same listener call (ie fourth time it is running)

I/Corona  (29621): Event type & URL is     history file:///data/user/11/com.appynerds.xxx/app_data/infoFinal.html

^^ in my error trap I call webView:back. What you see above is the 5th time the listener is called. How the event type is history and url is my original local html. 

I/Corona  (29621): Event type & URL is     loaded http://appynerds.com/index.php/contact

^^ Now this is bizarre… All was good until the 5th listener trigger and I was expecting to land in my original html page with an event type of loaded. I end up with a loaded for my contact page which gave me the error in the first place. Since internet is down I also end up with the Android’s page not loaded message. Yikes. 

So this 6th triggering of the web listener going back to the error page instead of obeying the webView:back is something I need to understand a little more and avoid if possible. 

Any thoughts? 

Yeah, that’s why I was thinking it might be better for you to reload your local HTML when an error is detected.  That may be more reliable than going back a page.  I’m think of the case where the WebView quickly loads more than one error page back-to-back.  That’s what I think is happening.

Yup. This is working better. I can see the error page briefly flash in between but I do end up where I need to. Given the offline is not a common use case for someone wanting to hit my email link I think its a safe bet that I can live with this solution. Thank you so much for your help.

Last question. Is there a way to reset back/forth history in the webView? Reason I’m asking is that, in this case we’ve been discussing, once user hits the email link and comes back to the initial page I still get my “Back” button visible since webView.canGoBack is true at this point. What I need to do is to somehow get webView know that I’m reloading my initial html so therefore there is no Back to go to. In this case if you hit back you end up going to the sendmail link. 

I think I can look at the URL and not make the back button visible if I’m on the initial html so I can deal with this programmatically but just asking if I could get webView.canGoBack reset. 

Thanks!!!

>> Last question. Is there a way to reset back/forth history in the webView?

Unfortunately no.  The only solution I can think of is to destroy and re-create the WebView, which is a bit heavy handed.

I also don’t recommend that you call the webView:back() function multiple times in a loop either because it performs that operation asynchronously, meaning that the canGoBack property won’t update until after the back operation finishes loading the previous page.

That said, I’ve seen a Corona developer destroy and re-create a WebView to reset the navigation history before.  What he did was create his own loading object made up of a white rectangle a text object that was positioned behind the native WebView.  This way it doesn’t look like the WebView quickly disappears and re-appears to the end-user, which would look ugly.  I also think he hid the WebView when loading a webpage on the Internet and then showed it once it finished loading via its Lua listener… which came in handy for websites that take a long time to load.  This was especially useful since the native WebView doesn’t actually show any load progress so that the end-user won’t think something is wrong.

Got it. I will deal with it. I think, rather than destroying and recreating the webView, better option for me is to trap the URL for initial html and when it is loaded simply hide the back button. Normally, the back button is not offered on that page because it happens to be the very first page but only in this unique case, where I have to send the user back to the initial page then I will simply hide the back button. 

This thing is wired to the teeth so I bet it will break in future as Google gets webView issues improved. Till then its as good as it gets. Thanks much for hanging in there with me on this one. I appreciate it. 

Happy to help!  It’s always good to know how some of our features are being used in real world apps.

Indeed! Thank you very much for not simply telling me to “Submit a feature request” and brushing me aside but rather trying to find a way to make this thing work as best as it can. Most appreciated. 

Excellent informative thread guys.  Joshua thanks for digging into the code and getting out the details too!