For my mobile Unity app, I need a web view element and use the UniWebView plugin. As shown in the code below, I wrote a script to dynamically load a html string. In my app the containing scene will be loaded many times, based on user interaction.
In the editor and on iOS it works as expected. On Android the web view is shown for the first scene load after app start. But later reloads sometimes work and sometimes fail arbitrarily shwoing a white area only. This happens although the loading spinner works, so it just seems to be a visualization failure.
The script is attached below. I put the script on a simple panel gameobject which is only used to define the screen area.
Any idea how I can solve this?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class YouTubePlayer : MonoBehaviour
{
private UniWebView uniWebView;
public void Start()
{
uniWebView = GetComponent<UniWebView>();
if (uniWebView == null)
{
Debug.Log("YOUTUBE PLAYER: Adding uniwebview Component");
uniWebView = gameObject.AddComponent<UniWebView>();
}
uniWebView.OnPageStarted += (view, url) =>
{
Debug.Log("YOUTUBE PLAYER: OnPageStarted with url: " + url);
};
uniWebView.OnPageFinished += (view, statusCode, url) =>
{
Debug.Log("YOUTUBE PLAYER: OnPageFinished with status code: " + statusCode);
};
uniWebView.OnPageErrorReceived += (UniWebView webView, int errorCode, string errorMessage) =>
{
Debug.Log("YOUTUBE PLAYER: OnPageErrorReceived errCode: " + errorCode
+ "\n\terrMessage: " + errorMessage);
};
uniWebView.SetShowSpinnerWhileLoading(true);
uniWebView.ReferenceRectTransform = gameObject.GetComponent<RectTransform>();
uniWebView.LoadHTMLString(YoutubeHTMLString, "https://www.youtube.com/");
uniWebView.Show(true);
}
private static string YoutubeHTMLString =
#"<html>
<head></head>
<body style=""margin:0\"">
<iframe width = ""100%"" height=""100%""
src=""https://www.youtube.com/embed/Ccj_H__4KGQ"" frameborder=""0""
allow=""autoplay; encrypted-media"" allowfullscreen>
</iframe>
</body>
</html>";
}
I found out that you have to call Show() before LoadHTMLString() on the UniWebView component. I show the corrected last lines of the Start() method below.
...
uniWebView.SetShowSpinnerWhileLoading(true);
uniWebView.ReferenceRectTransform = gameObject.GetComponent<RectTransform>();
uniWebView.Show(true);
uniWebView.LoadHTMLString(YoutubeHTMLString, "https://www.youtube.com/");
}
I can not tell why this solution works, but I consider it a bug (either in UniWebView, Unity or Android). It may have to do with asynchronous threads in the background that produce sometimes a problematic order on Android (I used an old Nexus 5 with Android 6.0.1).
Nevertheless, I hope this helps somebody, as I had two bad days of try and error ... ;-)
Related
UPDATE: it's a confirmed bug. Please upvote it here because it doesn't really receive a lot of attention from MS.
I need to override the shouldInterceptRequest method of WebViewClient to load in-app HTML content following that guide.
Here's the repo with the reproducible code: GitHub. I took a sample code from MS Q&A as well:
// ...
.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<Microsoft.Maui.Controls.WebView, ProblemHandler2>();
});
// ...
internal class ProblemHandler2 : WebViewHandler
{
protected override Android.Webkit.WebView CreatePlatformView()
{
var wv = new Android.Webkit.WebView(Android.App.Application.Context);
wv.SetWebViewClient(new CustomWebClient());
return wv;
}
}
In the repo, I included 2 custom handlers:
ProblemHandler2 is the exact snippet by the MSFT. I realized a problem: Setting MAUI WebView's Source property no longer navigates the real Android WebView:
WebViewHandler.Mapper.AppendToMapping("MyHandler", (handler, view) =>
{
#if ANDROID
var xWv = handler.PlatformView;
// For ProblemHandler2, this is needed to actually navigate:
xWv.LoadUrl("https://www.google.com/");
#endif
});
this.wv.Source = "https://www.google.com/";
ProblemHandler1 uses the default result and adds a custom handler. This fixes the navigation problem, but, both problem have the same issue:
ShouldInterceptRequest is never called. It is never called on anything even when I manually click a link to navigate. What am I missing? I am sure the CustomWebClient is correctly created and set.
I noticed none of the other callbacks works as well, for example:
public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
{
Debugger.Break();
Debug.WriteLine(url);
base.OnPageStarted(view, url, favicon);
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
Debugger.Break();
Debug.WriteLine(url);
base.OnPageFinished(view, url);
}
I also tried using WebViewHandler.Mapping but it also does not work:
WebViewHandler.Mapper.AppendToMapping("MyHandler", (handler, _) =>
{
#if ANDROID
handler.PlatformView.SetWebViewClient(new CustomWebClient());
#endif
});
I could be wrong but, I think this might have to do with your overridden version of the CreatePlatform method,
Can you try what the default WebViewHandler is doing:
protected override WebView CreatePlatformView()
{
var platformView = new MauiWebView(this, Context!)
{
LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
};
platformView.Settings.JavaScriptEnabled = true;
platformView.Settings.DomStorageEnabled = true;
platformView.Settings.SetSupportMultipleWindows(true);
return platformView;
}
Check this URL for the default handlers CreatePlatform setup :
https://github.com/dotnet/maui/blob/c6250a20d73e1992b4a02e6f3c26a1e6cbcbe988/src/Core/src/Handlers/WebView/WebViewHandler.Android.cs
Also don't use Application Context in Handlers, Handlers have their own Context property you can use.
Yes, it is the case as you said.
And I have created a new issue for this problem, you can follow it up here: https://github.com/dotnet/maui/issues/11004.
Thanks for your support and feedback for maui.
Best Regards.
I am using appium and webdriverIO to automate android native app. I need to perform scroll down and i have used
ele.scrollIntoView(true) //this returns not yet implemented error
is there any other way to scroll down?
I don't used java script to scroll down. but I have already given a detail answer with different approach (by some text, element and screen size). Please have a look on it.
How to reach the end of a scroll bar in appium?
Hope it will help.
I have find a way to perform swipe down by calling JsonWireProtocol api directly from my code.
https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
here is my code
module.exports.scrollAndroid = function (ele) {
console.log('driver.sessionID = ', driver.sessionId);
while (!$(ele).isDisplayed()) { . //$(ele)=$(//xpath or any other attribute)
this.scrollAPICall();
driver.pause(3000);
}
};
module.exports.scrollAPICall = function () {
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
console.log("result status >> ", this.status);
console.log("result text >> ", this.responseText);
};
url1 = `http://127.0.0.1:4723/wd/hub/session/${driver.sessionId}/touch/flick`;
xhttp.open('POST', url1);
console.log("URL >> ", url1)
xhttp.setRequestHeader("Content-Type", "application/json");
xhttp.setRequestHeader("charset", "UTF-8");
xhttp.send(JSON.stringify({
xspeed: 10,
yspeed: -100,
}));
if you want to scroll up then give yspeed as + value. ex:100
You can achieve a scroll down by performing a Screen Swipe Up action. Implementation of swipe class can be found from Boilerplate project. The Gestures.js class has all required functions. Here is the link to class
Please keep in mind that the swipe is performed based on the percentage. Once you implement this Gestures class you can then use it like below:
while(!element.isDisplayed()) {
Gestures.swipeUp(0.5)
}
I'm writing a simple game. I included HTML pages + scrits into the apk file (I put them into assets folder and then loaded into a webview)
browser.loadUrl("file:///android_asset/home.html");
The prototype was only for one player and it was working fine till I needed to add authorization (to have the ability to play with a human:). I created a page for that and did a request:
$.mobile.showPageLoadingMsg();
$.ajax({
url: 'https://www.myhost.com/login',
data: {mail:'admin#mail.com', password: '123'},
datatype: 'html',
type: 'POST',
success:function(html){
$.mobile.hidePageLoadingMsg();
$("#message").html('SUCCESS');
},
error: function(jqXHR, textStatus, errorThrown) {
$("#message").html('FAILED');
$.mobile.hidePageLoadingMsg();
}
})
This call works fine. But... then I needed to add account info to all the game pages so the server can know who is doing what. So...I decided t create a bridge for that:
browser.addJavascriptInterface(bridge, "Bridge");
On the Java side I created a function that stores success login info in the Application class (something like a session).
#JavascriptInterface
public void storeAuthData(String callback, String user, String pass) {
WebGameApplication.setUser(user);
WebGameApplication.setPassword(pass);
webview.loadUrl("javascript:"+callback);
}
And it also works. But how can I add this params if I have POST requests? I googled a lot.
For GET requests it's possible to do it here:
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
Log.i(TAG, "shouldInterceptRequest: " + url);
return super.shouldInterceptRequest(view, url);
}
But for POST I'm stuck. Maybe the solution I picked is incorrect? Of course I can extend the bridge by adding one extra method to retrive account info and then include this into each call whether it GET or POST.... but Really? There's no way of doing that?
Thanks in advance.
I'm afraid that POST requests are not passed through shouldInterceptRequest, only GET.
Could you set a cookie with the auth data rather than (or in addition to) using a JavaScript Bridge?
FYI, you should update your storeAuthData function such that it posts a task to the WebView, something like this:
#JavascriptInterface
public void storeAuthData(String callback, String user, String pass) {
...
webview.post(new Runnable() {
#Override
public void run() {
webview.loadUrl("javascript:"+callback);
}
});
}
The reason for this is important - WebView methods must be run on your applications UI thread, and JavaScript bridge callbacks are executed on a native background thread.
So, my overall situation is as follows: I have to get some HTML from a wordpress database and render it on a webview.
So I cant work with youtube API , I must render this HTML, ill have embeded videos on it and I need them to pop up on fullscreen. This solution Videos not playing in fullscreen mode wont work on API 10.
Is there anyway to make the embeded videos play on fullscreen below API 11?
You can create Frame layout above your WebView and attach to it VideoView. This way you can manage size, add custom MediaControls but there are a lot of work and you will faced with many problems.
Edit: You can capture event by:
if you can handle html, you can add JS method, that calls your application method throught the JS bridge. As example you put an image with onclick event. Click by this image calls JsVideFrameCallbackImpl.playVideo('videoUrl') in your app (http://android-developers.blogspot.com/2008/09/using-webviews.html):
<img src="img/video_1.jpg" width="494px" height="340px" onclick="javascript: JsVideFrameCallbackImpl.playVideo('videoUrl')">
and attach addJavascriptInterface in your activity to webview:
wv.addJavascriptInterface(new JsVideFrameCallbackImpl(), "JsVideFrameCallbackImpl");
//....
private final class JsVideFrameCallbackImpl {
private final static String LOG_TAG = "JsVideFrameCallbackImpl";
#SuppressWarnings("unused")
#JavascriptInterface
public void playVideo(final String videoUri) {
// call VideoPlayer activity as example
startActivity(new Intent(context, VideoPlayer.class));
}
}
or you can get the coords of any html element on the page via the same js-bridge and place VideoView on FrameLayout (Do not forget enable js in webview wv.getSettings().setJavaScriptEnabled(true); before using javascript in webview):
wv.loadUrl("javascript: jsInitMedia()"); // call js method jsInitMedia
jsInitMedia in the html (or you can apply this code directly in WebView as: wv.loadUrl("javascript: var el = document.getElemen..."):
function jsInitMedia() {
var el = document.getElementById(dataArray[i]); var coords = el.getBoundingClientRect();
// get top, left coords, width and height with coords.width, el.offsetTop and so on. Send them back as string (i use json format)
JsVideFrameCallbackImpl.init(jsonDataWithCoordsAsString);
}
and in your java code in JsVideFrameCallbackImpl :
#JavascriptInterface
public void init(final String json) {
Log.d(LOG_TAG, "Init:" + json);
// parse json and extract left, top, width and height
// now you can place any View with corresponding coords on your frame
//as well you can easy handle all click events
}
I'm working on making a browser as a hybrid app using worklight framework for Android. I implemented my address bar as an input element which received the user input and pass the arguments to the webview to load the page.
However, I cannot figure out how to do the reverse: whenever the user click on a link in webview, I want the address bar to change to the new location.
Are you implementing a native page that is opened? If so, take a look at ChildBrowser, that basically does the same thing. It has a TextView being used as an address bar. You may decide to use it, or get the bits and pieces you want out of it. Regardless, I would image what you want to do something like this. By overriding the onLoadResource in the WebViewClient, you should be able to grab the url and change your TextBox.
In response to the comment below: inside your environment's main js file in the wlEnvInit() function:
function wlEnvInit(){
wlCommonInit();
// Environment initialization code goes here
document.onclick=manageLinks;
}
Then in this function get the url and set the text of your input element:
function manageLinks(event) {
var link = event.target;
//go up the family tree until we find the A tag
while (link && link.tagName != 'A') {
link = link.parentNode;
}
if (link) {
var url = link.href;
console.log("url = " + url);
//You can decide if you want to separate external or
//internal links, depending on your application
var linkIsExternal = ((url.indexOf('http://') == 0) || (url.indexOf('https://') == 0));
if (linkIsExternal) {
myInput.setText(url);
return false;
}
}
return true;
}
Inside of your WebView, inside the plugin, intercept the URL like this:
webview.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
//use this area to set your input. Depending on how you
//implemented your plugin, you may need to return this value
//back to your main activity
Toast.makeText(cordova.getActivity(), "Loading: " + url, Toast.LENGTH_LONG).show();
}
});
Have you try to get the url from the href of and assign to the input variable and do the get/post? I know that it is possible in SDK i figure it dont will be harder in a framework. You can store the hiperlinks in a array with a parser or something similar.
example pseudocode:
When_hiperlink_clicked: //could be like a listener (search about it)
url = hiperlink.getURL("myHiperlink");
myinput.setText(url);
execute_input_bar_action();
Is difficult to figure out without code or something more, sorry.