kotlin.UninitializedPropertyAccessException: lateinit property WebView has not been initialized - javascript

I created an android app and loading an html file on android webView. Which is loaded successfully and working fine.
class MainActivity : AppCompatActivity() {
private lateinit var myAndroidWebView: WebView;
#SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setWebviewDetails();
}
private fun setWebviewDetails(){
//if(!::myAndroidWebView.isInitialized){
myAndroidWebView = findViewById(R.id.webView);
//}
myAndroidWebView.settings.javaScriptEnabled = true;
myAndroidWebView.loadUrl("file:///android_asset/App/index.html");
myAndroidWebView.addJavascriptInterface(WebAppInterface(this), "AndroidApp");
}
public fun testMessage(param: String){
println("Interface call-2")
myAndroidWebView.post(Runnable {
val str = "xxxXXXXXXXXXXXXXx $param"
myAndroidWebView.loadUrl("javascript:Application.UserInterface.sample('$str')")
})
println("Interface call-3")
}
}
Now I want to send message to Android app to JS and vice versa.
I have a button in HTML and triggeting the function
public fun showToast(toast: String) {}
From HTML view by using, AndroidApp.showToast("hello");
Which is working fine and I am getting call from JS to Android interface function showToast()
Now on request from the JS I want to get some values from Android and send back to JS as well.
I have an interface, on trigger button from HTML I am getting call on the below interface function.
And trying to call a method in the MainActivity, public fun testMessage(param: String){} is triggered succssfully.
Issue:
I am trying to send data to JS by using,
myAndroidWebView.loadUrl("javascript:Application.UserInterface.sample('$str')")
Here I am getting error.
W/System.err: kotlin.UninitializedPropertyAccessException: lateinit property myAndroidWebView has not been initialized
How do I resolve it.
Thanks.
/** Instantiate the interface and set the context */
class WebAppInterface(private val mContext: Context) {
var mainActivity:MainActivity = MainActivity();
/** Show a toast from the web page */
#JavascriptInterface
public fun showToast(toast: String) {
println("Interface call-1")
mainActivity.testMessage(mContext,toast);
}
}

lateinit property not initialized exception is thrown because you are trying to create an instance of MainActivity in WebInterface. var mainActivity:MainActivity = MainActivity();
It is Android system's job to create and load your activities. You should never try to initiate an activity.
Here, a rough improvement of your code. Try to adapt it to your needs.
interface JsCommunicator {
fun testMessage(param: String)
}
class WebAppInterface(private val communicator: JsCommunicator) {
#JavascriptInterface
fun showToast(toast: String) {
communicator.testMessage(toast)
}
}
class YourMainActivity : JsCommunicator {
// ...
private lateinit var myAndroidWebView: WebView
override fun testMessage(param: String) {
println("Interface call-2")
myAndroidWebView.post(Runnable {
val str = "xxxXXXXXXXXXXXXXx $param"
myAndroidWebView.loadUrl("javascript:Application.UserInterface.sample('$str')")
})
println("Interface call-3")
}
}

You are accessing web-view without initialising it.
call setWebviewDetails() first then testMessage()
or you can make webview nullable like this
private var myAndroidWebView: WebView? = null;
also you need to call startActivity() to create Activity not by
creating objects as this is framework class managed by Android
System.

Related

Blazor listen to javascript event

I have a javascript event called Hello:
addEventListener('hello', function () {
alert("event listener");
})
and, in another javascript function, I raise the event:
let event = new Event("hello", { bubbles: true });
document.dispatchEvent(event);
What I want to do now is let the event trigger in a javascript function.
Blazor should listen to the event, not javascript calling a Blazor method.
Hope anyone can assist me.
Regards me,
For custom events, you will need to manually utilize JavaScript/.NET interoperability.
Using the Instance Method Call method:
Pass the .NET instance by reference to JavaScript:
Make a static call to DotNetObjectReference.Create.
Wrap the instance in a DotNetObjectReference instance and call Create on the DotNetObjectReference instance. Dispose of DotNetObjectReference objects (an example appears later in this section).
Invoke .NET instance methods on the instance using the invokeMethod or invokeMethodAsync functions. The .NET instance can also be passed as an argument when invoking other .NET methods from JavaScript.
Example
Note, this is a very simplified example. You probably want to add a few things; start by IDisposable on your interop classes to avoid memory leaks.
In C#, create a helper class to manage the interop:
public class CustomEventHelper
{
private readonly Func<EventArgs, Task> _callback;
public CustomEventHelper(Func<EventArgs, Task> callback)
{
_callback = callback;
}
[JSInvokable]
public Task OnCustomEvent(EventArgs args) => _callback(args);
}
public class CustomEventInterop : IDisposable
{
private readonly IJSRuntime _jsRuntime;
private DotNetObjectReference<CustomEventHelper> Reference;
public CustomEventInterop(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public ValueTask<string> SetupCustomEventCallback(Func<EventArgs, Task> callback)
{
Reference = DotNetObjectReference.Create(new ScrollEventHelper(callback));
// addCustomEventListener will be a js function we create later
return _jsRuntime.InvokeAsync<string>("addCustomEventListener", Reference);
}
public void Dispose()
{
Reference?.Dispose();
}
}
In a Blazor component, add an instance of the interop class (Interop) and add a local method as a callback (HandleCustomEvent):
private CustomEventInterop Interop { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender) {
if (!firstRender)
{
return;
}
Interop = new(JS);
await Interop.SetupCustomEventCallback(args => HandleCustomEvent(args));
HasRendered = true;
}
private void HandleCustomEvent(EventArgs args) {
// ... handle custom event here
}
In JavaScript, add a method that references the DotNetObjectReference and can call the interop in C#:
function addCustomEventListener(dotNetObjectRef) {
document.addEventListener('hello', (event) => {
// Calls a method by name with the [JSInokable] attribute (above)
dotNetObjectRef.invokeMethodAsync('OnCustomEvent')
});
}
If using TypeScript, you might check out this GitHub Issue.
For anyone wondering the solution proposed by #Mister Magoo is no longer a preview for .NET 6 and is documented here with some exemples.
In a nutshell :
Create a C# class with the EventHandlerAttribute :
[EventHandler("oncityclicked", typeof(CustomSelectionCityEventArgs),
enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}
public class CustomSelectionCityEventArgs: EventArgs
{
public Guid Id { get; set; }
}
Add JS inside ./wwwroot/index.html and after <script src="_framework/blazor.webview.js" autostart="false"></script> :
<script>
Blazor.registerCustomEventType('cityclicked', {
createEventArgs: event => {
return {
id: event.detail
};
}
});
</script>
In you razor :
#code {
private void HandleCityClicked(CustomSelectionCityEventArgs eventArgs)
{
Console.WriteLine("Bouep");
}
}
<div id="CarteLeaflet" #oncityclicked="HandleCityClicked"></div>
And finally in the JS you can dispatch the event :
function OnMarkerClick(pId) {
const event = new CustomEvent('cityclicked', {
bubbles: true,
detail: pId
});
document.getElementById('CarteLeaflet').dispatchEvent(event);
}
Don't make the same mistake as me, the event name in the C# should start with "on" (JS : "cityclicked", C# : "oncityclicked").

Is there a workaround for Script Notify not working on UWP ms-appdata

I am developing an application for Xamarin.UWP which is trying to inject Javascript into a local html file (uri: ms-appdata:///local/index.html) like so:
async void OnWebViewNavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
if (args.IsSuccess)
{
// Inject JS script
if (Control != null && Element != null)
{
foreach (var f in Element.RegisteredFunctions.Where(ff => !ff.IsInjected))
{
await Control.InvokeScriptAsync("eval", new[] { string.Format(JavaScriptFunctionTemplate, f.Name) });
f.Injected();
}
}
}
}
Then when the Javascript method is called this will call the OnWebViewScriptNotify method so that I can proccess the request in my application.
The trouble is this doesnt work for some kind of security reasons:
This was a policy decision we made that we have had feedback on so we
re-evaluate it. The same restriction doesn't apply if you use
NavigateToStreamUri together with a resolver object. Internally that's
what happens with ms-appdata:/// anyway.
I then tried what is advised in this case which was to use a resolver as mentioned here: https://stackoverflow.com/a/18979635/2987066
But this has a massive affect on performance, because it is constantly converting all files to a stream to load in, as well as certain pages loading incorrectly.
I then looked at using the AddWebAllowedObject method like so:
private void Control_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
if (Control != null && Element != null)
{
foreach (var f in Element.RegisteredFunctions)
{
var communicator = new HtmlCommunicator(f);
Control.AddWebAllowedObject("HtmlCommunicator", communicator);
}
}
}
Where HtmlCommunicator is:
[AllowForWeb]
public sealed class HtmlCommunicator
{
public JSFunctionInjection Function { get; set; }
public HtmlCommunicator(JSFunctionInjection function)
{
Function = function;
}
public void Fred()
{
var d = 2;
//Do something with Function
}
}
and in my html it is like so:
try { window.HtmlCommunicator.Fred(); } catch (err) { }
But this doesn't work either.
So is there a way to work around this rediculous limitation?
So I found this answer: C# class attributes not accessible in Javascript
It says:
I believe you need to define the method name starting with a lower
case character.
For example: change GetIPAddress to getIPAddress.
I tested it on my side and found if I use the upper case name
'GetIPAddress', it won't work. But if I use getIPAddress, it works.
So I tried this:
I created a new project of type Windows Runtime Component as suggested here and I changed my method names to lower case so I had:
[AllowForWeb]
public sealed class HtmlCommunicator
{
public HtmlCommunicator()
{
}
public void fred()
{
var d = 2;
//Do something with Function
}
}
In my javascript I then had:
try { window.HtmlCommunicator.fred(); } catch (err) { }
and in my main UWP project I referenced the new Windows Runtime Component library and had the following:
public HtmlCommunicator communicator { get; set; }
private void Control_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
if (Control != null && Element != null)
{
communicator = new HtmlCommunicator();
Control.AddWebAllowedObject("HtmlCommunicator", communicator);
}
}
And this worked!

Javascript calling Android methods in WebView

I have some javascript running in WebView. In this Javascript code there a function which returns a boolean. I want to check the return value from this function and depends on it hide or not a view in my android code. I tried for one day and it does not work. Do someone knows where is my error? This is my code:
public class MyActivity extends Activity {
private static final String JS_INTERFACE = "Android";
....
webView.getSettings().setJavaScriptEnabled(true);
webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
webView.loadUrl(getUrl(this.getResources().getString(R.string.host)));
webView.addJavascriptInterface(new WebViewJavaScriptInterface(this), JS_INTERFACE);
webView.setWebViewClient(new WebViewClient(progressBar, this, tvError));
webView.setWebChromeClient(new WebChromeClient(progressBar));
webView.loadUrl("javascript:window.Android.showAdBanner(showSdkAd())");
}
public class WebViewJavaScriptInterface
{
....
#JavascriptInterface
public void showAdBanner(String jsResult) {
if (jsResult == "true") {
((Activity) context).findViewById(R.id.adView).setVisibility(View.GONE);
} else {
((Activity) context).findViewById(R.id.adView).setVisibility(View.GONE);
}
}
}
You're setting the visibility to View.GONE in both cases of the if (jsResult == "true") if statement.
I think the window in the js is unneeded, so
webView.loadUrl("javascript:window.Android.showAdBanner(showSdkAd())");
Should be
webView.loadUrl("javascript:Android.showAdBanner(showSdkAd())");
Also, the javascript callback will be executed in a background thread, so you need to move to the main thread (posting a runnable to a view, runOnUiThread, using a handler etc), before performing Ui operations.
If you have a reference to a View, you can do:
#JavascriptInterface
public void showAdBanner(String jsResult) {
viewReference.post(new Runnable() {
public void run() {
if (jsResult == "true") {
((Activity) context).findViewById(R.id.adView).setVisibility(View.GONE);
} else {
((Activity) context).findViewById(R.id.adView).setVisibility(View.GONE);
}
}
}
Since, you have a reference to the activity, you can replace viewReference.post with ((Activity) context).runOnUiThread
If you initialise a Handler on the main thread, it will be bound to the main thread. As a field of the Activity, you could have:
private Handler mHandler = new Handler();
And then replace viewReference.post with mHandler.post
You could also make a custom Handler that implements handleMessage(Message msg) and then you can just send it an empty message. However, you should read https://techblog.badoo.com/blog/2014/08/28/android-handler-memory-leaks/ to avoid memory issues.

vaadin. Do smth when client refresh page

I start use vaadin.
I need to run my javascript after reloading page.
How can i do something when client refresh page?
Or how to catch "refresh page" event in vaadin?
Sorry for my English.
Probably overriding paint method can help:
public class HelloWorldApplication extends Application {
#Override
public void init() {
final Window mainWindow = new Window("Hello world app");
SomeComponent hl = new SomeComponent();
hl.addComponent(new Label("Hello world"));
mainWindow.addComponent(hl);
setMainWindow(mainWindow);
}
public class SomeComponent extends HorizontalLayout {
#Override
public void paint(PaintTarget target) throws PaintException {
// TODO Auto-generated method stub
super.paint(target);
getWindow().executeJavaScript("alert('paint invoked')");
}
}

Pass data by hostpage error:JavaScriptObject$ cannot be cast to com.pkg.model

I followed this article to use hostpage to pass an array to client:
https://developers.google.com/web-toolkit/articles/dynamic_host_page
Currently,I can see follow content in firebug
<html style="overflow: hidden;">
<head>
......
<script type="text/javascript">
var rcmdFriends=[{"Name":"Friend-0","Image":"url"}];
</script>
</head>
......
</html>
Then I tried to use these code to get js variable(a json array actually) from hostpage and print it to user:
//get array from host page
private native JsArrayExt<People> getRecommendedFriends()/*-{
return $wnd.rcmdFriends;
}-*/;
#Override
public void onModuleLoad()
{
final FlowPanel fPanel = new FlowPanel();
JsArrayExt<People> channels = getRecommendedFriends();
for (int i = 0, len = channels.length(); i < len; i++)
{
//"print" name to user
fPanel.add(new Label(channels.get(i).getName()));
}
RootPanel.get().add(fPanel);
}
//model definition
#SingleJsoImpl(PeopleImpl.class)
public interface People extends HasName
{
String getImage();
void setImage(String Image);
}
But got this eror:
java.lang.ClassCastException: com.google.gwt.core.client.JavaScriptObject$ cannot be cast to com.pkg.People
Strangely,I can already see the length of "channels" is 1,and why do I get this casting error?How to solove this problem?
You cannot cast to an ordinary Java pojo. You must implement an overlay type
public class PersonJSON extends JavaScriptObject {
protected PersonJSON() {
}
public final native String getName() /*-{
return this.Name;
}-*/;
public final native String getImage() /*-{
return this.Image;
}-*/;
}
Then you can call
JsArray<PersonJSON> channels = getRecommendedFriends();
and read out the values from the PersonJSON elements;
Assuming JsArrayExt is the interface from Why can't I define interface for overlay type lightweight collections?, I suppose that the fact you do not use an explicit JSO subclass confuses the DevMode.
Because you directly call a JSNI method, I don't understand why you don't use a JsArrayExtImpl<PersonImpl> which I believe would Just Work™; there's no point in using the interfaces here.
If you really can't make it work, I'd suggest using AutoBeans instead (it unfortunately requires a small serialize/parse dance in DevMode: AutoBeanCodex.decode(factory, Person.class, new JSONObject(rawJso).toString()), whereas in prod mode you can simply use AutoBeanCodex.decode(factory, Person.class, (JsoSplittable) rawJso)). In your case, it'd require another dance because you're using an array as the root object; see GWT Autobean - how to handle lists?

Categories