Category Archives: Xamarin

Invoke C# code via Javascript using Xamarin.Android WebView

I’m working on a Xamarin.Forms experiment involving some use of Xamarin.Forms.WebView. I had some issues with XLabs.HybridWebView  which I found too slow for my porpouses (pages renders in many milliseconds and javascript animation doesn’t run as smooth as I expected).

In spite of those lack, I wrote a native Activity to solve my porpouse.

The complete solution

I needed those 2 functionalities the Xamarin.Forms doesn’t offer:

  • I needed to invoke some C# methods from Javascript
  • I needed to load an external URL and inject some javascript to customize the site
using Android.App;
using Android.Widget;
using Android.OS;
using Android.Webkit;
using Android.Content.Res;
using System.IO;
using Android.Content;
using Java.Interop;

namespace PocXamAndr
{
    [Activity(Label = "PocXamAndr", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        public static string jsInjection;
        static JavaScriptInterface jsInterface;
        WebView webView { get; set; }

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            jsInterface = new JavaScriptInterface(this);
            AssetManager assets = this.Assets;
            using (StreamReader sr = new StreamReader(assets.Open("script.js")))
            {
                jsInjection = sr.ReadToEnd();
            }
            WebView webView = new WebView(this);
            webView.Settings.JavaScriptEnabled = true;
            webView.Settings.AllowUniversalAccessFromFileURLs = true;
            webView.AddJavascriptInterface(jsInterface, "JSInterface");
            webView.SetWebViewClient(new CustomWebClient());
            webView.LoadUrl("http://yoururl.com");
            SetContentView(webView);
        }
    }

    public class JavaScriptInterface : Java.Lang.Object
    {
        private Context activity;
        public JavaScriptInterface(Context activiy)
        {
            this.activity = activiy;
        }

        [Export]
        [JavascriptInterface]
        public void doThings()
        {
            Toast.MakeText(activity, "Hello from C#", ToastLength.Short).Show();
        }
    }

    public class CustomWebClient : WebViewClient
    {
        public override void OnPageFinished(WebView view, string url)
        {
            base.OnPageFinished(view, url);
            injectScriptFile(view);

        }
        private void injectScriptFile(WebView view)
        {
            string encoded = Android.Util.Base64.EncodeToString(System.Text.Encoding.ASCII.GetBytes(MainActivity.jsInjection), Android.Util.Base64Flags.NoWrap);
            view.LoadUrl("javascript:(function() {" +
                    "var parent = document.getElementsByTagName('head').item(0);" +
                    "var script = document.createElement('script');" +
                    "script.type = 'text/javascript';" +
                    "script.innerHTML = window.atob('" + encoded + "');" +
                    "parent.appendChild(script)" +
                    "})()");
        }
    }
}

To use the Export decorator add a reference to Mono.Android.Export

The interesting parts

This code let me implement a custom WebClient (look at CustomWebClient inheritance model) which overrides the OnPageFinished (aka OnNavigationCompleted) default behaviout:

webView.Settings.JavaScriptEnabled = true;
            webView.Settings.AllowUniversalAccessFromFileURLs = true;
            webView.AddJavascriptInterface(jsInterface, "JSInterface");
            webView.SetWebViewClient(new CustomWebClient());

The AddJavascriptInterface creates a fake javascript object which links to the C# JavaScriptInterface instance.

The injectScriptFile method is pretty awesome: the problem I found with

webView.LoadUrl("javascript:...");

is that the javascript code was executed outside the DOM context and because of this it was useless for my purpose.

The javascript resources

I preferred place my javascript inside an external folder despite the I had to add a reference to System.Io. By the way, the app is only 4MB and loading the js outside made me free from the frustration of escaping every line of javascript.
Here my Assets/script.js

(function () {
	var node = document.createElement('LI');
	var anode = document.createElement('A');
	var textnode = document.createTextNode('Invoke C# code...');
	node.appendChild(anode);
	anode.appendChild(textnode);
	document.querySelector('.someStyle').appendChild(node); //Must match some DOM element
	node.addEventListener('click', function () {
		JSInterface.doThings(); //Will invoke the C# equivalent
	});
})();

Last lines

I wrote this code in Java before because after seing the XLabs.HybridWebView I opted for that solution. This post borns after I wrote the equivalent native code with Xamarin, just to have some fun and to compare performance.
I’m glad to say performance are equivalent using Xamarin.Android… I guess there still are some optimization problems with Xamarin.Forms 😐

Xamarin Forms SwipeListView

 

The running appLet’s see how to build a fully customizable and cross platform Swipe-Enabled ListView in Xamarin Forms.
[Download] You can download from my GitHub the working project. This article aims to show I solved the problem in order to let you build your own Xamarin Form component.

The starting idea

A SwipeListView is a ListView where you can swipe left or right to perform some actions.

ListViewTo achive our goal we would replace the content of every item into the ListView with three different Box called MainContent, SwipeLeftContent and SwipeRightContent. As you can imagine only the MainContent will be draggable. SwipeLeftContent and SwipeRightContent will be displayed depending on MainContent position.

As a Xamarin Forms developer you would try to build all the SwipeListView inside a PCL, but you will find that Xamarin Forms has some limits due to his cross-platform vocation.
At some point, for example, we would like to perform some pan operations but Xamarin Forms lacks of any decent gestures recognition functionality:

			var panRecognizer = new PanGestureRecognizer();
			panRecognizer.PanUpdated += (object sender, PanUpdatedEventArgs panEvent) {
				//All we have is panEvent.TotalX and panEvent.TotalY;
			}

We assume we have to create a native ViewCell, which means we may deal with a custom renderer. If you don’t know what a custom renderer is look at there: https://developer.xamarin.com/guides/xamarin-forms/custom-renderer/

Writing a ViewCell custom renderer (or, in other words, deriving from ViewCellRenderer) is more than what we need for now. I think that we should try to write all our design in a XAML PCL and use a custom renderer to manage gestures only.
So our Main.xaml would look like:

		<ListView ItemsSource="{Binding ListItems}">
			...<ViewCell>
				<xam:SwipeItemView BoundItem="{Binding .}">
					<xam:SwipeItemView.MainContent>
					<!-- Something bindable !-->
					</xam:SwipeItemView.MainContent>
				</xam:SwipeItemView>
			</ViewCell>...
		</ListView>

And we can expect a xam:SwipeItemView defined this way:

<ContentView>
	<Grid>
		<ContentView IsVisible="{Binding IsRightContentVisible}">
			<!-- Content showed when swiping right !--></ContentView>
		<ContentView IsVisible="{Binding !IsRightContentVisible}">
			<!-- Content showed when swiping right !--></ContentView>
		<ContentView>
			<ContentView x:Name="innerContent"><!--Default content !--></ContentView>
		</ContentView>
	</Grid>
</ContentView>

This object represents each list item object. I added this reference to make our component flexible: I wanted a fully customizable mainContent, which means a property which can contain a view with his own bindings.

The BindingContext is inherited inside all the hierarchy. You can imagine we don’t need a BoundItem object. But for our pourposes we need some internal binding inside our custom view in order to know if the user is swiping left or right (and than to choose which button have to be displayed).

The property IsRightContentVisible is the reason why we need a BoundObject:

namespace XamSwipeList.SwipeList
{
	public partial class SwipeItemView : ContentView, INotifyPropertyChanged
	{
		public bool SwipeCompleted { get; set; }
		public View MainContent { get ... set ... }
		public object BoundItem { get ... set ... }
		public static readonly BindableProperty MainContentProperty;
		public static readonly BindableProperty BoundItemProperty;
		public SwipeItemView()
		{...
			mainContent.BindingContext = this; //The important part
		}
		static SwipeItemView()
		{
			MainContentProperty = BindableProperty.Create(..., propertyChanged: MainContentChanged);
			BoundItemProperty = BindableProperty.Create(..., propertyChanged: BoundItemChanged);
		}

		private static void BoundItemChanged(BindableObject bindable, object oldValue, object newValue)
		{...
			(bindable as SwipeItemView).innerContent.BindingContext = newValue; //The important part
		...}

		private static void MainContentChanged(BindableObject bindable, object oldValue, object newValue)
		{...
			(bindable as SwipeItemView).innerContent.Content = (View)newValue;
		...}
	}
}

Dealing with the custom renderer

At this point we are going to write our custom renderers to manage Gestures and pass the swipe as a Translation for our View. Of course we have to override DispatchTouchEvent, but we will two problem.touchAreas

  • Our swipe action must start and ends inside the ViewCell or our swipe will be lost, because we can’t conserve touch outside the bounding box the the item which started the swipe (which is the one we want to swipe also if the swipe is completed outside his bounding box).
  • We will notice a second problem when we try to delete an Item from our ObservableCollection. Xamarin Forms has a bug that doesn’t allow you to properly remove an item. If you SwipeRight an item (changing his TranslationX property) in order to delete this item, the TranslationX property of the removed item will be applied to the next element. I reported this bug many times ago but nothing appened. To hardfix this bug we have to reset the TranslationX property before deleting the item. This bug is detailed on Xamarin Forms BugZilla

Because of those two problems, we have to create a new View, with his own CustomRenderer which inherits from ListView. This SwipeListView  will catch all the touch events fired inside the List.

namespace XamSwipeList.SwipeList
{
	public class SwipeListView : ListView
	{
		private List&lt;SwipeItemView&gt; TouchedElements;
		public void PreventXamarinBug()
		{
			foreach(var elem in TouchedElements)
			{
				elem.PristineItem();
			}
		}

		public void AppendTouchedElement(SwipeItemView item)
		{
			TouchedElements.Add(item);
		}
	}
}

Now, let’s see how the custom renderer works:

[assembly: Xamarin.Forms.ExportRenderer(...)]
namespace XamSwipeList.Droid.CustomRenderer
{
	class SwipeItemRenderer : ViewRenderer
	{
		public override bool DispatchTouchEvent(MotionEvent touch)
		{
			if(TouchDispatcher.TouchingView == null &amp;&amp; touch.ActionMasked == MotionEventActions.Down)
			{
				TouchDispatcher.TouchingView = this.Element;
				TouchDispatcher.StartingBiasX = touch.GetX();
				TouchDispatcher.StartingBiasY = touch.GetY();
				TouchDispatcher.InitialTouch = DateTime.Now;
				return true;
			} //TouchDispatcher should be a static class containing the swiping view
			return base.DispatchTouchEvent(touch);
		}
	}
}

We manage only the “first touch” on the ListItem‘s custom renderer. All the other actions will be managed into the SwipeList custom renderer:

[assembly: Xamarin.Forms.ExportRenderer(...)]
namespace XamSwipeList.Droid.CustomRenderer
{
	class SwipeListRenderer : ListViewRenderer
	{
		public override bool DispatchTouchEvent(MotionEvent touch)
		{
			if (TouchDispatcher.TouchingView != null)
			{
				double currentQuota = ((touch.GetX() - TouchDispatcher.StartingBiasX) / (double)this.Width); //swiping percent
				float x = touch.GetX();
				float y = touch.GetY();
				switch (touch.ActionMasked)
				{
					case MotionEventActions.Up:
						touchedElement.CompleteTranslation(currentQuota);
						this.Element.AppendTouchedElement(touchedElement);
						TouchDispatcher.ResetAll();
						break;
					case MotionEventActions.Move:
						TouchDispatcher.TouchingView.PerformTranslation(currentQuota);
						break;
				}
			}
			return base.DispatchTouchEvent(touch);
		}
	}
}

And this is it. The implementation of PerformTranslation() is trivial.

The real code

In this post I haven’t used working code for brevity and to focus on the logic of the implementation.

You can find the working project on my github here.

Contributors

I wrote the Android part only beacause I’m not confident with Xamarin iOs. Any contribution to this project on Github will be welcomed.
If you have any idea abount how to solve this Xamarin Forms bug, please send me a note. We all know Xamarin is young and buggy… but we can help fix these little issues.