#Xamarin.Forms Simple Dependency Injection (very simple #IoC)

2019-08-01 09_05_23-injection - Google SearchYou should use Dependency Injection as a design principle (aka IoC).  Here’s why. But you are here because you already want to and understand the need and benefits. Here’s how to in Xamarin Forms, simply.

First, go to NuGet and install Simple Injector into the main project of your solution. I’ve used this on projects with hundreds of thousands of uses and it works brilliantly in Xamarin.

We will need an Interface file and a class that implements that Interface:

    public interface IAnimal
    {
	    bool CanEatGrass();
    }
 
    public class DogIAnimal
    {
	    public bool CanEatGrass()
	    {
		    return false;
	    }
    }

Add a public static class to your App.xaml.cs so that you can access your IoC:

private static Container ioCContainer = new SimpleInjector.Container();
public static Container IoCContainer
{
	get => ioCContainer;
	set => ioCContainer = value;
}

Go to your App.xaml.cs file’s constructor and tell Simple Injector what kind of animal to get when an animal is requested:

App.IoCContainer.Register<IAnimalDog>(Lifestyle.Transient);


Now we are all wired up, let’s use it.

In your Pages.xaml.cs files’ constructors you usually create your model, in our case a Dog model. You can use the IoC to create it…and more importantly, any interfaces in the Dog’s constructor will be created too, so if you have dozens of tools like Loggers and Data layers, etc, they will all be created, and all of their dependencies and on and on. One line of code now:

BindingContext = App.IoCContainer.GetInstance<IAnimal>();

Done. Wasn’t that simple?

Advertisements

#Xamarin Forms: What is the Font Name in iOS for a Style?

download.pngIn Xamarin Forms you can use your own fonts, but there is a trick. In iOS you need to know the name of the font, which may not be obvious.

In this Style example, you can see that I am referencing the FontAwesome Solid font. Notice the dash and how different it is in Android.

<OnPlatform x:Key="FontAwesomeSolidFontFamily" x:TypeArguments="x:String"
            Android="fa-solid-900.ttf#Font Awesome 5 Pro Solid"
            iOS="FontAwesome5Pro-Solid" />

To get the name needed for iOS, which is different from Android, use this bit of code in your iOS AppDelegate.cs (Don’t forget to remove it when done)

foreach (var familyNames in UIFont.FamilyNames.OrderBy(c => c).ToList())
{
    Console.WriteLine(" * " + familyNames);
    foreach (var familyName in UIFont
           .FontNamesForFamilyName(familyNames)
           .OrderBy(c => c).ToList())
    {
        Console.WriteLine(" *-- " + familyName);
    }
}

When run, open the Output window and you’ll see all the names off the fonts on the device and those you added in your info.plist.

Here’s my info.plist for FontAwesome Solid.

	<key>UIAppFonts</key>
	<array>
		<string>Font Awesome 5 Pro-Light-300.otf</string>
		<string>Font Awesome 5 Pro-Solid-900.otf</string>
	</array>

 

Xamarin.Forms: Fast HtmlLabel with support for RTL languages

1_28-1lYrYTQoLhi87mllgBw.pngHere’s a really simple, and fast rendering HTML label control for Xamarin Forms. It focuses on speed and only implementing a few common HTML elements.

Supports all human languages including Right-To-Left languages like Arabic and Persian.

To your main project add a new HtmlLabel class, renderers will handle the rest in the Android and iOS project:

namespace App.Views.Common
{
    public class HtmlLabel :Label
    {
        
    }
}

In iOS, add this Renderer:

[assemblyExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
 
namespace App.iOS.Renderers
{
    public class HtmlLabelRenderer : LabelRenderer
    {
        private HtmlLabel _element//Holds the view for later re-use
 
        protected override void OnElementPropertyChanged(object senderSystem.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sendere);
            if (this.Element == null || this.Control == null)
                return;
 
	    //Watch for changes in common properties
            if (e.PropertyName == Label.TextProperty.PropertyName || 
                e.PropertyName == Label.FontAttributesProperty.PropertyName ||
                e.PropertyName == Label.FontFamilyProperty.PropertyName || 
                e.PropertyName == Label.FontSizeProperty.PropertyName ||
                e.PropertyName == Label.TextColorProperty.PropertyName 
                )
            {
                Update();
            }
        }
 
        protected override void OnElementChanged(ElementChangedEventArgs<Labele)
        {
            base.OnElementChanged(e);
 
            if (Element is HtmlLabel view)
            {
	            view.FontFamily = "Arial"//Update with your font.
	            _element = view;   //Hold on to the element
	            Update();
            }
        }
 
        private void Update()
        {
            var attr = new NSAttributedStringDocumentAttributes();
            var nsError = new NSError();
            attr.DocumentType = NSDocumentType.HTML;
 
            if (_element is HtmlLabel htmlLabel)
            {
                var fontFamily = htmlLabel.FontFamily??"Arial";
                var fontSize = htmlLabel.FontSize;
                var text = htmlLabel.Text;
                var color = htmlLabel.TextColor;
                
                //Default to Left To Right
                var dir = "ltr";
                if (text.HasValues() && ContainsArabicOrPersianCharacter(text))
                {
                    dir = "rtl";  //Arabic or Persian
                }
 
				//Render the string with left to right or right to left support
                string html = $"\"font-family:{fontFamily};font-size:{fontSize};color:{color.ToHexString()};\">{text}";
                var myHtmlData = NSData.FromString(htmlNSStringEncoding.Unicode);
                Control.AttributedText = new NSAttributedString(myHtmlDataattrref nsError);
            }
        }
 
        static bool ContainsArabicOrPersianCharacter(string s)
        {
	        byte[] bytes = Encoding.Unicode.GetBytes(s.SafeSubstring(s.Length/2,5));  //Sample half way in the string then see if any are arabic code page
            for (int i = 1; i < bytes.Lengthi += 2)
                if (bytes[i] == 6) //0x06** is arabic/persian code page
                    return true;
            return false;
        }
    }

And in Android:

[assemblyExportRenderer(typeof(HtmlLabel), typeof(HtmlLabelRenderer))]
namespace App.Droid.Renderers
{
    public class HtmlLabelRenderer : LabelRenderer
    {
		private static bool _canUseHtml = true;	 //Android 5 and below can't
        private HtmlLabel _element;	 //Hold on to the element
        public HtmlLabelRenderer(Context c): base(c)
        {
           
        }
 
        protected override void OnElementPropertyChanged(object senderSystem.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sendere);
            if (this.Element == null || this.Control == null)
                return;
 
			//Watch these properties
            if (e.PropertyName == Label.TextProperty.PropertyName ||
                e.PropertyName == Label.FontAttributesProperty.PropertyName ||
                e.PropertyName == Label.FontFamilyProperty.PropertyName ||
                e.PropertyName == Label.FontSizeProperty.PropertyName ||
                e.PropertyName == Label.TextColorProperty.PropertyName)
            {
                Update();
            }
        }
 
        protected override void OnElementChanged(ElementChangedEventArgs<Labele)
        {
            base.OnElementChanged(e);
 
            if (Element is HtmlLabel view)
            {
	            _element = view;  //Hold on the the element
	            Update();
            }
        }
 
        private void Update()
        {
            if (_element is HtmlLabel htmlLabel)
            {
                var fontFamily = htmlLabel.FontFamily;
                var fontSize = htmlLabel.FontSize;
                var text = htmlLabel.Text;
                var color = htmlLabel.TextColor;
                try
                {
 
	           //Watch for Right to Left languages
                    var dir = "ltr";
                    if (text.HasValues() && ContainsArabicOrPersianCharacter(text))
                    {
                        dir = "rtl";
                    }
 
		    //SEE:
                    //https://www.grokkingandroid.com/android-quick-tip-formatting-text-with-html-fromhtml/
                    string html = $"<html dir='{dir}'><body><font style=\"face:{fontFamily};size:{fontSize};color:{color.ToHexString()}\">{text}</font></body></html>";
 
		 //If we ever fail, it means the Android version can't use HTML labels (Android 5)
		 //so gracefully downgrade
                    if (_canUseHtml)
                    {
	                    Control.SetText(Html.FromHtml(htmlFromHtmlOptions.OptionUseCssColors), TextView.BufferType.Spannable);
                    }
                    else
                    {
	                    Control.SetText(text.StripHtml(false), TextView.BufferType.Normal);
                    }
                }
                catch  //Failed so this version of android can't
                {
	                _canUseHtml = false;  //Android 5 and below - don't try again
                    Control.SetText(text.StripHtml(false),TextView.BufferType.Normal);
                }
            }
        }
 
        static bool ContainsArabicOrPersianCharacter(string s)
        {
            byte[] bytes = Encoding.Unicode.GetBytes(s.SafeSubstring(s.Length / 2, 5)); //Sample half way in the string then see if any are arabic code page
            for (int i = 1; i < bytes.Lengthi += 2)
                if (bytes[i] == 6) //0x06** is arabic code page
                    return true;
            return false;
        }
    }

 

.Net C# Getting a rich DateTime Duration object to represent 2 dates

2019-07-26 14_34_23-0638616_PE699035_S5.JPG (600×600).pngOften I need to find the duration between two dates, but with more metadata to better explain the duration, but also to be Linq compliant.

First, let’s create a class which will be the container for our metadata and will help us render more human-readable durations:

public enum EnumDurationKind
{
    NotSet,
    Minute,
    Hour,
    Day,
    
}
public class Duration
{
    public EnumDurationKind Kind { getset; } = EnumDurationKind.NotSet;
    public int Amount { getset; }
 
    public override string ToString()
    {
        var x = ""; 
        switch (Kind)
        {
            case EnumDurationKind.Minute:
                x = "m";
                break;
            case EnumDurationKind.Hour:
                x = "h";
                break;
            case EnumDurationKind.Day:
                x = "d";
                break;
        }
 
        if (x.IsNullOrEmpty() && Amount == 0) return "";
 
        return $"{Amount}{x}";
    }
}

Now, an extension method to pull out the metadata:

public static Duration GetDurationBetweenTwoDates(this DateTime startDateDateTime endDate)
        {
            Duration result = new Duration();
                DateTime from = startDate;
                DateTime to = endDate;
 
                TimeSpan total = to - from;
                var mins = Convert.ToInt32(Math.Round(total.TotalMinutes,0));
                var hours = Convert.ToInt32(Math.Round(total.TotalHours, 0));
                var days = Convert.ToInt32(Math.Round(total.TotalDays, 0));
 
                //Special case for hours, like 100
                if (mins > 0 && mins % 60 != 0)
                {
                    result.Amount = mins;
                    result.Kind = EnumDurationKind.Minute;
                }
 
                //Special case for hours, like 26
                if (result.Kind == EnumDurationKind.NotSet && hours > 0 && hours % 24 != 0)
                {
                    result.Amount = hours;
                    result.Kind = EnumDurationKind.Hour;
                }
 
                if (result.Kind==EnumDurationKind.NotSet && days > 0)
                {
                    result.Amount = days;
                    result.Kind = EnumDurationKind.Day;
                }
 
                if (result.Kind == EnumDurationKind.NotSet && hours > 0)
                {
                    result.Amount = hours;
                    result.Kind = EnumDurationKind.Hour;
                }
 
                if (result.Kind == EnumDurationKind.NotSet && mins > 0)
                {
                    result.Amount = mins;
                    result.Kind = EnumDurationKind.Minute;
                }
 

            return result;
        }

And finally, a test (one of many) to show it:

[Test]
public void Min_60()
{
    DateTime d = DateTime.Now;
    var duration = d.GetDurationBetweenTwoDates(d.AddMinutes(60));
    Assert.IsTrue(duration.Kind == EnumDurationKind.Hour);
    Assert.IsTrue(duration.Amount == 1);
}

 

Xamarin Forms Shell: Handling Android Scrolling in WebViews

2019-07-26 13_14_18-android webview xamarin - Google Search.pngXamarin Forms Shell is an awesome new development tool allowing you to build amazing apps faster. Shell takes care of a lot of the plumbing so you can focus more on the app to make your customer happy, rather than the behind the scenes details needed to support your features.

There is a special case you do need to take care of on Android to allow the app to scroll WebViews as there currently is a bug. When a user tries to scroll, it won’t.

Instead of using WebView in your XAML, change that to use a new custom one that simply inherits from WebView:

namespace App.Views.Common
{
    /// <summary>
    /// Needed for Android scroll issue
    /// </summary>
    public class MyWebView  : WebView
    {
    }
}

In your XAML reference it will look like this:

<common:MyWebView 
x:Name="WebView" 
HorizontalOptions="FillAndExpand" 
VerticalOptions="FillAndExpand">
</common:MyWebView>

Now we add a renderer to the Android project:

[assemblyExportRenderer(typeof(MyWebView), typeof(MyWebViewRenderer))]
namespace App.Droid.Renderers
{
    class MyWebViewRenderer : WebViewRenderer
    {
        public MyWebViewRenderer(Context context) : base(context)
        {
        }
 
        protected override void OnElementChanged(ElementChangedEventArgs<WebViewe)
        {
            base.OnElementChanged(e);
        }
 
        public override bool DispatchTouchEvent(MotionEvent e)
        {
            Parent.RequestDisallowInterceptTouchEvent(true);
            return base.DispatchTouchEvent(e);
        }
    }
}

 iOS requires no changes.

#Xamarin Forms #Shell: Creating a Gradient Flyout

David Ortinau’s Xappy was a fun demo of some of the features of the Rendering capabilities of the new Xamarin Forms Shell. That has served as an inspiration for the gradient flyout in this article. We’ve extended it to handle some Android Dispose issues in Shell that in the next Xamarin Forms should go away.

Here’s how it looks when running:

2019-07-26 12_47_38-iPhone 7 iOS 12.4.png

In Shell, you can enhance the UI with simple Renderers you add as classes to your iOS and Android projects. You decorate them with [ExportRender] to tell Xamarin to use them.

Style

Add this to the App.xaml so that you have the Flyout styles ready to be accessed. Note that they are dynamic so that they can be updated live.


    <Color x:Key="FlyoutGradientStart">#347FB9</Color>
    <Color x:Key="FlyoutGradientEnd">#38AECC</Color>

Renderers

The easy one in this example is iOS, it’s straight forward, just add it anywhere in your iOS project:

using System;
using CoreAnimation;
using CoreGraphics;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
 
 
[assemblyExportRenderer(typeof(Shell), typeof(App.iOS.Renderers.GradientShellRenderer))]
namespace App.iOS.Renderers
{
	/// <summary>
	/// Inspired by David Ortinau's Xappy renderer
	/// </summary>
    public class GradientShellRenderer : ShellRenderer
    {
        private CAGradientLayer _flyoutBackground = null;
 
        protected override void OnElementSet(Shell element)
        {
            base.OnElementSet(element);
        }
 
        protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection)
        {
            var renderer = base.CreateShellSectionRenderer(shellSection);
            if (renderer == nullreturn null;
 
            if (renderer is ShellSectionRenderer r)
            {
	            r.NavigationBar.ShadowImage = new UIImage();
            }
 
            return (IShellSectionRenderer)renderer;
        }
 
        protected override IShellFlyoutContentRenderer CreateShellFlyoutContentRenderer()
        {
 
            var flyout = base.CreateShellFlyoutContentRenderer();
            flyout.WillAppear += OnFlyoutWillAppear;
 
            var tv = (UITableView)flyout.ViewController.View.Subviews[0];
            tv.ScrollEnabled = false;
 
            return flyout;
        }
 
        /// <summary>
        /// Only grab the bounds of the View when View rendering calculations are already done
        /// </summary>
        private void OnFlyoutWillAppear(object senderEventArgs e)
        {
            if (_flyoutBackground == null && sender != null && sender is IShellFlyoutContentRenderer flyout)
			{
	            var view = flyout.ViewController.View;
 
                _flyoutBackground = new CAGradientLayer
                {
	                Frame = new CGRect(0, 0, view.Bounds.Widthview.Bounds.Height),
	                Colors = new []
	                {
		                ((ColorApp.Current.Resources["FlyoutGradientStart"]).ToCGColor(), ((ColorApp.Current.Resources["FlyoutGradientEnd"]).ToCGColor()
	                }
                };
 
                flyout.ViewController.View.Layer.InsertSublayer(_flyoutBackground, 0);
                flyout.WillAppear -= OnFlyoutWillAppear;
            }
        }
    }
}

Next in your Android project add this one. Note that we handle the Dispose due to a bug in Xamarin Shell in 4.1

The dispose needs to be taken care of or your app will crash a lot because of

ShellSectionRenderer.UnhookEvents ()

System.NullReferenceException: Object reference not set to an instance of an object
[assemblyExportRenderer(typeof(Shell), typeof(App.Droid.Renderers.GradientShellRenderer))]
 
namespace App.Droid.Renderers
{
	/// <summary>
	/// Inspired by David Ortinau's Xappy renderer
	/// </summary>
    public class GradientShellRenderer : ShellRenderer
    {
	    bool _disposed;
        public GradientShellRenderer(Context context) : base(context)
        {
        }
 
        protected override void OnElementSet(Shell element)
        {
            base.OnElementSet(element);
        }
 
        protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection)
        {
            var renderer = base.CreateShellSectionRenderer(shellSection);
            return (IShellSectionRendererrenderer;
        }
 
        protected override IShellFlyoutRenderer CreateShellFlyoutRenderer()
        {
            var flyout = base.CreateShellFlyoutRenderer();
 
            return flyout;
        }
 
        protected override IShellFlyoutContentRenderer CreateShellFlyoutContentRenderer()
        {
 
            var flyout = base.CreateShellFlyoutContentRenderer();
 
            try
            {
	            GradientDrawable gradient = new GradientDrawable(
		            GradientDrawable.Orientation.BottomTop,
		            new Int32[]
		            {
			            ((ColorApp.Current.Resources["FlyoutGradientStart"]).ToAndroid(),
			            ((ColorApp.Current.Resources["FlyoutGradientEnd"]).ToAndroid()
 
		            }
	            );
 
	            var cl = ((CoordinatorLayoutflyout.AndroidView);
	            cl.SetBackground(gradient);
 
	            var g = (AppBarLayoutcl.GetChildAt(0);
	            g.SetBackgroundColor(Color.Transparent.ToAndroid());
	            g.OutlineProvider = null;
 
	            var header = g.GetChildAt(0);
	            header.SetBackgroundColor(Color.Transparent.ToAndroid());
 
            }
            catch (Exception e)
            {
	            Logger.LogError(e);
            }
 
            return flyout;
        }
 
        protected override void Dispose(bool disposing)
        {
	        if (_disposed)
	        {
		        return;
	        }
 
	        if (disposing && Element!=null)
	        {
		        Element.PropertyChanged -= OnElementPropertyChanged;
		        Element.SizeChanged -=
			        (EventHandlerDelegate.CreateDelegate(typeof(EventHandler), this"OnElementSizeChanged"); // OnElementSizeChanged is private, so use reflection
	        }
 
	        _disposed = true;
        }
    }
}

 

#Xamarin Forms: When Apple rejects your binary for a quirky weird reason

question-mark-2110767_960_720.jpgYou’ve just updated your #Xamarin Forms app, it’s perfect and so you send the update via the Application Loader to Apple and…. the Apple servers reject it for some seeming crazy reason.

Don’t worry. It’s not you. Your code is fine. Here’s a checklist I go through to get the app into the approval process every time.

info.plist

Sometimes Visual Studio changes your info.pist and breaks it.

Review it for the common items like CFBundleName, UIDeviceFamily, UIRequiredDeviceCapabilities. I often find that the Assets catalog is missing suddenly.

Check for it:

	<key>XSAppIconAssets</key>
	<string>Resources/Images.xcassets/AppIcons.appiconset</string>

Certificates

This is the most common issue for me, even with Automatic set. Double and triple-check that your certificate and profile are set correctly.

2019-07-26 12_04_17-Window.png

ipa

After you’ve built your application and are ready to upload the ipa, check to make sure that the ipa file doesn’t have 0 file length images. If it does, just delete the bin and obj folders from the solution and rebuild.

To check for missing files in your ipa, right-click the ipa and unzip it. Then right-click the unzipped file and choose Show Package Contents

bin/obj

Sometimes, though rarely, I have to delete the bin and obj from the solution, then go to the Mac and delete the build folder there too, reboot everything and then rebuild. On the Mac the Build folder is in ~/Library/Caches/Xamarin/mtbs/builds – You can delete anything there.