Making Enum as Bindable Property

This post is actually just an update from my previous post about Entry with title. I’ve made some enhancement to optimize my titled entry. From several changes, I think this enum bindable property is the most important so I decided to make one post discussing about it. But in this post I will also review some other minor changes. So, Let’s begin.

Additional Properties and methods

I added two additional properties. The first is Text, for binding a text to the entry, and Entry keyboard to binding the type of the keyboard. Those two properties are two most essential features of entry that I previously forgot to add. For Text property, it’s just another simple string binding property, but for the keyboard, it’s little bit different. Because entry’s keyboard has several different types that developer can choose depend on what field the entry’s used for. So, I created an enum bindable property to tackle that issue. This is how the code looks like.

public partial class TitledEntry : ContentView
{
    string placeholder = string.Empty;

    public enum KeyboardEnum
    {
        Default,
        Text,
        Chat,
        Url,
        Email,
        Telephone,
        Numeric,
    }

    public TitledEntry()
    {
        InitializeComponent();

        EntryContent.BindingContext = this;
        LabelTitle.BindingContext = this;
        LabelTitle.Text = string.Empty;
    }

    public static BindableProperty PlaceholderProperty =
        BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(TitledEntry), null, BindingMode.TwoWay);

    public string Placeholder
    {
        get => (string)GetValue(PlaceholderProperty);
        set => SetValue(PlaceholderProperty, value);
    }

    public static BindableProperty TextProperty =
        BindableProperty.Create(nameof(Text), typeof(string), typeof(TitledEntry), null, BindingMode.TwoWay);

    public string Text
    {
        get => (string)GetValue(TextProperty);
        set => SetValue(TextProperty, value);
    }

    public static BindableProperty KeyboardProperty =
        BindableProperty.Create(nameof(KeyboardProperty), typeof(KeyboardEnum), typeof(TitledEntry), KeyboardEnum.Default, BindingMode.TwoWay);

    public KeyboardEnum EntryKeyboard
    {
        get => (KeyboardEnum)GetValue(KeyboardProperty);
        set
        {
            SetValue(KeyboardProperty, value);
            SetKeyboard();
        }
    }

    void Handle_Focused(object sender, FocusEventArgs e)
    {
        LabelTitle.Text = Placeholder;

        if (EntryContent.Text == null || EntryContent.Text.Length == 0)
        {
            placeholder = EntryContent.Placeholder;
            EntryContent.Placeholder = string.Empty;
        }

    }

    void Handle_Unfocused(object sender, FocusEventArgs e)
    {
        if (EntryContent.Text == null || EntryContent.Text.Length == 0)
        {
            EntryContent.Placeholder = placeholder;
            LabelTitle.Text = string.Empty;
        }

    }

    void Handle_TextChanged(object sender, TextChangedEventArgs e)
    {
        var entry = sender as Entry;
        if (!string.IsNullOrEmpty(entry.Text))
        {
            LabelTitle.Text = Placeholder;
        }
    }

    void SetKeyboard()
    {
        switch (EntryKeyboard)
        {
            case KeyboardEnum.Default:
                EntryContent.Keyboard = Keyboard.Default;
                break;
            case KeyboardEnum.Text:
                EntryContent.Keyboard = Keyboard.Text;
                break;
            case KeyboardEnum.Chat:
                EntryContent.Keyboard = Keyboard.Chat;
                break;
            case KeyboardEnum.Url:
                EntryContent.Keyboard = Keyboard.Url;
                break;
            case KeyboardEnum.Email:
                EntryContent.Keyboard = Keyboard.Email;
                break;
            case KeyboardEnum.Telephone:
                EntryContent.Keyboard = Keyboard.Telephone;
                break;
            case KeyboardEnum.Numeric:
                EntryContent.Keyboard = Keyboard.Numeric;
                break;
            default:
                EntryContent.Keyboard = Keyboard.Default;
                break;
        }
    }
}

As you can see at the code, I create similar enum like the original entry’s keyboard and then I created a bindable property for that enum. And then I create the method SetKeyboard to convert my enum to the real keyboard. I know I can use value converter, but I think it’s more simple this way.

Another method that I added is Text Changed event handling for the entry. I use this method to handle if entry text already has value from beginning.  And for note, I also make a change in xaml side. I turned the stack layout into grid to optimize the rendering performance of the xaml.

BindableEnum1

Sample Code is available in my Github repo

Advertisements

Turning Entry’s Placeholder into Title in Xamarin Forms

I really hope that this title is not misleading. I’m not sure ‘turning’ is the right word, but I don’t know better word to describe what I want to discuss in this post. As we know together, in Android Native app, we can have Entry component whose placeholder will turn into title with a little ‘going up’ animation when user start interact with it. And because you can only have it in Android, not in other platform, Xamarin Forms doesn’t support it, at least not yet. Unfortunately, in my current project I’m already required to create such a thing. So, this is how I do the work around, not perfect yet but not too bad either.

Custom Entry with Title

The first thing I do is creating a custom content view with a stack layout in it. The stack layout contains an Entry and a Label that will serve as Entry’s title. Why stack layout, not Grid? It’s not just for simplicity’s sake. Remember the ‘going up’ animation I discuss earlier? I want to create an effect like that, not as good as the original, but I think it’s good enough. When you hide something in stack layout and something trigger it to appear, it will create a little animation, up or down depend on the position. It also works the other way around, when you hide something, you’ll get similar animation. So, this how I put the Entry and Label on the xaml file.

TitledEntryCode1

Pay the on the Entry. It has event handler for Focused and Unfocused event, that where we gonna do the trick. And the code below is how the code behind looks like.

public partial class TitledEntry : ContentView
{
    string placeholder = string.Empty;

    public TitledEntry()
    {
        InitializeComponent();

        if (EntryContent.Text == null || EntryContent.Text.Length == 0)
        {
            LabelTitle.IsVisible = false;
        }

        EntryContent.BindingContext = this;
        LabelTitle.BindingContext = this;
    }

    public static BindableProperty PlaceholderProperty =
        BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(TitledEntry), null, BindingMode.TwoWay);

    public string Placeholder
    {
        get => (string)GetValue(PlaceholderProperty);
        set => SetValue(PlaceholderProperty, value);
    }

    void Handle_Focused(object sender, FocusEventArgs e)
    {
        LabelTitle.IsVisible = true;

        if (EntryContent.Text == null || EntryContent.Text.Length == 0)
        {
            placeholder = EntryContent.Placeholder;
            EntryContent.Placeholder = string.Empty;
        }

    }

    void Handle_Unfocused(object sender, FocusEventArgs e)
    {
        if (EntryContent.Text == null || EntryContent.Text.Length == 0)
        {
            EntryContent.Placeholder = placeholder;
            LabelTitle.IsVisible = false;
        }

    }
}

So all I do is just giving condition about Entry’s text length and stack layout will do the rest. If there’s one thing you need to remember is setting the binding context of the Entry and Label to current class like I do in the constructor or you won’t be able to bind them from view model.

Let’s use it

Now that we already have our custom entry ready, let see it in action. I create a view model contains 2 strings called Username and Email and I set a view model as the binding context for the class whose xaml file you can see below.

TitledEntryCode2

And if try to run the solution, you’ll have Entries like the real Android’s Entry.

 

Update : Making Enum as Bindable Property

Sample Code is available in my Github repo

 

Get to know about iOS Web Kit Web View

Recently I’ve been informed that I want to display Web View on iOS application, I actually have 3 options. The first, the most common one, using UI Web View. I’ve been using it in all my project whenever I need  to display a web view, whether Swift project or Xamarin. Second one is Web Kit Web View or WK Web View in short. WK Web View has similar functionalities and methods like UI Web View but it designed to overcome UI Web View weakness when compiling Java Script. Third is Safari View Controller. It can be said that you put Safari Browser inside your app. The purpose is your user doesn’t need to navigate outside your app when you want to display a web. It’s about maintaining user engagement to your app. I already posted twice about UI Web View and not so much I can discuss about Safari, so in this post I want to discuss about WK Web View and how to migrate from UI Web View to WK Web View in Xamarin Forms.

Hybrid Web View

If you look up about Hybrid Web View on internet, the first link you get will directly navigate you to Xamarin documentation about how to use WK Web View. For starter I will follow through the example that documentation provides. So, here my code for my Hybrid WK Web View.

public class HybridWebKitWebView : View
{
    Action action;
    public static readonly BindableProperty UriProperty = BindableProperty.Create(
      propertyName: "Uri",
      returnType: typeof(string),
      declaringType: typeof(HybridWebKitWebView),
      defaultValue: default(string));

    public string Uri
    {
        get { return (string)GetValue(UriProperty); }
        set { SetValue(UriProperty, value); }
    }

    public void RegisterAction(Action callback)
    {
        action = callback;
    }

    public void Cleanup()
    {
        action = null;
    }

    public void InvokeAction(string data)
    {
        if (action == null || data == null)
        {
            return;
        }
       action.Invoke(data);
    }
}

Renderer and Delegate

Still from documentation, here’s the code for WK Web View Renderer, It’s pretty much the same, I just get rid some codes because I wanna load from internet not local content.

public class HybridWKWebViewRenderer: ViewRenderer
{
    WKUserContentController userController;

    protected override void OnElementChanged(ElementChangedEventArgs e)
    {
        base.OnElementChanged(e);

        if (Control == null)
        {
            userController = new WKUserContentController();

            var config = new WKWebViewConfiguration { UserContentController = userController };
            var webView = new WKWebView(Frame, config);
            webView.NavigationDelegate = new HybridWKWebViewDelegate(this);
            SetNativeControl(webView);
        }
        if (e.OldElement != null)
        {
            var hybridWebView = e.OldElement as HybridWebKitWebView;
            hybridWebView.Cleanup();
        }
        if (e.NewElement != null)
        {
            Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
        }
    }
}

Next is the Delegate class. The delegate class of WK Web View is actually pretty much the same with Delegate class for UI Web View. So, if you want to migrate from UI Web View to WK Web View, it won’t be too difficult. It just has different class names. Here’s some important methods and their ‘new name’ in WK Web View

  • LoadStarted became DidStartProvisionalNavigation
  • LoadFailed became DidFailProvisionalNavigation
  • LoadingFinished became DidFinishNavigation
  • ShouldStartLoad became DecidePolicy

They don’t just have different names but different parameters as well, so here’s the example of empty Delegate Class of WK Web View.

public class HybridWKWebViewDelegate: WKNavigationDelegate
{
    HybridWKWebViewRenderer hybridWebViewRenderer;
    public HybridWKWebViewDelegate(HybridWKWebViewRenderer _webViewRenderer = null)
    {
        hybridWebViewRenderer = _webViewRenderer ?? new HybridWKWebViewRenderer();
    }

    [Export("webView:didStartProvisionalNavigation:")]
    public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigation navigation)
    {

    }

    [Export("webView:didFailProvisionalNavigation:")]
    public override void DidFailProvisionalNavigation(WKWebView webView, WKNavigation navigation, NSError error)
    {

    }

    [Export("webView:didFinishNavigation:")]
    public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
    {

    }

    [Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
    public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler)
    {

    }
}

Decide Policy

Let’s dive little bit deeper about DecidePolicy Method because it’s works little bit different compare to ShouldStartLoad method. When you load a web page in WK Web View, this method is the first one who get triggered. In this method, you can decide whether you want to allow the web view to load the web page or not.

But to handle that kind of event, first you need to know what Navigation Type that the web page using. There’re six of them and you need to handle the navigation activities based on them. Here’s the simple sample of it.

[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler)
{
    switch (navigationAction.NavigationType)
    {
        case WKNavigationType.BackForward:
            decisionHandler(WKNavigationActionPolicy.Allow);
            break;
        case WKNavigationType.FormResubmitted:
            decisionHandler(WKNavigationActionPolicy.Cancel);
            break;
        case WKNavigationType.FormSubmitted:
            decisionHandler(WKNavigationActionPolicy.Allow);
            break;
        case WKNavigationType.LinkActivated:
            decisionHandler(WKNavigationActionPolicy.Cancel);
            break;
        case WKNavigationType.Other:
            decisionHandler(WKNavigationActionPolicy.Allow);
            break;
        case WKNavigationType.Reload:
            decisionHandler(WKNavigationActionPolicy.Cancel);
            break;
    }
}

From the names, I believe you can guess what kind Navigation Type are them. The only one which little bit unclear maybe the Other type. Well, so far I only meet this type twice. First, when the web view load the url that I set for the first time. So, everything will be started from this type before the web view navigate to other page. The second one I will discuss in the next section.

Handle Button Click Event

For example how to use Delegate class in WK Web View, I will use example of what I’ve done in UI Web View. First is about handling button click event. Here’s how I did it using WK Web View Delegate class. The navigation type I used is Other.

[Export("webView:didFinishNavigation:")]
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
    // find the particular button
    string funcurl = string.Empty;
    funcurl = "var btn1 =  document.getElementsByClassName('btn-primary')[1]; if(btn1 != null){btn1.addEventListener('click', function() { window.location = \"dosomething\"; }); }" +
              "var btn2 =  document.getElementsByClassName('btn-primary')[2]; if(btn2 != null){reject.addEventListener('click', function() { window.location = \"dosomethingelse\"; }); }";

    webView.EvaluateJavaScript("javascript: r(function(){" + funcurl + "});", null);
}

[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler)
{
    switch (navigationAction.NavigationType)
    {

        case WKNavigationType.Other:

            var request = navigationAction.Request;

            if (request.Url.ToString().Contains("dosomething"))
            {
                // do something here
                decisionHandler(WKNavigationActionPolicy.Cancel);
            }
            else if (request.Url.ToString().Contains("dosomethingelse"))
            {
                // do something else here
                decisionHandler(WKNavigationActionPolicy.Cancel);
            }
            break;
        default:
            decisionHandler(WKNavigationActionPolicy.Allow);
            break;
    }
}

Adding Authorization Header

Second example is about Adding Authorization Header. I think adding this header suppose to in every type of navigation. But, in this example I will just add it in two most common navigation types, Other and LinkActivated. Link Activated is navigation type when user clink a link in a web page. So, here’s how it’s done.

[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler)
{
    switch (navigationAction.NavigationType)
    {
        case WKNavigationType.LinkActivated:
            var request = navigationAction.Request;

            if (!request.Headers.ContainsKey(new NSString("Authorization")))
            {
                var copy = request.MutableCopy() as NSMutableUrlRequest;
                var token = "";

                NSMutableDictionary dic = new NSMutableDictionary();
                dic.Add(new NSString("Authorization"), new NSString("Bearer " + token));
                copy.Headers = dic;

                string currentUrl = request.Url.ToString();

                webView.LoadRequest(copy);
                decisionHandler(WKNavigationActionPolicy.Cancel);
            }
            else
            {
                decisionHandler(WKNavigationActionPolicy.Allow);
            }

            break;

        case WKNavigationType.Other:

           request = navigationAction.Request;

            if (!request.Headers.ContainsKey(new NSString("Authorization")))
            {
                var copy = request.MutableCopy() as NSMutableUrlRequest;
                var token = "";

                NSMutableDictionary dic = new NSMutableDictionary();
                dic.Add(new NSString("Authorization"), new NSString("Bearer " + token));
                copy.Headers = dic;

                string currentUrl = request.Url.ToString();

                webView.LoadRequest(copy);
                decisionHandler(WKNavigationActionPolicy.Cancel);
            }
            else
            {
                decisionHandler(WKNavigationActionPolicy.Allow);
            }

             break;
        default:
            decisionHandler(WKNavigationActionPolicy.Allow);
            break;
    }

}

 

Accordion ListView in Xamarin Forms

Accordion ListView or Expanded ListView, a list view that can show another list view inside its list view item, is one of UI components that Xamarin Forms doesn’t have it by default. So, in this post, I want to discuss one of the simplest way to create an accordion list view look alike in Xamarin Forms.

Custom Observable Collection

One property that we need the most to create accordion list view is visibility property with boolean data type. This property will determine wether the children list view is visible or not. So, when user tap to an item in list view, that property have to change. And because it’s only a property in one of the list view items, we gonna need custom observable collection that support that condition. Because regular observable collection won’t be support it. The property will change, but it won’t affect the UI view. I have discussed this custom observable collection in first post, and we gonna need it again in this case.


public class CustomObservableCollection : ObservableCollection
{
    public CustomObservableCollection() { }
    public CustomObservableCollection(IEnumerable items) : this()
    {
        foreach (var item in items)
           this.Add(item);
    }
public void ReportItemChange(T item)
    {
        NotifyCollectionChangedEventArgs args =
        new NotifyCollectionChangedEventArgs(
        NotifyCollectionChangedAction.Replace,
        item,
        item,
        IndexOf(item));
        OnCollectionChanged(args);
    }
}

<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;">&#65279;</span>

This custom observable collection add a method called ReportItemChange that will trigger CollectionEventChanged. So, when we change the visibility property, the UI view will be notified.

Entities

So, for example in this post, I will create list of countries which have list of cities on every country. So, these are entities that I use in this example.


public class City
{
    public string CityName { get; set; }
}

public class Country
{
    public string CountryName { get; set; }
    public bool IsChildrenVisible { get; set; }
    public string ArrowIconSource
    {
        get
        {
           if (IsChildrenVisible)
              return "uparrow.png";
           else
              return "downarrow.png";
        }
    }
    public ObservableCollection Cities { get; set; }
    public int ChildrenRowHeightRequest
    {
        get
        {
            if (Cities != null)
                return Cities.Count * 50;
            else
                return 0;
        }
    }
}

Country, as parent entity, has a visibility property called IsChildrenVisible that will determine whether the cities are showed or not. It also has ChildrenRowHeightRequest property to determine height request for Cities list view.

View Model

After entities, we move on to the view model. In view model we create list of countries that will be bind to the view. As a default, we will set the IsChildrenVisible property to false. And in this view model, we also have method called ShowCities that update that property.


public class AccordionCountriesViewModel : BaseViewModel
{
    private CustomObservableCollection countries;
    public CustomObservableCollection Countries
    {
       get => countries;
       set => SetProperty(ref countries, value);
    }

    public AccordionCountriesViewModel()
    {
       ObservableCollection USACities = new ObservableCollection()
       {
           new City(){ CityName = "Washington DC" },
           new City(){ CityName = "New York" },
           new City(){ CityName = "Los Angeles" }
       };

       ObservableCollection ChinaCities = new ObservableCollection()
       {
           new City(){ CityName = "Beijing" },
           new City(){ CityName = "Shanghai" },
           new City(){ CityName = "Shenzhen" }
       };

       ObservableCollection RussiaCities = new ObservableCollection()
       {
          new City(){ CityName = "Moscow" },
          new City(){ CityName = "St. Peterburg" },
          new City(){ CityName = "Kazan" }
       };

       countries = new CustomObservableCollection()
       {
          new Country(){ CountryName = "USA", IsChildrenVisible = false, Cities = USACities },
          new Country(){ CountryName = "China", IsChildrenVisible = false, Cities = ChinaCities },
          new Country(){ CountryName = "Russia", IsChildrenVisible = false, Cities = RussiaCities },
       };
   }

   public void ShowCities(Country mCountry)
   {
      Country country = Countries.SingleOrDefault(c => c.CountryName == mCountry.CountryName);
      country.IsChildrenVisible = !country.IsChildrenVisible;
      Countries.ReportItemChange(country);
   }
}

Let’s try it out

All preparations are complete. We will create a view which has list view inside another list view. And then we user tap the list view item, it will call method ShowCities in view model.

Screen Shot 2018-02-22 at 6.25.00 AM
And this is how we bind the view model and handle the item tapped event.


public partial class AccordionCountriesPage : ContentPage
{
    AccordionCountriesViewModel viewModel;
    public AccordionCountriesPage()
    {
         InitializeComponent();
         BindingContext = viewModel = new AccordionCountriesViewModel();
    }

    void Handle_ItemTapped(object sender, ItemTappedEventArgs e)
    {
        Country mCountry = (Country)e.Item;
        viewModel.ShowCities(mCountry);
    }
}

Now you can try and run the code.

Credit :

  • Arrow Icons from FlatIcon, Down Arrow icon by Freepik, Right Arrow icon by GraphicsBay, Up Arrow icon by Hadrien

 

UPDATE : Adding Animation to Accordion List View

Adding Authorization Header in Web View

So, this is my second post about web view. Still from my last project, I had to deal with some problems regarding web view. One of them was adding authorization header in web view. When I look up on net, there were so many answers, but most of them didn’t work for me. So, I thought I should share which method that works for me.

Android Web View Renderer

In Android, I simply put the Authorization header on web view renderer. In some forums, there’re some different opinions about where we should put this header, like put it in web chrome client, but those methods didn’t work in my case. So, this is how I added the authorization header in my project.

public class HybridWebViewRenderer : WebViewRenderer
{
    public HybridWebViewRenderer(Context context) : base(context)
    { }
    public HybridWebViewRenderer(){ }

    protected override void OnElementChanged(ElementChangedEventArgs e)
    {
        base.OnElementChanged(e);
        var webView = Control as Android.Webkit.WebView;
        var Token = Settings.AccessToken;

        Dictionary headers = new Dictionary()
        {
            {"Authorization", "Bearer " + Token }
        };
        this.Control.LoadUrl(Control.Url, headers);
    }

}

I put it on OnElementChanged method, as a result, whenever the user navigate to other page in web view, causing the url changed, this method will be fired. So, the header will be added whenever user make new request in web view.

iOS Web View Delegate

Different from it’s Android counter part, we can’t add the Authorization header on web view renderer in iOS, we have to put it on web view delegate. In iOS web view delegate, there’s a method called ShouldStartLoad

Just like its name, this method is fired whenever the web view load new page. So, it’s actually pretty similar with the Android, but the way this method works is little bit different. In Nutshell, what we gonna do in this method are :

  1. Checking if the request has Authorization Header
  2.  If not, Copy the request and then add the header to the copied request.
  3. Cancel the original request without Authorization Header
  4. If the request already has the header, just let it through

So, this how it looks like in real code.

public class HybridWebViewRenderer : WebViewRenderer
{
    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);
        Delegate = new HybridUiWebViewDelegate(this);

        this.ScrollView.DecelerationRate = UIScrollView.DecelerationRateNormal;
        var webView = e.NewElement as HybridWebView;
    }

}

public class HybridUiWebViewDelegate : UIWebViewDelegate
{
    public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType)
    {
        if (!request.Headers.ContainsKey(new NSString("Authorization")))
        {
            var copy = request.MutableCopy() as NSMutableUrlRequest;
            var token = Settings.AccessToken;

            NSMutableDictionary dic = new NSMutableDictionary();
            dic.Add(new NSString("Authorization"), new NSString("Bearer " + token));
            copy.Headers = dic;

           string currentUrl = request.Url.ToString();

           if (currentUrl.ToLower() != StaticVariables.CurrentUrl.ToLower())
           {
               StaticVariables.CurrentUrl = currentUrl;
               StaticVariables.NavigationStack.Add(currentUrl);
           }

           webView.LoadRequest(copy);

           return false;
      }
      return true;
    }
}

Hope this method also works for you.

Nested Checkbox in Xamarin Forms

We already have two posts discussing about check box, but I wanna to take it one step further. So, in this post we’ll discuss one of the most common behavior that a check box has. Let’s say we have a list of check boxes, and every one of them, the check box, also have list of check boxes of their own, or in short, a list of nested check boxes. In this nested check box, when we check the parent box, all the children boxes will be automatically checked as well. On the contrary, when on of child is unchecked, the parent will be automatically unchecked as well. So, we’ll be discussing how that process can be executed in Xamarin forms, started from the custom content view of the check box.

Parent and Child Check Box

Just like the previous post about check box, I created custom content view of check box. Because we gonna have two different check box, so I created two different content view, one for parent, one for the child. And to distinguish between those two check boxes, I put the box in the left for parent, and in the right for the child. Here’s how the parent check box content view looks like

NestedCode1

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ChildrenCheckBox : ContentView, INotifyPropertyChanged
{
    public ChildrenCheckBox()
    {
        InitializeComponent();
    }

    public static BindableProperty IconSourceProperty =
        BindableProperty.Create(nameof(IconSource), typeof(string), typeof(ChildrenCheckBox), null, BindingMode.TwoWay);

    public string IconSource
    {
        get => (string)GetValue(IconSourceProperty);
        set => SetValue(IconSourceProperty, value);
    }

    public static BindableProperty MenuTitleProperty =
         BindableProperty.Create(nameof(MenuTitle), typeof(string), typeof(ChildrenCheckBox), null, BindingMode.TwoWay);

    public string MenuTitle
    {
        get => (string)GetValue(MenuTitleProperty);
        set => SetValue(MenuTitleProperty, value);
    }
}

And the following code is the Parent Check box code.

NestedCode2

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ParentCheckBox : ContentView, INotifyPropertyChanged
{
    public ParentCheckBox()
    {
        InitializeComponent();
    }

    public static BindableProperty IconSourceProperty =
        BindableProperty.Create(nameof(IconSource), typeof(string), typeof(ParentCheckBox), null, BindingMode.TwoWay);

    public string IconSource
    {
        get => (string)GetValue(IconSourceProperty);
        set => SetValue(IconSourceProperty, value);
    }

    public static BindableProperty MenuTitleProperty =
        BindableProperty.Create(nameof(MenuTitle), typeof(string), typeof(ParentCheckBox), null, BindingMode.TwoWay);

    public string MenuTitle
    {
        get => (string)GetValue(MenuTitleProperty);
        set => SetValue(MenuTitleProperty, value);
    }
}

Parent and Children View Model

After done with the UI, now we’re talking about the business process, about the view model. Just like the content view, we also have two kind view model, one for the parent, and one for the children. Let’s started with the parent. In the parent view model, beside all standard methods, we gonna have two main method. First, SetParent. So, we’ll receive list of children check boxes as parameter and then in this method we’ll set the parent of this check boxes to the current parent view model. The second method is UpdateChildren, is where we set the all children checked when the parent is also checked.

public class ParentCheckboxViewModel : BaseViewModel
{
    protected bool isChecked;
    public bool IsChecked
    {
        get => isChecked;
        set
        {
            SetProperty(ref isChecked, value);
            UpdateChildren();
            UpdateImageSource();
        }
    }

    protected string menuTitle;
    public String MenuTitle
    {
        get => menuTitle;
        set => SetProperty(ref menuTitle, value);
    }

    protected string iconSource;
    public string IconSource
    {
        get => iconSource;
        set => SetProperty(ref iconSource, value);
    }

    protected ObservableCollection children;
    public ObservableCollection Children
    {
        get => children;
        set => SetProperty(ref children, value);
    }

    protected ChildrenCheckboxViewModel selectedChild;
    public ChildrenCheckboxViewModel SelectedChild
    {
        get => selectedChild;
        set => SetProperty(ref selectedChild, value);
    }

    public ICommand ChildTappedCommand { get; set; }

    public ParentCheckboxViewModel(ObservableCollection _children)
    {
        this.Children = _children;
        ChildTappedCommand = new Command(ChildTapped);
        IsChecked = false;
        SetParent();
    }

    private void ChildTapped()
    {
        foreach(ChildrenCheckboxViewModel child in Children)
        {
            if (child.MenuTitle == SelectedChild.MenuTitle)
                child.IsChecked = !child.IsChecked;
        }
    }

    protected void SetParent()
    {
        foreach(ChildrenCheckboxViewModel child in Children)
        {
            child.Parent = this;
            child.IsChecked = false;
        }
    }

    protected void UpdateChildren()
    {
        if(IsChecked)
        {
            foreach(ChildrenCheckboxViewModel child in Children)
            {
                child.IsChecked = true;
            }
        }
    }

    protected void UpdateImageSource()
    {
        if (IsChecked)
        {
            IconSource = "CheckBox.png";
        }
        else
        {
            IconSource = "CheckBoxEmpty.png";
        }
    }
}

In Children view model, beside all the basic methods, we have one important method, UpdateParent. In this method, we’ll uncheck the parent if the current children is getting unchecked by user.

public class ChildrenCheckboxViewModel : BaseViewModel
{
    protected bool isChecked;
    public bool IsChecked
    {
        get => isChecked;
        set
        {
            SetProperty(ref isChecked, value);
            UpdateImageSource();
            UpdateParent();
        }
    }

    protected string menuTitle;
    public String MenuTitle
    {
        get => menuTitle;
        set => SetProperty(ref menuTitle, value);
    }

    protected string iconSource;
    public string IconSource
    {
        get => iconSource;
        set => SetProperty(ref iconSource, value);
    }

    protected ParentCheckboxViewModel parent;
    public ParentCheckboxViewModel Parent
    {
        get => parent;
        set => SetProperty(ref parent, value);
    }

    protected void UpdateImageSource()
    {
        if (IsChecked)
        {
            IconSource = "CheckBox.png";
        }
        else
        {
            IconSource = "CheckBoxEmpty.png";
        }
    }

    protected void UpdateParent()
    {
        if (!IsChecked)
        {
            Parent.IsChecked = false;
        }
    }
}

Let’s try it out

Now that we’ve finished all preparation, let’s put all of them in a real case. So, I’ve created a simple program that utilize the parent and children check box. Here’s the code.

NestedCode3

 

public class NestedCheckboxViewModel: BaseViewModel
{
    protected ObservableCollection listChildren1;
    public ObservableCollection ListChildren1
    {
        get => listChildren1;
        set => SetProperty(ref listChildren1, value);
    }

    protected ObservableCollection listChildren2;
    public ObservableCollection ListChildren2
    {
        get => listChildren2;
        set => SetProperty(ref listChildren2, value);
    }

    protected ObservableCollection listChildren3;
    public ObservableCollection ListChildren3
    {
        get => listChildren3;
        set => SetProperty(ref listChildren3, value);
    }

    protected ObservableCollection listChildren4;
    public ObservableCollection ListChildren4
    {
        get => listChildren4;
        set => SetProperty(ref listChildren4, value);
    }

    protected ObservableCollection listChildren5;
    public ObservableCollection ListChildren5
    {
        get => listChildren5;
        set => SetProperty(ref listChildren5, value);
    }

    protected ObservableCollection listParent;
    public ObservableCollection ListParent
    {
        get => listParent;
        set => SetProperty(ref listParent, value);
    }

    protected ParentCheckboxViewModel selectedParent;
    public ParentCheckboxViewModel SelectedParent
    {
        get => selectedParent;
        set => SetProperty(ref selectedParent, value);
    }

    public ICommand ParentTappedCommand { get; set; }

    public NestedCheckboxViewModel()
    {
        ListChildren1 = new ObservableCollection()
        {
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 1.1" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 1.2" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 1.3" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 1.4" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 1.5" },
        };

        ListChildren2 = new ObservableCollection()
        {
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 2.1" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 2.2" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 2.3" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 2.4" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 2.5" },
        };

        ListChildren3 = new ObservableCollection()
        {
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 3.1" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 3.2" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 3.3" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 3.4" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 3.5" },
        };

        ListChildren4 = new ObservableCollection()
        {
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 4.1" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 4.2" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 4.3" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 4.4" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 4.5" },
        };

        ListChildren5 = new ObservableCollection()
        {
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 5.1", },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 5.2" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 5.3" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 5.4" },
            new ChildrenCheckboxViewModel(){ MenuTitle = "Child CheckBox 5.5" },
        };

        ListParent = new ObservableCollection()
        {
            new ParentCheckboxViewModel(ListChildren1){ MenuTitle = "Parent Checkbox 1" },
            new ParentCheckboxViewModel(ListChildren2){ MenuTitle = "Parent Checkbox 2" },
            new ParentCheckboxViewModel(ListChildren3){ MenuTitle = "Parent Checkbox 3" },
            new ParentCheckboxViewModel(ListChildren4){ MenuTitle = "Parent Checkbox 4" },
            new ParentCheckboxViewModel(ListChildren5){ MenuTitle = "Parent Checkbox 5" },
        };

        ParentTappedCommand = new Command(ParentTapped);
    }

    private void ParentTapped()
    {
        foreach(ParentCheckboxViewModel parent in ListParent)
        {
            if (parent.MenuTitle == SelectedParent.MenuTitle)
                parent.IsChecked = !parent.IsChecked;
        }
    }
}

If you run the code, you’ll have a list of nested check boxes. And then if you check one of the parent, all the children boxes will be checked, and if you uncheck one of the children, the parent will be automatically uncheck.

Credit:

  • Check Box Icon from FlatIcon, Checked Check Box icon by Google, Empty Check Box icon by Dave Gandy
  • Flow List View by Daniel Luberda (github)

Sample Code is available in my Github repo

Handle Button Click Event on Web View

In my last project, I was required to load a web view from internet. It was piece of cake, at least what it look like at first. But then it wasn’t that simple anymore when I need to add listener event in C# based from what user did in web view. Let say, the user click a button, it would do whatever it supposed to do in web, but I also had to do something accordingly in my xamarin app based on what button user just clicked. I wasn’t sure if that was even possible, but after wandering all day long in stackoverflow, I found solution for both Android and iOS.

Android Web View Renderer

We move to Android project first. In this project we need create three classes. First, of course the renderer of our web view. Second is web view client, this where we gonna inject our event to html, in this case we will inject it to a button. And then the third is web chrome client where we’ll put event listener to our injected event.

Starting with the renderer, all we need to do is set web view client and web chrome client to our own web view client and chrome client.

 

public class HybridWebViewRenderer : WebViewRenderer
{
    public HybridWebViewRenderer(Context context) : base(context)
    { }
    public HybridWebViewRenderer(){ }

    protected override void OnElementChanged(ElementChangedEventArgs e)
    {
        base.OnElementChanged(e);

        Control.Settings.JavaScriptEnabled = true;
        var webView = Control as Android.Webkit.WebView;

        this.Control.SetWebViewClient(new HybridWebViewClient());
        this.Control.SetWebChromeClient(new HybridWebChromeClient());

        this.Control.LoadUrl(Control.Url);
    }
}

 

Then in web view client, basically what we gonna do is, find the particular button we want and then inject the event. The event is, of course, button click event and we gonna make it show up an alert. Yes, alert! Not just any alert, but an alert with specific keyword that we can recognize it later in web chrome client when we adding event listener.  For this example, I will use ‘dosomething’ and ‘dosomethingelse’ as our keywords. Let see how it looks like.

public class HybridWebViewClient : WebViewClient
{

    public override async void OnPageFinished(WebView view, string url)
    {
        base.OnPageFinished(view, url);

        int i = 10;
        while (view.ContentHeight == 0 && i-- > 0)
            await Task.Delay(1000);
        // find the particular button
        string funcurl = "var btn1 = document.getElementsByClassName('btn-primary')[1]; if(btn1 != null){btn1.addEventListener('click', function() { alert('dosomething'); }); }" +
                         "var btn2 = document.getElementsByClassName('btn-primary')[2]; if(btn2 != null){btn2.addEventListener('click', function() { alert('dosomethingelse'); }); }";
        view.LoadUrl("javascript: r(function(){" + funcurl + ");
        break;

    }
}

In web chrome client, we’ll filter any alert from the html page to find out if any of our alert has beed fired. We’ll override OnJsAlert method, do some process we suppose to do, and then cancel the result and  return it with true value. By doing that it means we won’t show the alert to user because it’s unnecessary for them to see our alert.

public class HybridWebChromeClient : WebChromeClient
{
    public override bool OnJsAlert(WebView view, string url, string message, JsResult result)
    {
        if(message.Contains("doseomething"))
        {
            // do something here
            result.Cancel();
            return true;
        }
        else if(message.Contains("dosomethingelse"))
        {
            // do something else here
            result.Cancel();
            return true;
        }
       return base.OnJsAlert(view, url, message, result);
    }
}

iOS Web View Renderer

In iOS, the process is slightly simpler, because we only need two classes, first the web view renderer class, and the of course the web view delegate class. Just like the Android project, all we need to do in renderer is just setting the delegate class to our own delegate class and the let the delegate class do all the job. This is how the renderer looks like.

public class HybridWebViewRenderer : WebViewRenderer
{
    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);
        Delegate = new HybridUiWebViewDelegate(this);

        this.ScrollView.DecelerationRate = UIScrollView.DecelerationRateNormal;
        var webView = e.NewElement as HybridWebView;

    }
}

Unlike Android, in iOS we don’t have web chrome client to catch javascript alert event, so we need to do something else. All we have is just a delegate class and all its override methods. But among those methods, there’s one method that will be very useful in situation like this. That method called ShouldStartLoad. This method is fired when user start navigating to new url and we can decide programmatically what the app gonna do when it happens. So, what we gonna do is, make the button load certain url, and then we catch the event in ShouldStartLoad method, do what we suppose to there and then cancel the request so web view won’t actually load our url. The url, of course, is a fake url that contain our keyword that we can identify, just like what we did with alert in Android.

public class HybridUiWebViewDelegate : UIWebViewDelegate
{
    HybridWebViewRenderer hybridWebViewRenderer;
    public HybridUiWebViewDelegate(HybridWebViewRenderer _webViewRenderer = null)
    {
        hybridWebViewRenderer = _webViewRenderer ?? new HybridWebViewRenderer();
    }
public override async void LoadingFinished(UIWebView webView)
    {
        var wv = hybridWebViewRenderer.Element as HybridWebView;
        if (wv != null)
        {
           await Task.Delay(100);// wait here till content is rendered

           // find the particular button
           string funcurl = string.Empty;
           funcurl = "var btn1 =  document.getElementsByClassName('btn-primary')[1]; if(btn1 != null){btn1.addEventListener('click', function() { window.location = \"dosomething\"; }); }" +
                     "var btn2 =  document.getElementsByClassName('btn-primary')[2]; if(btn2 != null){reject.addEventListener('click', function() { window.location = \"dosomethingelse\"; }); }";

           webView.EvaluateJavascript("javascript: r(function(){" + funcurl + "});");

    }

    public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType)
    {
        if(request.Url.ToString().Contains("dosomething"))
        {
            // do something here
            return false;
        }
        else if(request.Url.ToString().Contains("dosomethingelse"))
        {
            // do something else here
            return false;
       }

      return true;
    }
}

Credit: