Taoffi's blog

prisonniers du temps

Xamarin forms – Platform startup: Forms.Init() visit!

As XF documentations say, your platform-application (Droid, iOS or WP) should call Forms.Init() at startup.

That should be done:

  • In iOS: in the AppDelegate.FinishedLaunching method
    • global::Xamarin.Forms.Forms.Init();
  • In Droid: in the MainActivity. OnCreate (Bundle bundle)
    • global::Xamarin.Forms.Forms.Init(this, bundle);
  • In Win Phone: in your application's MainPage constructor
    • global::Xamarin.Forms.Forms.Init();

 

 

 

 

What does Forms.Init do?

On droid

Here is an example of what Forms.Init method does on Android:

 

public static void Init(Activity activity, Bundle bundle)
{
    // get the calling assembly 
    Assembly callingAssembly = Assembly.GetCallingAssembly(); 

    // Call SetupInit 
    SetupInit(activity, callingAssembly); 
}

 

Let us continue following the call to SetupInit():

 

private static void SetupInit(Activity activity, Assembly resourceAssembly) 
{
    // set the Context to current activity 
    Context = activity; 

    // initialize resources for this assembly 
    ResourceManager.Init(resourceAssembly); 

    // set the AccentColor according to OS version 
    if (Build.VERSION.SdkInt <=    BuildVersionCodes.GingerbreadMr1) 
    { 
       Color.Accent = Color.FromHex("#fffeaa0c"); 
    } 
    else 
    { 
       Color.Accent = Color.FromHex("#ff33b5e5"); 
    } 

    // log (if not initialized) 
    if (!IsInitialized) 
    { 
       Log.get_Listeners().Add(new DelegateLogListener( 
 (c, m) => Trace.WriteLine(m, c))); 
    } 

    // set the Device.OS version and platform services (here android) 
       Device.OS = TargetPlatform.Android; 
       Device.PlatformServices = new AndroidPlatformServices(); 

    // recreate the device info from this activity 
    if (Device.info != null) 
    { 
       ((AndroidDeviceInfo) Device.info).Dispose(); 
       Device.info = null; 
    } 

    IDeviceInfoProvider formsActivity = activity as IDeviceInfoProvider; 

    if (formsActivity != null) 
    { 
       Device.Info = new AndroidDeviceInfo(formsActivity); 
    } 

    // recreate the ticker 
    AndroidTicker ticker = Ticker.Default as AndroidTicker; 

    if (ticker != null) 
    { 
       ticker.Dispose(); 
   } 

   Ticker.Default = new AndroidTicker(); 

    // initialize renderers (if not initialized (again)) 
    if (!IsInitialized) 
    { 
       // register renderers for attributes export / cell / image source 
       Type[] typeArray1 = new Type[] 
                   { typeof(ExportRendererAttribute), 
                   typeof(ExportCellAttribute), 
                   typeof(ExportImageSourceHandlerAttribute) 
                   }; 

       // registrer the handlers of these types 
       Registrar.RegisterAll(typeArray1); 
    } 

    // set the device idiom according to screen width dpi 
    Device.Idiom = (Context.Resources.Configuration.SmallestScreenWidthDp >= 600) 
             ? TargetIdiom.Tablet : TargetIdiom.Phone; 
 
    // set search expression default (if not already set) 
    if (ExpressionSearch.Default == null) 
    { 
       ExpressionSearch.Default = new AndroidExpressionSearch(); 
    } 

    // set initialzed flag 
    IsInitialized = true; 
} 

 

 

 

On iOS

 

public static void Init() 
{ 
    if (!IsInitialized) 
    { 
       // set initialized flag 
       IsInitialized = true; 

       // set the AccentColor 
       Color.Accent = Color.FromRgba(50, 0x4f, 0x85, 0xff); 

       Log.get_Listeners().Add(new DelegateLogListener( 
             // obscure decompilation of an anonymous methodJ 
          <>c.<>9__9_0 
             ?? (<>c.<>9__9_0 = new Action<string, string>      (<>c.<>9.<Init>b__9_0)))); 

       // device os and platform services 
       Device.OS = TargetPlatform.iOS; 
       Device.PlatformServices = new IOSPlatformServices(); 

       Device.Info = new IOSDeviceInfo(); 

       // set the ticker 
       Ticker.Default = new CADisplayLinkTicker(); 

       // register renderers for attributes export / cell / image source 
       Type[] typeArray1 = new Type[] 
                      { typeof(ExportRendererAttribute), 
                      typeof(ExportCellAttribute), 
                      typeof(ExportImageSourceHandlerAttribute) 
                      }; 

       Registrar.RegisterAll(typeArray1); 

       // set device idiom 
       Device.Idiom = (UIDevice.get_CurrentDevice  ().get_UserInterfaceIdiom() == 1L) 
             ? TargetIdiom.Tablet : TargetIdiom.Phone; 

       // set search expression default 
       ExpressionSearch.Default = new iOSExpressionSearch(); 
    } 
}

 

 

On Win Phone

 

public static void Init()
{
    if (!isInitialized)
    {
        // create an event trigger object (why?) 
        // note: constructor initializes an EventNameProperty DependencyProperty 
        new EventTrigger();

        string name = Assembly.GetExecutingAssembly().GetName().Name;
        ResourceDictionary dictionary1 = new ResourceDictionary();

        // load xaml resources 
        dictionary1.set_Source(
                new Uri(string.Format("/{0};component/WPResources.xaml", name),
                UriKind.Relative));

        // add resources to merged dictionaries 
        Application.get_Current().get_Resources().get_MergedDictionaries().Add(dictionary1);

        // set accent( color from resources 
        Color color = (Application.get_Current().get_Resources().get_Item("PhoneAccentBrush") as SolidColorBrush).get_Color();

        byte introduced3 = color.get_R();
        byte introduced4 = color.get_G();
        byte introduced5 = color.get_B();

        Color.Accent = Color.FromRgba((int)introduced3,
                                (int)introduced4,
                                (int)introduced5,
                                (int)color.get_A());

        // log 
        Log.get_Listeners().Add(new DelegateLogListener(<> c.<> 9__3_0
                    ?? (<> c.<> 9__3_0 = new Action<string,
                    string>(<> c.<> 9.< Init > b__3_0))));

        // set device os and platform services 
        Device.OS = TargetPlatform.WinPhone;
        Device.PlatformServices = new WP8PlatformServices();
        Device.Info = new WP8DeviceInfo();

        // register renderers for attributes export / cell / image source 
        Type[] typeArray1 = new Type[]
                    { typeof(ExportRendererAttribute),
                            typeof(ExportCellAttribute),
                            typeof(ExportImageSourceHandlerAttribute)
                    };

        Registrar.RegisterAll(typeArray1);

        // set ticker default 
        Ticker.Default = new WinPhoneTicker();

        // set device idiom 
        Device.Idiom = TargetIdiom.Phone;

        // set search expression default 
        ExpressionSearch.Default = new WinPhoneExpressionSearch();

        // set initialzed flag 
        isInitialized = true;
    }
}

 

 

 

Xamarin forms: an inside look – II. The (simplified) journey of a Label from your code to screen

Understanding how your UI code ends up by being shapes and colors on a device screen may help in better usage, better interpretation for encountered issues and for evaluating if you really need a custom renderer for a given control.

Exploring this also explains some of the interesting internal Xamarin forms mechanisms to play its awesome cross-platform game!

To do this, I will here attempt to explore the journey of a simple control: The Label.

For simplicity, I will track the journey to an Android device.

Label hierarchy

Label is a class defined Xamarin.Forms.Core.dll. Its hierarchy is roughly the following:

 

The Label renderer

At the end of this simple control processing chain, we have the android's Label renderer responsible of drawing it on the screen of each device… The renderer hierarchy (on Android):

The mono component

The ViewRenderer (XF Platform.Android.dll) is defined as:

public abstract class ViewRenderer<TView, TNativeView>

: VisualElementRenderer<TView>,

global::Android.Views.View.IOnFocusChangeListener,

IJavaObject,

IDisposable

 

It inherits the VisualElementRenderer (abstract) class which is defined as:

public abstract class VisualElementRenderer<TElement>

: FormsViewGroup,

IVisualElementRenderer,

IRegisterable,

IDisposable,

global::Android.Views.View.IOnTouchListener,

IJavaObject,

global::Android.Views.View.IOnClickListener

where TElement : VisualElement

 

Both of them refer to objects from the Android.Views and Android.Runtime namespaces defined in Mono.Android.dll.

 

The Label renderer referenced types, inheritance and interface implementations (summary)

 

Label renderer calls (summary)

The IRegisterable and Registrar link nodes

As you can notice, both ViewRenderer and VisualElementRenderer implement the IRegisterable Interface defined in XF.Core.dll.

 

IRegistrable is implemented by all Renderers. And is referenced by the Registrar class (we will see later).

Let us follow IRegistrable implemented child tree till the Label Renderer node:

(Incidentally, in he above figure, we again notice our ImageSourceHandler at the very root of the tree… you may look at my notes here)

The key node: XF Registrar

Registrar (Xamarin.Forms.Core.dll) is an internal static class. It exposes (internally) some methods for registering generic handlers of which we find the Renderers.

The method RegisterAll(Type[] attrTypes) of the Registrar class proceeds registration of the input array of types (note: as the class is internal, I could not find – for now – who calls this method: see a sample call at the end of this post).

If we follow the method's decompiled code, we understand that items of the Type array argument are expected to be each decorated with an attribute of type HandlerAttribute (deriving from System.Attribute). Here is a simplified version of the method's code:

 

internal static void RegisterAll(Type[] attrTypes)
{
    Assembly[] assemblies = Device.GetAssemblies();

    foreach (Assembly assembly2 in assemblies)
    {
        foreach (Type type in attrTypes)
        {
            Attribute[] attributeArray = Enumerable.ToArray<Attribute>(CustomAttributeExtensions.GetCustomAttributes(assembly2, type));
            foreach (HandlerAttribute attribute in attributeArray)
            {
                if (attribute.ShouldRegister())
                {
                    Registered.Register(attribute.HandlerType, attribute.TargetType);
                }
            }
        }
    }
}

 

 

HandlerAttribute exposes two members: HandlerType and TargetType, both are of type System.Type

 

Few attributes derive from this HandlerType attribute. Of which you have… oh… the famous ExportRendererAttribute!

 

And that is roughly how XF finds the renderer to call for a given control on each platform.

Sample call to Registrar at droid application launch in AppCompat:

Xamarin forms backyard - exploring MSBuild projects

In a previous post, I talked about how to manually change the project (.csproj) file to create an App.xaml and App.xaml.cs files in a Xamarin forms project. The reason for this is to be able to declare and use global application resources (styles, assets… etc.) linked to the main application file (App.cs changed to App.xaml.cs). That is, in a way, to repair a guilty XF project template that doesn't create these items by default.

Like in all development processes, when you use xamarin forms, you spend your time coding and building your projects.

In Windows, this essential build process goes through MSBuild which reads and interprets .csproj file's instructions into specific actions to compile and package your applications.

Those .csproj files are XML files containing configurations, variables and instructions… many can be manipulated in Visual Studio or Xamarin Studio. Still, some of these configurations require a deeper dive into MSBuild details. Your project, for instance, may sometimes fail to build for reasons that are difficult to trace without such dive.

Diving into MSBuild is something different from simply reading the .csproj xml instructions.

Understanding the way these instructions would be interpreted and handled by MSBuild is crucial for your investigations success.

MSBuild API to the rescue

Fortunately, MSBuild offers an API that allows you to explore or modify the build engine behavior according to your requirements.

I used this API to build a simple MSBuild browser (Windows WPF application) that allows you to examine the detailed interpretations of MSBuild to a .csproj or .vbproj file.

A project configuration browser

The browser uses the objects defined in the MSBuild Microsoft.Build.Evaluation namespace.

Three main view models of the application are used to represent MSBuild objects:

  • iProject: msbuild project view model. Encapsulates the collection of:
  • iProjectItemType: project item types (and actions). Each project item type encapsulating a collection of:
  • iProjectItem: an item of the related type. This object encapsulates a property bag of the related MSBuild item's properties (For instance: metadata).

 

PropertyBag object is used to allow future application extensibility. It parses MSBuild objects' properties independently of the version referenced (the current application references MSBuild 14.0.0.0).

 

The result looks like this:

 

You can download the binaries Here

If you are interested in extending features, you can download the source code here.

 

Xamarin forms – the OnPlatform<T> heavens!

Xamarin forms offer this class (OnPlatform<T>) of great help to adapt your code (either c# or Xaml) to the current runtime device's platform.

So you may write:

string helloDevice = Device.OnPlatform<string>(
            "Hello iOS", "Hello Droid", "Hello Win Phone");

 At runtime, on iOS, your string would have the related platform value:

In Xaml, you specify the type to which the OnPlatform should act and the values for each platform:

<ContentPage.Padding>
   <OnPlatform x:TypeArguments="Thickness">
      <OnPlatform.iOS>0, 20, 0, 0</OnPlatform.iOS>
      <OnPlatform.Android>0, 0, 0, 0</OnPlatform.Android>
      <OnPlatform.WinPhone>0, 0, 0, 0</OnPlatform.WinPhone>
   </OnPlatform>
</ContentPage.Padding>

The main difficulty in Xaml is that you should know exactly the Type to pass as TypeArgument.

For system known types, you should use the x: prefix like in:

x:TypeArguments="x:String"
x:TypeArguments="x:Double"
x:TypeArguments="x:Int32"

For Xamarin Forms objects properties and types, it is easy to use the Visual Studio Object Browser to obtain the correct information:

Here, for instance, you know that the Padding is of type Xamarin Forms Thickness:

    <OnPlatform x:TypeArguments="Thickness">