How to communicate between native and native web view running a flutter's web app? - flutter-web

Android allows specifying a Javascript interface to act as a bridge between the webview and Android code. Similarly, iOS provides the UiWebView stringByEvaluatingJavaScriptFromString method that allows communicating with the underlying webview.
But the issue is that once we build the flutter web app and say we include it as part of a web view in android , Then whenever communication needs to take place , there is some amount of code that we should write in both android side and javascript side manually.
Since editing javascript code generated from flutter web app build is impractical , we need a way to establish a communication channel on flutter side which will talk to the native side using the same type of javascript bridge above. This will be something similar to the method channels when using the flutter app on the native side directly.
So how would we inject or add our custom javascript code from inside flutter so that it will be added during compiling the js file ?

You can create a webmessage channel on the android side (iOS has an equivalent).
There is a description of how to do it here:
How Do You Use WebMessagePort As An Alternative to addJavascriptInterface()?
A brief setup on the javascript side looks like this - you would need to implement this in dart html:
const channel = new MessageChannel();
var nativeJsPortOne = channel.port1;
var nativeJsPortTwo = channel.port2;
window.addEventListener('message', function(event) {
if (event.data != 'capturePort') {
nativeJsPortOne.postMessage(event.data)
} else if (event.data == 'capturePort') {
/* The following three lines form Android class 'WebViewCallBackDemo' capture the port and assign it to nativeJsPortTwo
var destPort = arrayOf(nativeToJsPorts[1])
nativeToJsPorts[0].setWebMessageCallback(nativeToJs!!)
WebViewCompat.postWebMessage(webView, WebMessageCompat("capturePort", destPort), Uri.EMPTY) */
if (event.ports[0] != null) {
nativeJsPortTwo = event.ports[0]
}
}
}, false);
nativeJsPortOne.addEventListener('message', function(event) {
alert(event.data);
}, false);
nativeJsPortTwo.addEventListener('message', function(event) {
alert(event.data);
}, false);
nativeJsPortOne.start();
nativeJsPortTwo.start();
A similar solution for listening over using an eventlistener on the window in flutter web is below:
window.onMessage.listen((onData) {
print(onData.toString());
MessageEvent messageEvent = onData;
for (Property property in properties) {
if (messageEvent.data["id"] == property.id) {
});
} else if (messageEvent.data["northEastLng"] != null) {
print(messageEvent.data["northEastLat"]);
if (double.parse(messageEvent.data["northEastLat"]) == bounds.northEast.lat && double.parse(messageEvent.data["northEastLng"]) == bounds.northEast.lng){
return;
}
LatLng southWest = new LatLng(double.parse(messageEvent.data["southWestLat"]), double.parse(messageEvent.data["southWestLng"]));
LatLng northEast = new LatLng(double.parse(messageEvent.data["northEastLat"]), double.parse(messageEvent.data["northEastLng"]));
LatLngBounds incomingBounds = new LatLngBounds(southWest, northEast);
if (incomingBounds == bounds) {
return;
}
bounds = incomingBounds;
_propertyListPresenter.filterBounds(bounds);
}
}
});

Related

callkit with pushkit in xamarin

I'm trying to integrate callkit with pushkit in one of my app in xamarin using Twilio voip. I was able to do so by defining required classes and delegates.
I can receive a call when my app is in foreground. but when my app is backgrounded or killed, its not received.
I have this method in my appdelegate:
[Export("pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:")]
[Preserve(Conditional = true)]
public void DidReceiveIncomingPush(PKPushRegistry registry, PKPushPayload payload, string type, Action completion)
{
try
{
Console.WriteLine("My push is coming (Inside Action method!");
var callerid = payload.DictionaryPayload["twi_from"].ToString();
Console.WriteLine($"from: {callerid}");
completion= delegate {
if (payload != null)
{
TwilioService.Setnotification(payload);
}
};
completion.Invoke();
// Tried using only this
// completion(); but it didn't work.
}catch(Exception ex)
{
Console.WriteLine(ex.message);
}
}
So question is how to bring Native dialer when call is arriving and app is in background or killed. I don't understand how to use "Action" parameter of above method.
I see this error in my device logs:
Info (114) / callservicesd: Application <private> will not be launched because it failed to report an incoming call too many times (or repeatedly crashed.)
Thanks.

How to get back to app after google login

I'm trying to implement google login in my app using xamarin.auth like below
var auth = new OAuth2Authenticator("284202576320-7kgdhaa5sgvkoe03jmmcv0p8lfdma306.apps.googleusercontent.com","cAZW7uegD-h2-
tNMMf5q1UGQ","https://www.googleapis.com/auth/userinfo.email",new
Uri("https://accounts.google.com/o/oauth2/auth"),new
Uri("http://dev.myfav.restaurant/Account/LoginComplete"),new
Uri("https://accounts.google.com/o/oauth2/token"),null,true)
{
AllowCancel = true,
};
but Completed event not firing and its going to web page after login :(
I'm getting below error
i need to get back user to my app how can i achieve this ???? Can anyone help me on this please.
Thanks in advance
Hey follow these two examples one is using web view and one is using google sign in sdk for google auth.
https://timothelariviere.com/2017/09/01/authenticate-users-through-google-with-xamarin-auth/
and
https://developer.xamarin.com/samples/xamarin-forms/WebServices/OAuthNativeFlow/
So according to this issue reported by Mounika.Kola .I think u should refer that authenticator.Completed -= OnAuthCompleted is there in ur code. For reference just see these codes which i used for google authorization in Xamarin Forms.
void OnLoginClicked(object sender, EventArgs e)
{
string clientId = null;
string redirectUri = null;
switch (Device.RuntimePlatform)
{
case Device.iOS:
clientId = Constants.iOSClientId;
redirectUri = Constants.iOSRedirectUrl;
break;
case Device.Android:
clientId = Constants.AndroidClientId;
redirectUri = Constants.AndroidRedirectUrl;
break;
}
var authenticator = new OAuth2Authenticator(
clientId,
null,
Constants.Scope,
new Uri(Constants.AuthorizeUrl),
new Uri(redirectUri),
new Uri(Constants.AccessTokenUrl),
null,
true);
authenticator.Completed += OnAuthCompleted;
authenticator.Error += OnAuthError;
AuthenticationState.Authenticator = authenticator;
var presenter = new Xamarin.Auth.Presenters.OAuthLoginPresenter();
presenter.Login(authenticator);
}
async void OnAuthCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
var authenticator = sender as OAuth2Authenticator;
if (authenticator != null)
{
authenticator.Completed -= OnAuthCompleted;
authenticator.Error -= OnAuthError;
}
User user = null;
if (e.IsAuthenticated)
{
// If the user is authenticated, request their basic user data from Google
// UserInfoUrl = https://www.googleapis.com/oauth2/v2/userinfo
var request = new OAuth2Request("GET", new Uri(Constants.UserInfoUrl), null, e.Account);
var response = await request.GetResponseAsync();
if (response != null)
{
// Deserialize the data and store it in the account store
// The users email address will be used to identify data in SimpleDB
string userJson = await response.GetResponseTextAsync();
user = JsonConvert.DeserializeObject<User>(userJson);
}
if (account != null)
{
store.Delete(account, Constants.AppName);
}
await store.SaveAsync(account = e.Account, Constants.AppName);
await DisplayAlert("Email address", user.Email, "OK");
}
}
I hope it helps you.
In iOS once you have completed the authentication with Xamarin.Auth you just need to dismiss the viewController and you will be put back in your app.
You do this subscribing to the Completed event of the OAuth2Authenticator
auth.Completed += (sender, e) =>
{
DismissViewController(true, null);
};
If the "Native UI" is used (the last parameter in the constructor is set to true), which means that external/system browser is used for login not WebView. So, on Android instead of WebView [Chrome] CustomTabs is used and on iOS instead of UIWebView (or WKWebView) SFSafariViewController is used.
With native UI user is leaving your app and the only way to return to your app is app-linking (or deep-linking) and this requires completely different approach.
1st you cannot use http[s] scheme for redirect_url (OK on Android it is possible, but on iOS not). Use custom scheme for that.
See the sample[s] (Xamarin.Forms ComicBook):
https://github.com/moljac/Xamarin.Auth.Samples.NugetReferences
And the docs in the repo:
https://github.com/xamarin/Xamarin.Auth/tree/master/docs

Port Admob to mobile web

I am in the depths of creating a mobile web game that I will soon release. I am interested in monetizing this app. I want the game to feel like its a regular app and therefore wish to have admob ads (with deep links to the app store). I have heard that these also have substantially higher cpm that adsense mobile. My question is this: if I "port" admob using node.js will the clicks and views be recorded as server side, i.e. coming from one place, or mobile, i.e. from the user?
Here are some resources I am thinking of using:
https://media.admob.com/api/v1/docs/
https://github.com/floatinghotpot/cordova-plugin-admob
Any thoughts?
I am the author of the plugin you mentioned: https://github.com/floatinghotpot/cordova-plugin-admob
The basic version has been deprecated, and the pro version is recommended: https://github.com/floatinghotpot/cordova-admob-pro
Instead of porting AdMob to mobile web, you can consider use Cordova to pack your html5 game as a hybrid APP, then publish to Apple AppStore or Google Play Store.
Use the AdMob plugin to present Ad in your app, then Google will pay you.
Only a few javascript lines to present the Ad, and the AdMob SDK will take care of Ad presenting and user click.
See the example code:
function onLoad() {
if(( /(ipad|iphone|ipod|android|windows phone)/i.test(navigator.userAgent) )) {
document.addEventListener('deviceready', initApp, false);
} else {
initApp();
}
}
var admobid = {};
if( /(android)/i.test(navigator.userAgent) ) {
admobid = { // for Android
banner: 'ca-app-pub-6869992474017983/9375997553',
interstitial: 'ca-app-pub-6869992474017983/1657046752'
};
} else if(/(ipod|iphone|ipad)/i.test(navigator.userAgent)) {
admobid = { // for iOS
banner: 'ca-app-pub-6869992474017983/4806197152',
interstitial: 'ca-app-pub-6869992474017983/7563979554'
};
} else {
admobid = { // for Windows Phone
banner: 'ca-app-pub-6869992474017983/8878394753',
interstitial: 'ca-app-pub-6869992474017983/1355127956'
};
}
// it will display smart banner at top center, using the default options
if(AdMob) AdMob.createBanner( {
adId: admobid.banner,
position: AdMob.AD_POSITION.TOP_CENTER,
autoShow: true } );

Clean way to pass events to Titanium from webview?

A common explanation of how to pass events from a Titanium webview involves calling Ti.App.fireEvent() from the HTML in the webview. But, I would like to listen to the webview itself instead of the global Ti.App object, so that duplicate events from different webviews won't trigger inappropriate code for the context.
Eg. if I listen to Ti.App for the "OK_BUTTON" event, it will mean something different depending on where in the flow it is being called. So, I would have to remove and add new listeners for each context.
There is a documented way to pass events directly from the webview using a normal HTML anchor tag with the undefined protocol "xxxx://" and catching the ensuing error event in Titanium. Is there a cleaner way to do this yet? It would be nice to keep the "error" event for errors as intended.
I think you'll need to use Ti.App.fireEvent() from the webview but add data identifying the source webview to the event.
e.g. assign a unique id to each of your webviews and pass that into the webview by doing an 'evalJS()' in the webview's 'load' event handler. (Or by setting the id in your html if you have Titanium generating that)
Here is a basic utility module that assigns an id to a webview and provides a function fireEvent() to webview context that will trigger an event on the webView object in Titanium.
var listenerWebViews = {};
exports.createListenerWebView = function(config) {
var id = createRandomString();
var webView = Ti.UI.createWebView(config);
webView.listenerId = id;
webView.evalJS('function fireEvent(type, e) { var f = { originalType:type, listenerId:"'+id+'"}; Ti.App.fireEvent("WEBVIEW_LISTENER_EVENT", f); }');
listenerWebViews[id] = webView;
var oldRemoveFunction = webView.remove;
webView.remove = function(){
listenerWebViews[id] = null;
oldRemoveFunction();
}
return webView;
}
Ti.App.addEventListener("WEBVIEW_LISTENER_EVENT", function(e){
var webView = listenerWebViews[e.listenerId];
if (webView) {
webView.fireEvent(e.originalType, e);
}
});
With that module included, this works:
var view = module.createListenerWebView({
url: 'myPage.html'
});
view.addEventListener('my_type', function(){
alert('webview event!');
});
view.evalJS("fireEvent('my_type');");

Xrm is undefined in Dynamics CRM 2011

I can't seem to get my Xrm variable working when I create .js code. Is there a library I need to include or a function I need to run first? Do I need to make sure that ClientGlobalContext.js.aspx is referenced correctly? I'm in a 'C++' mindset and just wondering if there is any sort of 'include' command that I need to be running.
My js file looks like this, and if I remove the 'window.parent.' from the 2nd function the code breaks if I call it.
///<reference path="C:\Users\steve.lee\Downloads\XrmPage-vsdoc.js"/>
if (typeof (SDK) == "undefined")
{ SDK = { __namespace: true }; }
SDK.XRM = {
getCurrentControl: function () {
var currentControl = Xrm.Page.ui.getCurrentControl();
if (currentControl == null) {
alert("No controls currently have focus.");
}
else {
alert("The control for the '" + currentControl.getLabel() + "' attribute currently has focus.");
}
},
getCurrentGUID: function () {
if (window.parent.Xrm.Page.data.entity != null) {
var GUIDvalue = window.parent.Xrm.Page.data.entity.getId();
if (GUIDvalue != null) {
return GUIDvalue;
}
else {
return null;
}
}
else {
return null;
}
},
__namespace: true
};
Is your code running as a javascript library added to a form or is it sat inside a web resource?
If its the latter, you need window.parent if its a HTML web resource. As this gets you access to the Xrm object on the form. In the parent window above it.
If it's a javascript library attached to the form properties then the XRM object is available by default.
The ClientGlobalContext.js.aspx is used when you have a web resource that may not be in the context of a form and still gives you access to the server url and the logged in user for example.
The Xrm variable should be available without any includes or setup for normal JS code in CRM 2011.
The basics are covered here

Resources