How to wait for execution to be finished in C# function from Javascript in Hybrid WebView

David 356 Reputation points
2021-04-22T13:59:52.047+00:00

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

Developer technologies .NET Xamarin
{count} votes

Accepted answer
  1. David 356 Reputation points
    2021-04-23T16:09:29.043+00:00

    I have decided to leave it asynchronous and change the way it works.

    I will call invokexamarinforms('GPS') on the button click and then I let xamarin webview execute this: webViewElement.EvaluateJavaScriptAsync($"setresult_getgpslocation('{result}')");

    The function setresult_getgpslocation() will submit the data to the server instead. That way it works asynchronously.

    0 comments No comments

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.