Search This Blog

Saturday 26 June 2021

Hybrid WebView in Xamarin Forms.

Hybrid WebView


Github Url: https://github.com/mistrypragnesh40/HybridWebView

Xamarin Forms Project.

Create CustomWebView Renderer in your Xamarin form Project.

HybridWebView/Renderer/CustomWebView.cs

using System;
using Xamarin.Forms;
 
namespace HybridWebView.Renderer
{
    public class CustomWebView : View
    {
        Action<string> action;
 
        public static readonly BindableProperty SourceProperty = BindableProperty.Create(
            propertyName: "Source",
            returnType: typeof(string),
            declaringType: typeof(CustomWebView),
            defaultValue: default(string),
            propertyChanged: ItemsSourcePropertyChanged
            );
        private static void ItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (CustomWebView)bindable;
            if (control != null)
            {
                control.Source = newValue.ToString();
            }
        }
 
        public string Source
        {
            get { return (string)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }
 
        public void RegisterAction(Action<string> callback)
        {
            action = callback;
        }
 
        public void Cleanup()
        {
            action = null;
        }
 
        public void InvokeAction(string data)
        {
            if (action == null || data == null)
            {
                return;
            }
            action.Invoke(data);
        }
    }
}

Now Implement CustomWebView Renderer in your Xamarin iOS  and Android Project.

Xamarin iOS Implementation.

HybridWebView.iOS/Renderer/HybridWebViewRenderer.cs

using System;
using System.ComponentModel;
using System.IO;
using Foundation;
using HybridWebView.iOS.Renderer;
using HybridWebView.Renderer;
using WebKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
 
[assembly: ExportRenderer(typeof(CustomWebView), typeof(HybridWebViewRenderer))]
namespace HybridWebView.iOS.Renderer
{
    public class HybridWebViewRenderer : ViewRenderer<CustomWebView, WKWebView>, IWKScriptMessageHandler
    {
        const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
        WKUserContentController userController;
 
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
 
            if (e.PropertyName == "Source")
            {
                if (Element.Source != null)
                {
                    string contentDirectoryPath = Path.Combine(NSBundle.MainBundle.BundlePath);
                    Control.LoadHtmlString(Element.Source, new NSUrl(contentDirectoryPath, true));
                }
    
            }
        }
        protected override void OnElementChanged(ElementChangedEventArgs<CustomWebView> e)
        {
            base.OnElementChanged(e);
 
            if (e.OldElement != null)
            {
                userController.RemoveAllUserScripts();
                userController.RemoveScriptMessageHandler("invokeAction");
                var hybridWebView = e.OldElement as CustomWebView;
                hybridWebView.Cleanup();
            }
            if (e.NewElement != null)
            {
                if (Control == null)
                {
                    userController = new WKUserContentController();
                    var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
                    userController.AddUserScript(script);
                    userController.AddScriptMessageHandler(this, "invokeAction");
 
                    var config = new WKWebViewConfiguration { UserContentController = userController };
                    var webView = new WKWebView(Frame, config);
                    SetNativeControl(webView);
                }
                if (Element.Source != null)
                {
                    string contentDirectoryPath = Path.Combine(NSBundle.MainBundle.BundlePath);
                    Control.LoadHtmlString(Element.Source, new NSUrl(contentDirectoryPath, true));
                }
            }
        }
 
        public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
        {
            if (message.Body != null)
            {
                Element.InvokeAction(message.Body.ToString());
            }
        }
    }
}

Now Add HtmlWebView.JS File in your HybridWebView.iOS/Resources Folder.

HtmlWebView.JS

$(document).ready(function () {
    $('body').on('click', 'a', function (e) {
        e.preventDefault();
        invokeCSCode(e.target.href);
    })
});
function invokeCSCode(data) {
    try {
        invokeCSharpAction(data);
    } catch (err) {
    }
}
 

Xamarin Android Implementation.

HybridWebView.Android/Renderer/HybridWebViewRenderer.cs

using System;
using System.ComponentModel;
using Android.Content;
using Android.Service.Controls;
using Android.Webkit;
using HybridWebView.Droid.Renderer;
using HybridWebView.Renderer;
using Java.Interop;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
 
[assembly: ExportRenderer(typeof(CustomWebView), typeof(HybridWebViewRenderer))]
namespace HybridWebView.Droid.Renderer
{
    public class HybridWebViewRenderer : ViewRenderer<CustomWebView, Android.Webkit.WebView>
    {
        const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
        Context _context;
        public string BaseUrl = "file:///android_asset/";
        public HybridWebViewRenderer(Context context) : base(context)
        {
            _context = context;
        }
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
 
            if (e.PropertyName == "Source")
            {
                Control.LoadDataWithBaseURL(BaseUrl, Element.Source, "text/html", "UTF-8", null);
            }
        }
        protected override void OnElementChanged(ElementChangedEventArgs<CustomWebView> e)
        {
            base.OnElementChanged(e);
 
            if (e.OldElement != null)
            {
                Control.RemoveJavascriptInterface("jsBridge");
                var hybridWebView = e.OldElement as CustomWebView;
                hybridWebView.Cleanup();
            }
            if (e.NewElement != null)
            {
                if (Control == null)
                {
                    var webView = new Android.Webkit.WebView(_context);
                    webView.Settings.JavaScriptEnabled = true;
                    webView.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
                    SetNativeControl(webView);
                }
                Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
                Control.LoadDataWithBaseURL(BaseUrl, Element.Source, "text/html", "UTF-8", null);
            }
        }
    }
 
 
    /// <summary>
    /// JavascriptWebViewClient
    /// </summary>
    public class JavascriptWebViewClient : WebViewClient
    {
        string _javascript;
 
        public JavascriptWebViewClient(string javascript)
        {
            _javascript = javascript;
        }
 
        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            view.EvaluateJavascript(_javascript, null);
        }
    }
 
    /// <summary>
    /// JsBridge Class
    /// </summary>
    public class JSBridge : Java.Lang.Object
    {
        readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;
 
        public JSBridge(HybridWebViewRenderer hybridRenderer)
        {
            hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
        }
 
        [JavascriptInterface]
        [Export("invokeAction")]
        public void InvokeAction(string data)
        {
            HybridWebViewRenderer hybridRenderer;
 
            if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
            {
                hybridRenderer.Element.InvokeAction(data);
            }
        }
    }
}
 

Now Add HtmlWebView.JS File in your HybridWebView.Android/Assets Folder.

HtmlWebView.JS

$(document).ready(function () {
    $('body').on('click', 'a', function (e) {
        e.preventDefault();
        invokeCSCode(e.target.href);
    })
});
function invokeCSCode(data) {
    try {
        invokeCSharpAction(data);
    } catch (err) {
    }
}
 

How to use CustomWebView in Xamarin Form Content Page.

Add CustomWebView in Your Content Page.

HybridWebView/MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:renderer="clr-namespace:HybridWebView.Renderer"
             x:Class="HybridWebView.MainPage">
 
    <StackLayout>
        <renderer:CustomWebView x:Name="hybridWebView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
    </StackLayout>
 
</ContentPage>
 


In CodeBehind Set Jquery & HtmlWebView.JS Reference and bind htmlContent to hybridwebView.Source.

hybridWebView.RegisterAction(data => ShowAction(data)); // showAction() method will invoke when Anchor tag is clicked.

HybridWebView/MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;
 
namespace HybridWebView
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
 
 
            string jquery = @" <script src='https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js'></script>";
            string webView = @"<script src=""HtmlWebView.js""></script>";
 
            string html = $"<html><head>{jquery} {webView}</head>" +
                           "Html Link1 : <a href='http://www.google.com'>Click here</a> </br>" +
                           "Html Link2: <a href='https://xamarincodingtutorial.blogspot.com'>Xamarin Coding Tutorial</a> </html>";
            hybridWebView.Source = html;
           hybridWebView.RegisterAction(data => ShowAction(data));
        }
 
        public void ShowAction(string data)
        {
            if (!string.IsNullOrWhiteSpace(data))
            {
                try
                {
                    Browser.OpenAsync(data, BrowserLaunchMode.SystemPreferred);
                }
                catch (Exception ex)
                {
 
                }
            }
        }
    }
}
 



Monday 21 June 2021

How to add event phone calendar xamarin forms.

Xamarin Form Project 

ICalendarService Interface.

using System;
using System.Threading.Tasks;
 
namespace Test_App.Services.Interfaces
{
    public interface ICalendarService
    {
        Task<(bool isAdded, string message)> AddEventToCalendar(DateTime startDate, DateTime endDate, string title, string description, string location);
    }
}
 


Xamarin IOS Project

Create a CalendarService class that inherits ICalendarService Interface.

using EventKit;
using Foundation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Test_App.iOS.Services;
using Test_App.Services.Interfaces;
using UIKit;
 
[assembly: Xamarin.Forms.Dependency(typeof(CalendarService))]
namespace Test_App.iOS.Services
{
    public class CalendarService : ICalendarService
    {
        public async Task<(bool isAdded, string message)> AddEventToCalendar(DateTime startDate, DateTime endDate, string title, string description, string location)
        {
            bool isEventAdded = false;
            string message = string.Empty;
            try
            {
                var requestAccessResponse = await AppConstant.App.Current.EventStore.RequestAccessAsync(EKEntityType.Event);
 
                if (requestAccessResponse.Item1)
                {
                    NSDate nsStartDate = AppConstant.DateTimeToNSDate(startDate);
                    NSDate nsEndDate = AppConstant.DateTimeToNSDate(endDate);
                    NSPredicate query = AppConstant.App.Current.EventStore.PredicateForEvents(nsStartDate, nsEndDate, null);
 
                    EKEvent newEvent = EKEvent.FromStore(AppConstant.App.Current.EventStore);
                    var events = AppConstant.App.Current.EventStore.EventsMatching(query).ToList();
                    if (events?.Count > 0)
                    {
                        bool isExist = events.Any(f => f.Title == title && f.Notes == description && f.Location == location);
 
                        if (isExist)
                        {
                            message = "This Event already exist in your calendar.";
                            isEventAdded = true;
                        }
                    }
 
                    if (!isEventAdded)
                    {
                        newEvent.Calendar = AppConstant.App.Current.EventStore.DefaultCalendarForNewEvents;
                        newEvent.StartDate = nsStartDate;
                        newEvent.EndDate = nsEndDate;
                        newEvent.Title = title;
                        newEvent.Location = location;
                        newEvent.Url = new NSUrl("https://xamarincodingtutorial.blogspot.com/");
                        newEvent.Notes = description;
                        NSError et;
                        bool isSaved = AppConstant.App.Current.EventStore.SaveEvent(newEvent, EKSpan.ThisEvent, true, out et);
                        if (isSaved)
                        {
                            message = "Event Added to your calendar.";
                            isEventAdded = true;
                        }
                        else
                        {
                            if (et != null)
                            {
                                message = et.Description;
                            }
                        }
                    }
 
                }
                else
                {
                    message = "Calendar Permission required to save event";
                }
            }
            catch (Exception ex)
            {
                message = ex.Message;
            }
            return (isEventAdded, message);
        }
 
 
    }
}

Create a AppConstant.cs Class in your iOS Project that contain Date Conversion Code.

AppConstant.cs
using EventKit;
using Foundation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UIKit;
 
namespace Test_App.iOS
{
    public static class AppConstant
    {
        public static NSDate DateTimeToNSDate(this DateTime date)
        {
            if (date.Kind == DateTimeKind.Unspecified)
                date = DateTime.SpecifyKind(date, DateTimeKind.Local);
            return (NSDate)date;
        }
 
        public class App
        {
            public static App Current
            {
                get { return current; }
            }
            private static App current;
            public EKEventStore EventStore
            {
                get { return eventStore; }
            }
            protected EKEventStore eventStore;
            static App()
            {
                current = new App();
            }
            protected App()
            {
                eventStore = new EKEventStore();
            }
        }
    }
}

Xamarin Android Project

Create a CalendarService class that inherits ICalendarService Interface.

using Android.Content;
using Android.Provider;
using System;
using System.Threading.Tasks;
using Test_App.Droid.Services;
using Test_App.Services.Interfaces;
 
[assembly: Xamarin.Forms.Dependency(typeof(CalendarService))]
namespace Test_App.Droid.Services
{
    public class CalendarService : ICalendarService
    {
        public async Task<(bool isAdded, string message)> AddEventToCalendar(DateTime startDate, DateTime endDate, string title, string description, string location)
        {
            bool isEventAdded = true;
            string message = string.Empty;
            try
            {
                Intent eventValues = new Intent(Intent.ActionInsert);
                eventValues.SetData(CalendarContract.Events.ContentUri);
                eventValues.AddFlags(ActivityFlags.NewTask);
                eventValues.PutExtra(CalendarContract.Events.InterfaceConsts.CalendarId, 2);
                eventValues.PutExtra(CalendarContract.Events.InterfaceConsts.Title, title);
                eventValues.PutExtra(CalendarContract.Events.InterfaceConsts.Description, description);
                eventValues.PutExtra(CalendarContract.ExtraEventBeginTime, AppConstant.GetDateTimeMS(startDate.Year, startDate.Month, startDate.Day, startDate.Hour, startDate.Minute));
                eventValues.PutExtra(CalendarContract.ExtraEventEndTime, AppConstant.GetDateTimeMS(endDate.Year, endDate.Month, endDate.Day, endDate.Hour, endDate.Minute));
                eventValues.PutExtra(CalendarContract.Events.InterfaceConsts.EventTimezone, "UTC");
                eventValues.PutExtra(CalendarContract.Events.InterfaceConsts.EventEndTimezone, "UTC");
                eventValues.PutExtra(CalendarContract.Events.InterfaceConsts.EventLocation, location);
                Android.App.Application.Context.StartActivity(eventValues);
            }
            catch (Exception ex)
            {
                isEventAdded = false;
            }
            return (isEventAdded, message);
        }
    }
}

Create a AppConstant.cs Class in your Android Project that contain Date Conversion Code.

AppConstant.cs
using Java.Util;
 
namespace Test_App.Droid
{
    public static class AppConstant
    {
        public static long GetDateTimeMS(int year, int month, int day, int hr, int min)
        {
            Calendar c = Calendar.GetInstance(Java.Util.TimeZone.Default);
 
            c.Set(Java.Util.CalendarField.DayOfMonth, day);
            c.Set(Java.Util.CalendarField.HourOfDay, hr);
            c.Set(Java.Util.CalendarField.Minute, min);
            c.Set(Java.Util.CalendarField.Month, month -1);
            c.Set(Java.Util.CalendarField.Year, year);
 
            return c.TimeInMillis;
        }
    }
}

Implementations

 var eventDetails = await DependencyService.Get<ICalendarService>().AddEventToCalendar(startDateTime, endDateTime, eventName, description, location);


For Requesting Calendar Permission. (Android)

Plugin Used: Plugin.Permission.CrossPermission

var status = await CrossPermissions.Current.CheckPermissionStatusAsync<CalendarPermission>();
if (status != PermissionStatus.Granted)
{
	PermissionStatus permissionStatus = await CrossPermissions.Current.RequestPermissionAsync<CalendarPermission>();
	if (permissionStatus != PermissionStatus.Granted)
        {
              //"Heads Up! Please Allow Calendar Permission."
	}
}



Popular Posts