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

I use native.showPopup(“mail”, options) in a number of regular, storyboard scenes and it works great. I send my email or cancel and in either case my app comes back to view with the storyboard scene that took us to mail app where I left it. All is good. 

I am now implementing a webView in my app to show some local HTML. Inside the webView’s listener I catch it when user taps on a mailto: link, such as a Contact Us kind of link with an embedded mailto:xyz@abc.com type email address.

In the webView listener I call native.showPopup(“mail”, options) when user clicks that Contact Us link so I can send the email and return back to my webView but unfortunately native.showPopup() behaves differently in this case. When called from a webView it does not return to my app and when the mail is sent it returns to the inbox of the mail app. 

Has anyone else noticed this anomaly and figured out a way around it? Thank you very much for your help.

Turns out for some weird reason, webView:stop() does not work when the link clicked is a mailto: link. It works for all other links that would have opened the link inside the webView itself but mailto: that sends you to the native app at OS level can’t be stopped with webView:stop(). Since apparently I wasn’t able to stop the webView sending me to the native mail app I wasn’t able to even get the native.showPopup(“mail”, options) to show. Managed to solve it using a little hack… Here goes… 

Since I have control over the html help file I simply changed the mailto link to a regular link while keeping the abc@xyz.com as the visible link text. I simply placed “test” as the link URL into my HTML. In my webView listener I listen and check the URLs as they get clicked. I look for “test” inside the URL clicked. If I find it I call webView:stop() and then call native.showPopup(“mail”, options) instead. 

This is now working and I am able to get back to my webView scene after the mail is sent. Hope this helps others who might be stuck with the same thing in time to come. 

Regards,

Kerem

Nice workaround :slight_smile: I was going to suggest something similar but I saw you already solved it.

Thanks. I hope the API can be fixed so that webView:stop() will actually stop the loading of a mailto: link as well. This is the only way to get a consistent UX in the cases where app user goes to third-party web pages where we don’t control the way links are built. 

The webView:stop() function can only stop the loading of web pages.  Tapping a mailto:// link causes the WebView to launch your device’s default mail app (not a popup), which is not a web page.  That’s why the webView:stop() function won’t stop it.  And since the mailto:// is launching your Mail app, that’s why your trapped in that app.

If you want the end-user to remain in your app, then the solution is to set up a Lua listener to trap the URL request for the mailto:// URL scheme and return true to override iOS’ default behavior.  That’s your opportunity to display a mail popup within your own app via the native.showPopup().  This sounds like what you’re doing now, which is the right solution.

Just note that Android’s WebView behavior is completely different.  Android’s native WebViews will not display the mail app by default when tapping a mailto:// URL scheme.  Instead, the WebView will attempt to load the mailto:// as a webpage, which of course will fail and display an error page in the WebView instead.  So, on Android, you have no choice but to override the URL request and implement it yourself.  This is actually how it works at the native level on Android.  Unfortunately, even if you do override the URL request, you’ll still see a failed to load webpage flashed onscreen.  There really isn’t a nice way to handle this on Android, which makes what you’re doing overly difficult.

Hi Joshua, 

Thanks for your input and help. I will try “return true” in my URL trap for “mailto”. I didn’t think of return true to stop the webView from loading the external mail app so I created this “broken link” hack which meant the webView could not load anything and I was able to trap this and trigger the native.showPopup etc. Your idea should make for a more refined solution and would probably get rid of that one blink I’m getting when user taps on the broken link.

So it sounds like this solution is the only way to deal with this issue on Android as well. Will test later today and report back.

Once again thank you very much.

Regards,

Kerem

Hmmm. You were absolutely right. My hack works well on IOS but not on Android. webView on IOS discards the bad URL, blinks and moves on. On Android it brings up a message telling me that my URL is broken. 

So I will improve my listener with the return true in addition to the “broken URL” hack. Fingers crossed.

Thanks once again for your help.

Ok. On Android return true or webView:stop() still does not limit the loading of the error page. Funny thing is how my url is no longer a mailto but still gets through webView:stop(). Problem is that once I return from the mail app the error page is there. I will try to detect when I’m on Android and reload the original help HTML file in the meanwhile. If you have any ideas to make this look a little better or get more straightforward I’m all ears. Thanks much!!!

Joshua, I re-read your post above and you clearly mention return true is the way to override that URL request on IOS. Is there a similar way to override the request on Android? I’m ok with the brief flash of that message as long as I can throw it away and return to the original html. Thanks

Found a way. One hack brings on another one… I found that if I throw a webView:back() into the URL trap and make it conditional on Android I see the flashing error page but I end up back in my caller html. All good. Thanks for your help.

Edit : Throws hands in the air while running around the room… No joy… 

I’m sorry.  I had it backwards.  You need to return “false” from your urlRequest to override the request.  Returning true (or not returning a value at all) tell its to go ahead and load the URL.

   http://docs.coronalabs.com/api/library/native/showWebPopup.html#example

Our sample app “Interface/WebOverlay” that is included with the Corona Simulator shows an example of this.  There is a “corona:close” URL in the HTML which when tapped on will be overridden by the “main.lua” to close the web popup.

Ahh there you go. I know you were testing me and I failed!  :slight_smile:

Thanks much for the follow-up. This will be ok I’m sure. 

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.