XfQaD

#XFQaD: Compile XAML without code behind in Xamarin.Forms

#XFQaD: Compile XAML without code behind in Xamarin.Forms

How I discovered this #XFQaD

When I was reorganizing the application resources on my current side project, I decided to create some thematically divided ResourceDictionary files. This has led me to do a quick research on Xamarin.Forms resource dictionaries.

If you’re doing this research, you will stumble upon this post from 2018 (!), where the hint to the magic I’ll show you soon was hidden.

Until now, I only used this with ResourceDictionary files. Maybe it will be helpful also for other XAML resources like controls (I will try that in the future).

How to make XAML only files compile

Add a new XAML file (sample is still ResourceDictionary) to your project:

Add_ResourceDictionary

Delete the code behind file, and add your XAML code. Before hitting the Build button, add this line immediately after the .xml file header:

<?xaml-comp compile="true" ?>

This line is where the magic happens. It tells the compiler to include the XAML file in the Build, pretty much like it does with XAML compile attribute in the code behind file.

Conclusion

The only place where I found this hint was the blog post mentioned above. Neither the docs on XAML Compilation nor the docs on resource dictionaries are mentioning this trick. It somehow went quietly from the nightly builds into production code.

Using this trick, we are able to have a more clean project structure. As always, I hope this post will be helpful for some of you.

Until the next post, happy coding!

Posted by msicc in Dev Stories, 9 comments
#XfQaD: Limit maximum lines of Label and indicate text truncation

#XfQaD: Limit maximum lines of Label and indicate text truncation

The problem

Xamarin.Forms.Labelhas a common set of properties we can use to configure how our text is shown. However, it does miss a property to limit the maximum of text lines and a proper indication of eventually truncated text. Knowing that UWP, Android and iOS have working and easy-to-use implementations on their platform controls used for the Xamarin.Forms.Label, there is only one solution to the problem: exposing a custom control and its platform renderers. That’s what we are going to do in this #XfQaD.

XfMaxLines Label implementation

Let’s have a look at the Xamarin.Forms implementation first. I am just adding a BindablePropertyto a derived class implementation to define the maximum of lines I want to see:

public class XfMaxLinesLabel : Label
{
    public XfMaxLinesLabel(){ }

    public static BindableProperty MaxLinesProperty = BindableProperty.Create("MaxLines", typeof(int), typeof(XfMaxLinesLabel), int.MaxValue, BindingMode.Default);

    public int MaxLines
    {
        get => (int)GetValue(MaxLinesProperty);
        set => SetValue(MaxLinesProperty, value);
    }
}

UWP

The UWP renderer uses the TextBlock properties MaxLinesto limit the amount of shown lines, while the TextTrimmingproperty is set to ellipsize the last word before reaching the limit. The implementation is pretty straight forward:

[assembly: ExportRenderer(typeof(XfMaxLinesLabel), typeof(XfMaxLinesLabelRenderer))]
namespace MaxLinesLabel.UWP
{
    public class XfMaxLinesLabelRenderer : LabelRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);

            if (((XfMaxLinesLabel)e.NewElement).MaxLines == -1 || ((XfMaxLinesLabel)e.NewElement).MaxLines == int.MaxValue)
                return;

            this.Control.MaxLines = ((XfMaxLinesLabel)e.NewElement).MaxLines;
            this.Control.TextTrimming = Windows.UI.Xaml.TextTrimming.WordEllipsis;
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName == XfMaxLinesLabel.MaxLinesProperty.PropertyName)
            {
                if (((XfMaxLinesLabel)this.Element).MaxLines == -1 || ((XfMaxLinesLabel)this.Element).MaxLines == int.MaxValue)
                    return;

                this.Control.MaxLines = ((XfMaxLinesLabel)this.Element).MaxLines;
                this.Control.TextTrimming = Windows.UI.Xaml.TextTrimming.WordEllipsis;
            }
        }
    }
}

Android

The Android implementation uses the MaxLinesproperty of Android’s TextView control to limit the maximum visible lines. The Ellipsizeproperty is used to show the three dots for truncation at the end of the last visible line. Once again, pretty straight forward.

[assembly: ExportRenderer(typeof(XfMaxLinesLabel), typeof(XfMaxLinesLabelRenderer))]
namespace MaxLinesLabel.Droid
{
    class XfMaxLinesLabelRenderer : LabelRenderer
    {
        public XfMaxLinesLabelRenderer(Context context) : base(context)
        {
        }


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

            if (((XfMaxLinesLabel)e.NewElement).MaxLines == -1 || ((XfMaxLinesLabel)e.NewElement).MaxLines == int.MaxValue)
                return;
            this.Control.SetMaxLines(((XfMaxLinesLabel)e.NewElement).MaxLines);
            this.Control.Ellipsize = TextUtils.TruncateAt.End;
        }


        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName == XfMaxLinesLabel.MaxLinesProperty.PropertyName)
            {
                if (((XfMaxLinesLabel)this.Element).MaxLines == -1 || ((XfMaxLinesLabel)this.Element).MaxLines == int.MaxValue)
                    return;
                this.Control.SetMaxLines(((XfMaxLinesLabel)this.Element).MaxLines);
                this.Control.Ellipsize = TextUtils.TruncateAt.End;
            }
        }
    }
}

iOS

Like Android and Windows, also the UILabel control on iOS has a MaxLinesproperty. You’re right, we’ll use this one to limit the count of visible lines. Using the LineBreakModeproperty, we can automate the text truncation indicator equally easy as on Android and UWP:

[assembly: ExportRenderer(typeof(XfMaxLinesLabel), typeof(XfMaxLinesLabelRenderer))]
namespace MaxLinesLabel.iOS
{
    public class XfMaxLinesLabelRenderer : LabelRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);

            if (((XfMaxLinesLabel)e.NewElement).MaxLines == -1 || ((XfMaxLinesLabel)e.NewElement).MaxLines == int.MaxValue)
                return;

            this.Control.Lines = ((XfMaxLinesLabel)e.NewElement).MaxLines;
            this.Control.LineBreakMode = UILineBreakMode.TailTruncation;
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName == XfMaxLinesLabel.MaxLinesProperty.PropertyName)
            {
                if (((XfMaxLinesLabel)this.Element).MaxLines == -1 || ((XfMaxLinesLabel)this.Element).MaxLines == int.MaxValue)
                    return;

                this.Control.Lines = ((XfMaxLinesLabel)this.Element).MaxLines;
                this.Control.LineBreakMode = UILineBreakMode.TailTruncation;
            }
        }
    }
}

Conclusion

As you can see, it is pretty easy to create a line limited, truncation indicating custom Label for your Xamarin.Forms app. The implementation is done in a few minutes, but it makes writing your cross platform app a bit easier. I don’t know why this is not (yet) implemented in current Xamarin.Forms iterations, but I do hope they’ll do so to further reduce the number of needed custom renderers.

In the meantime, feel free to check the sample code on GitHub and use it in your apps. As always, I hope this post is helpful for some of you.

Happy coding, everyone!

Posted by msicc in Android, Dev Stories, iOS, Windows, Xamarin, 1 comment
#XfQaD: read package identity and version from platform project with Xamarin.Forms

#XfQaD: read package identity and version from platform project with Xamarin.Forms

All of my apps, no matter on which platform, need to know their version number (for displaying in app) and their package identifier (for opening them in their store). If you are following around for some time, you know I prefer own solutions in a lot of use cases – that’s why I created another #XfQaD for this task, even if there are plugins around for that.

The concept

Once again, I am utilizing the built-in Xamarin.FormsDependencyService for this task. So the concept is pretty easy:

  • interface that dictates the available options
  • platform implementations that execute the code and return the values I want

Let’s have a look at

The interface

namespace PackageInfo
{
    public interface IAppDataHelper
    {
        string GetApplicationPackageName();

        string GetApplicationVersion();

        string GetApplicationVersionName();
    }
}

The interface provides three string returning methods. As the versioning is different on all three platforms, I return two different version strings to cover that fact.

UWP implementation

The UWP implementation uses the Package class, which provides access to the package information, including those we are interested in. As the UWP has just one version type, it returns the same value for version and version name:

using PackageInfo.UWP;
using Windows.ApplicationModel;
using Xamarin.Forms;

[assembly: Dependency(typeof(AppDataHelper))]
namespace PackageInfo.UWP
{
    public class AppDataHelper : IAppDataHelper
    {
        private Package _package;

        public AppDataHelper()
        {
            _package = Package.Current;
        }

        public string GetApplicationPackageName()
        {
            return _package.Id.FamilyName;
        }

        public string GetApplicationVersion()
        {
            return  $"{_package.Id.Version.Major}.{_package.Id.Version.Minor}.{_package.Id.Version.Build}.{_package.Id.Version.Revision}";
        }

        public string GetApplicationVersionName()
        {
            return $"{_package.Id.Version.Major}.{_package.Id.Version.Minor}.{_package.Id.Version.Build}.{_package.Id.Version.Revision}";
        }
    }
}

Android implementation

The Android implementation uses the PackageManager class, which uses the GetPackageInfo method to provide the information about the currently installed package. As Android has a different version structure (see more info here), it returns two different strings for version and version name:

using Android.Content;
using Android.Content.PM;
using PackageInfo.Droid;
using Xamarin.Forms;

[assembly: Dependency(typeof(AppDataHelper))]
namespace PackageInfo.Droid
{
    public class AppDataHelper : IAppDataHelper
    {
        private readonly Context _context;
        private readonly PackageManager _packageManager;
        public AppDataHelper()
        {
            _context = Android.App.Application.Context;
            _packageManager = _context.PackageManager;
        }

        public string GetApplicationPackageName()
        {
            return _context.PackageName;
        }

        public string GetApplicationVersion()
        {
            return _packageManager.GetPackageInfo(_context.PackageName, 0).VersionCode.ToString();
        }

        public string GetApplicationVersionName()
        {
            return _packageManager.GetPackageInfo(_context.PackageName, 0).VersionName;
        }
    }
}

iOS implementation

Even iOS provides a way to get the package identity and version. It uses the NSBundle.MainBundle implementation to get the info. To get those we are interested in, we just query the InfoDictionarythe MainBundleholds:

using Foundation;
using PackageInfo.iOS;
using Xamarin.Forms;

[assembly: Dependency(typeof(AppDataHelper))]
namespace PackageInfo.iOS
{
    public class AppDataHelper : IAppDataHelper
    {
        private readonly NSDictionary _infoDictionary;

        public AppDataHelper()
        {
            _infoDictionary = NSBundle.MainBundle.InfoDictionary;
        }

        public string GetApplicationPackageName()
        {
            return _infoDictionary[new NSString("CFBundleIdentifier")].ToString();
        }

        public string GetApplicationVersion()
        {
            var appVersionString = NSBundle.MainBundle.ObjectForInfoDictionary("CFBundleShortVersionString").ToString();
            var appBuildNumber = NSBundle.MainBundle.ObjectForInfoDictionary("CFBundleVersion").ToString();

            return $"{appVersionString}.{appBuildNumber}";
        }

        public string GetApplicationVersionName()
        {
            return NSBundle.MainBundle.ObjectForInfoDictionary("CFBundleShortVersionString").ToString();
        }
    }
}

That’s all it takes to get your application’s package identity and version. You can have a look yourself in this GitHub sample and play around with it. If you want to extend and read more information, the above implementation is easily expandable.

As always, I hope this post will be helpful for some of you. Happy coding, everyone!

Posted by msicc in Android, Dev Stories, iOS, Windows, 0 comments
#XfQaD: Using ProgressRing for UWP and keep a single activity indicator API in Xamarin.Forms

#XfQaD: Using ProgressRing for UWP and keep a single activity indicator API in Xamarin.Forms

I recently recognized that I have written quite a few “Quick-and-Dirty”-solutions for Xamarin Forms that run well for most scenarios. There is a chance they will not work in all and every scenario, and therefore may need some more work at a later point. I am sharing them to bring the ideas to the community, and often these “QaDs” are enough one needs to solve one particular problem. As they do not fit well into my other series I am writing (“Xamarin Forms, the MVVMLight toolkit and I” for example), I gave them their own tag: #XfQaD.

The scenario

The first scenario may not be important to a lot of people, but I wanted to solve this rather small one quickly for me. The UWP implementation of Xamarin Forms’ ActivityIndicatoruses the ProgressBarinstead of a ring indicator like Android and iOS:

default activity indicator screenshots

image credits: Xamarin

While this will be fine in most cases, I had the problem of limited space, and I wanted a similar UI on all three platforms for that app. The UWP has a perfect matching native control, so I implemented my own ActivityIndicatorimplementation called LoadingRing. It uses the ProgressRingcontrol on UWP and keeps the default ActivityIndicatoron all other platforms. I also wanted to keep a single API I can use throughout my app without always thinking about the platform usings.

Implementation structure

The QaD-solution I came up with has a simple structure:

  • base class implementation providing the API for the custom renderer on UWP
  • the custom renderer in the UWP project
  • a catalyst class that unifies the different implementations

Let’s have a look into the code:

API for the custom renderer

The API for the custom render has the same properties as the Xamarin.Forms.ActivityIndicator has. They are BindableProperties, so they are perfectly prepared for MVVM. Here is all that we need in there:

public class ProgressRingIndicator : View
{
    public ProgressRingIndicator()
    {
        if (Device.RuntimePlatform != Device.UWP)
        {
            throw new NotSupportedException($"{nameof(ProgressRingIndicator)} is just for UWP, use {nameof(ActivityIndicator)} on {Device.RuntimePlatform}");
        }
    }

    public static readonly BindableProperty ColorProperty = BindableProperty.Create("Color", typeof(Color), typeof(ProgressRingIndicator), default(Color), BindingMode.Default);

    public Color Color
    {
        get => (Color)GetValue(ColorProperty);
        set => SetValue(ColorProperty, value);
    }

    public static readonly BindableProperty IsRunningProperty = BindableProperty.Create("IsRunning", typeof(bool), typeof(ProgressRingIndicator), default(bool), BindingMode.Default);

    public bool IsRunning
    {
        get => (bool)GetValue(IsRunningProperty);
        set => SetValue(IsRunningProperty, value);
    }
}

If you need more info on the implementation of BindableProperties, just have a look at the Xamarin.Forms documentation. Basically, they are what Windows developers know as DependencyProperty.

The renderer and two little extensions

One of the great things of Xamarin.Forms is the ability to use native controls via custom renderers. It makes implementing platform specific code easy while keeping the amount of shared code pretty high. As I know that also beginners read my posts, here is once again a link to the Xamarin documentation. Let’s have a look at the two little extension I mentioned first, as they make our renderer code more readable.

Xamarin.Forms and the UWP have different implementations of the Color structure (Xamarin | UWP). In order to connect them, we need to translate the Xamarin.Forms.Colorto a Windows.UI.Colorand pass the later one to a SolidColorBrushto give the ProgressRingthe color we want. The implementation is pretty straight forward:

public static class Extensions
{
    public static Color ToUwPColor(this Xamarin.Forms.Color color)
    {
        return Color.FromArgb(
            Convert.ToByte(color.A * 255),
            Convert.ToByte(color.R * 255),
            Convert.ToByte(color.G * 255),
            Convert.ToByte(color.B * 255));
    }

    public static SolidColorBrush ToUwpSolidColorBrush(this Xamarin.Forms.Color color)
    {
        return new SolidColorBrush(color.ToUwPColor());
    }
}

The Windows.UI.Color.FromArgbmethod is accepting only bytes as value, so we have to convert the Xamarin.Forms.Colorchannels to bytes and pass them along. With these extensions, we will have the color setting in the renderer in just one single line.

So let’s get finally to the renderer:

[assembly: ExportRenderer(typeof(ProgressRingIndicator), typeof(ProgressRingIndicatorRenderer))]
namespace [YourNameSpaceHere].UWP
{
    public class ProgressRingIndicatorRenderer : ViewRenderer<ProgressRingIndicator, ProgressRing>
    {
        private ProgressRing _progressRing;

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

            if (this.Control != null) return;

            _progressRing = new ProgressRing();

            if (e.NewElement != null)
            {
                _progressRing.IsActive = this.Element.IsRunning;
                _progressRing.Visibility = this.Element.IsRunning ? Visibility.Visible : Visibility.Collapsed;
                var xfColor = this.Element.Color;
                _progressRing.Foreground = xfColor.ToUwpSolidColorBrush();

                SetNativeControl(_progressRing);
            }
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName == nameof(ProgressRingIndicator.Color))
            {
                _progressRing.Foreground = this.Element.Color.ToUwpSolidColorBrush();
            }

            if (e.PropertyName == nameof(ProgressRingIndicator.IsRunning))
            {
                _progressRing.IsActive = this.Element.IsRunning;
                _progressRing.Visibility = this.Element.IsRunning ? Visibility.Visible : Visibility.Collapsed;
            }

            if (e.PropertyName == nameof(ProgressRingIndicator.WidthRequest))
            {
                _progressRing.Width = this.Element.WidthRequest > 0 ? this.Element.WidthRequest : 20;
                UpdateNativeControl();
            }

            if (e.PropertyName == nameof(ProgressRingIndicator.HeightRequest))
            {
                _progressRing.Height = this.Element.HeightRequest > 0 ? this.Element.HeightRequest : 20;
                UpdateNativeControl();
            }
        }
    }
}

ViewRender<TElement, TNativeElement>enables us to use native controls in Xamarin.Forms, so we’re deriving from it. Like any custom renderer, our renderer overrides the OnElementChangedmethod to set the initial rendering values. The Controlproperty is the native control implementation, while the Xamarin.Forms control comes in via ElementChangedEventArgs.NewElementproperty, but you can also use the Elementproperty in most cases.

In order to react to changes of the different properties of the control, we need to handle the OnElementPropertyChangedevent. This event can fire quite often, so it makes absolutely sense to filter code execution to run only when a specific property change happens.

Bring back my single API

With the code above, I am already able to use the ProgressRingIndicator. However, I have to use the On<T>platform implementation everywhere to do so. As I already mentioned before, I want to have a single API when I use the control. To solve this problem, I created a catalyst class:

public class LoadingRing : ContentView
{
    public readonly ProgressRingIndicator UwpProgressRing;
    public readonly ActivityIndicator ActivityIndicator;

    public LoadingRing()
    {
        switch (Device.RuntimePlatform)
        {
            case Device.UWP:
                this.UwpProgressRing = new ProgressRingIndicator();
                this.UwpProgressRing.HorizontalOptions = LayoutOptions.FillAndExpand;
                this.UwpProgressRing.VerticalOptions = LayoutOptions.FillAndExpand;
                this.Content = this.UwpProgressRing;
                break;
            default:
                this.ActivityIndicator = new ActivityIndicator();
                this.ActivityIndicator.HorizontalOptions = LayoutOptions.FillAndExpand;
                this.ActivityIndicator.VerticalOptions = LayoutOptions.FillAndExpand;
                this.Content = this.ActivityIndicator;
                break;
        }

        SizeChanged += LoadingRing_SizeChanged;

    }

    private void LoadingRing_SizeChanged(object sender, EventArgs e)
    {
        switch (Device.RuntimePlatform)
        {
            case Device.UWP:
                this.UwpProgressRing.HeightRequest = this.HeightRequest;
                this.UwpProgressRing.WidthRequest = this.WidthRequest;
                break;
            default:
                this.ActivityIndicator.HeightRequest = this.HeightRequest;
                this.ActivityIndicator.WidthRequest = this.WidthRequest;
                break;
        }
    }

    public static readonly BindableProperty ColorProperty = BindableProperty.Create("Color", typeof(Color), typeof(LoadingRing), default(Color), BindingMode.Default, propertyChanged: OnColorPropertyChanged);

    private static void OnColorPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        if (bindable is LoadingRing current)
        {
            switch (Device.RuntimePlatform)
            {
                case Device.UWP:
                    if (current.UwpProgressRing != null) current.UwpProgressRing.Color = (Color)newvalue;
                    break;
                default:
                    if (current.ActivityIndicator != null) current.ActivityIndicator.Color = (Color)newvalue;
                    break;
            }
        }
    }

    public Color Color
    {
        get => (Color)GetValue(ColorProperty);
        set => SetValue(ColorProperty, value);
    }

    public static readonly BindableProperty IsRunningProperty = BindableProperty.Create("IsRunning", typeof(bool), typeof(LoadingRing), default(bool), BindingMode.Default, propertyChanged: OnIsRunningChanged);

    private static void OnIsRunningChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        if (bindable is LoadingRing current)
        {
            switch (Device.RuntimePlatform)
            {
                case Device.UWP:
                    if (current.UwpProgressRing != null) current.UwpProgressRing.IsRunning = (bool)newvalue;
                    break;
                default:
                    if (current.ActivityIndicator != null) current.ActivityIndicator.IsRunning = (bool)newvalue;
                    break;
            }
        }
    }

    public bool IsRunning
    {
        get => (bool)GetValue(IsRunningProperty);
        set => SetValue(IsRunningProperty, value);
    }

}

The implementation derives from ContentView. Depending on the platform my app is running, I am using my custom implementation of the ProgressRingIndicatorcontrol or the default Xamarin.Forms.ActivityIndicator to set the Contenton it. It is also important to handle the SizeChangedevent properly, otherwise the control will never be resized. As the custom implementation before, this catalyst exposes the same properties as the ActivityIndicator, so it is very easy to replace all existing places where I use the default control with it.

That’s it, we have a QaD-implementation that makes it easier to have a similar activity-indicating UI across platforms now. If you want to see it in action, there is a sample available on GitHub. As always, I hope this post is helpful for some of you.

Happy Coding, everyone!

Posted by msicc in Dev Stories, Windows, Xamarin, 3 comments