Hi,
I am using a HybridWebVIew.
My project is based on https://github.com/UdaraAlwis/Xamarin-Playground/tree/master/XFHybridWebViewAdvDemo
There is a JS function in the Razor View that calls Xamarin Forms to get for instance, geolocation. My problem is when I call this function from javascript , the code does not wait for the c# to be finished so I have not got the geolocation values ready in Javascript.
Is there a way I can execute the C# function and wait for the results from the javascript in the razor view?
This is my code:
Android:
public class JavascriptWebViewClient : WebViewClient
{
readonly string _javascript;
public JavascriptWebViewClient(string javascript)
{
_javascript = javascript;
}
public override void OnReceivedSslError(Android.Webkit.WebView view, SslErrorHandler handler, Android.Net.Http.SslError error)
{
//base.OnReceivedSslError(view, handler, error);
handler.Proceed();
}
public override void OnPageStarted(WebView view, string url, Android.Graphics.Bitmap favicon)
{
base.OnPageStarted(view, url,favicon);
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
public class JsBridge : Java.Lang.Object
{
readonly WeakReference<HybridWebViewRenderer> HybridWebViewMainRenderer;
public JsBridge(HybridWebViewRenderer hybridRenderer)
{
HybridWebViewMainRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public async void InvokeAction(string data)
{
if (HybridWebViewMainRenderer != null && HybridWebViewMainRenderer.TryGetTarget(out var hybridRenderer))
{
var dataBody = data;
if (dataBody.Contains("|"))
{
var paramArray = dataBody.Split("|");
var param1 = paramArray[0];
var param2 = paramArray[1];
((HybridWebView)hybridRenderer.Element).InvokeAction(param1, param2);
}
else
{
((HybridWebView)hybridRenderer.Element).InvokeAction(dataBody, null);
}
}
}
}
Xamarin Forms:
public class HybridWebView : WebView
{
private Action<string, string> _action;
public void RegisterAction(Action<string, string> callback)
{
_action = callback;
}
public void Cleanup()
{
_action = null;
}
public void InvokeAction(string param1, string param2)
{
if (_action == null || (param1 == null && param2 == null))
{
return;
}
if (MainThread.IsMainThread)
_action.Invoke(param1, param2);
else
MainThread.BeginInvokeOnMainThread(() => _action.Invoke(param1, param2));
}
}
public class DeviceFeaturesHelper
{
public async Task<string> TakePhoto(ContentPage pageContext)
{
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
await pageContext.DisplayAlert("No Camera", ":( No camera available.", "OK");
return null;
}
await CheckForCameraAndGalleryPermission();
var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
Directory = "TestPhotoFolder",
SaveToAlbum = true,
CompressionQuality = 75,
CustomPhotoSize = 50,
PhotoSize = PhotoSize.Medium,
MaxWidthHeight = 1000,
});
if (file == null)
return null;
// Convert bytes to base64 content
var imageAsBase64String = Convert.ToBase64String(ConvertFileToByteArray(file));
return imageAsBase64String;
}
public async Task<string> SelectPhoto(ContentPage pageContext)
{
if (!CrossMedia.Current.IsPickPhotoSupported)
{
await pageContext.DisplayAlert("Photos Not Supported", ":( Permission not granted to photos.", "OK");
return null;
}
await CheckForCameraAndGalleryPermission();
var file = await Plugin.Media.CrossMedia.Current.PickPhotoAsync(new Plugin.Media.Abstractions.PickMediaOptions
{
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium,
});
if (file == null)
return null;
// Convert bytes to base64 content
var imageAsBase64String = Convert.ToBase64String(ConvertFileToByteArray(file));
return imageAsBase64String;
}
private byte[] ConvertFileToByteArray(MediaFile imageFile)
{
// Convert Image to bytes
byte[] imageAsBytes;
using (var memoryStream = new MemoryStream())
{
imageFile.GetStream().CopyTo(memoryStream);
imageFile.Dispose();
imageAsBytes = memoryStream.ToArray();
}
return imageAsBytes;
}
private async Task<bool> CheckForCameraAndGalleryPermission()
{
List<Permission> permissionList = new List<Permission>{ Permission.Camera, Permission.Storage, Permission.Photos };
List<PermissionStatus> permissionStatuses = new List<PermissionStatus>();
foreach (var permission in permissionList)
{
var status = await CrossPermissions.Current.CheckPermissionStatusAsync(permission);
permissionStatuses.Add(status);
}
var requiresRequesst = permissionStatuses.Any(x => x != PermissionStatus.Granted);
if (requiresRequesst)
{
var permissionRequestResult = await CrossPermissions.Current.RequestPermissionsAsync(permissionList.ToArray());
}
return true;
}
public async Task<string> GetDeviceData()
{
// Device Model (SMG-950U, iPhone10,6)
var device = DeviceInfo.Model;
// Manufacturer (Samsung)
var manufacturer = DeviceInfo.Manufacturer;
// Device Name (Motz's iPhone)
var deviceName = DeviceInfo.Name;
// Operating System Version Number (7.0)
var version = DeviceInfo.VersionString;
// Platform (Android)
var platform = DeviceInfo.Platform;
// Idiom (Phone)
var idiom = DeviceInfo.Idiom;
// Device Type (Physical)
var deviceType = DeviceInfo.DeviceType;
return $"{nameof(DeviceInfo.Model)}: {device}<br />" +
$"{nameof(DeviceInfo.Manufacturer)}: {manufacturer}<br />" +
$"{nameof(DeviceInfo.Name)}: {deviceName}<br />" +
$"{nameof(DeviceInfo.VersionString)}: {version}<br />" +
$"{nameof(DeviceInfo.Platform)}: {platform}<br />" +
$"{nameof(DeviceInfo.Idiom)}: {idiom}<br />" +
$"{nameof(DeviceInfo.DeviceType)}: {deviceType}";
}
public async Task<string> GetGpsLocation()
{
var request = new GeolocationRequest(GeolocationAccuracy.High);
var location = await Geolocation.GetLocationAsync(request);
if (location != null)
{
//return $"{nameof(Location.Latitude)}: {location.Latitude}<br />" +
// $"{nameof(Location.Longitude)}: {location.Longitude}<br />" +
// $"{nameof(Location.Altitude)}: { location.Altitude ?? 0.00 }";
return $"{location.Latitude}|" +
$"{location.Longitude}|" +
$"{location.Altitude ?? 0.00}";
}
return null;
}
public async Task<bool> SaveCredentials(string data)
{
try
{
await SecureStorage.SetAsync("token", data);
return true;
}
catch (Exception ex)
{
// Possible that device doesn't support secure storage on device.
}
return false;
}
public async Task<bool> GetCredentials(string data)
{
try
{
await SecureStorage.GetAsync("token");
return true;
}
catch (Exception ex)
{
// Possible that device doesn't support secure storage on device.
}
return false;
}
public async Task<bool> DeleteCredentials(string data)
{
try
{
return SecureStorage.Remove("token");
}
catch (Exception ex)
{
// Possible that device doesn't support secure storage on device.
}
return false;
}
}
MainPage code:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainPage : ContentPage
{
private DeviceFeaturesHelper _deviceFeaturesHelper;
public MainPage()
{
InitializeComponent();
webViewElement.Source = new HtmlWebViewSource()
{
Html = HtmlSourceContent.Content,
};
webViewElement.RegisterAction(ExecuteActionFromJavascript);
_deviceFeaturesHelper = new DeviceFeaturesHelper();
}
private async void ExecuteActionFromJavascript(string param1, string param2)
{
//statusActivityIndicator.IsVisible = true;
//statusLabel.Text = $"Received request: {param1} {param2}";
if (param1 != null && param1.Equals("PHOTO") && param2.Equals("CAMERA"))
{
var result = await _deviceFeaturesHelper.TakePhoto(this);
if (result != null)
{
await webViewElement.EvaluateJavaScriptAsync($"setresult_takephoto('{result}')");
}
}
else if (param1 != null && param1.Equals("PHOTO") && param2.Equals("GALLERY"))
{
var result = await _deviceFeaturesHelper.SelectPhoto(this);
if (result != null)
{
await webViewElement.EvaluateJavaScriptAsync($"setresult_selectphoto('{result}')");
}
}
else if (param1 != null && param1.Equals("DEVICEINFO"))
{
var result = await _deviceFeaturesHelper.GetDeviceData();
if (result != null)
{
await webViewElement.EvaluateJavaScriptAsync($"setresult_getdeviceinfo('{result}')");
}
}
else if (param1 != null && param1.Equals("GPS"))
{
var result = await _deviceFeaturesHelper.GetGpsLocation();
if (result != null)
{
await webViewElement.EvaluateJavaScriptAsync($"setresult_getgpslocation('{result}')");
}
}
else if (param1 != null && param1.Equals("REMEMBER"))
{
}
//statusActivityIndicator.IsVisible = false;
//statusLabel.Text = $"Waiting for requests from Javascript in the WebView...";
}
private void htmlSourceSwitch_Toggled(object sender, ToggledEventArgs e)
{
if (e.Value)
{
webSourceStatusLabel.Text = "HTML Source: Online hosted Web content.";
//webViewElement.Source = "https://testwebpage.htmlsave.com/";
webViewElement.Source = "https://192.168.1.126:45455/";
}
else
{
webSourceStatusLabel.Text = "HTML Source: Local generated Web content.";
webViewElement.Source = new HtmlWebViewSource()
{
Html = HtmlSourceContent.Content,
};
}
}
}
The javascript is called like this from the web view: invokexamarinforms('GPS'); when I call this function, the code will send longitute|latitude|altitude to the webview. So is there a way I can wait for those values to be available when I call invokexamarinforms('GPS') or return the string when calling invokexamarinforms('GPS')?
I cannot find a solution and not certain where the issue is to make it wait.
Thanks