<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Julian's Blog]]></title><description><![CDATA[Hi, I am Julian, a passionate app and software developer with a focus on C# .NET, Xamarin.Forms and .NET MAUI. I like writing Clean Code using TDD. Here, I writ]]></description><link>https://blog.ewers-peters.de</link><generator>RSS for Node</generator><lastBuildDate>Mon, 13 Apr 2026 11:48:11 GMT</lastBuildDate><atom:link href="https://blog.ewers-peters.de/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to bind to functions from XAML in .NET MAUI]]></title><description><![CDATA[Introduction
In .NET MAUI, we sometimes face the situation that we would like to evaluate a specific value, e.g. a property from a child object, against some logic from the ViewModel. I personally sometimes wish it were possible to bind directly to a...]]></description><link>https://blog.ewers-peters.de/how-to-bind-to-functions-from-xaml-in-net-maui</link><guid isPermaLink="true">https://blog.ewers-peters.de/how-to-bind-to-functions-from-xaml-in-net-maui</guid><category><![CDATA[data binding]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[#maui]]></category><category><![CDATA[xaml]]></category><category><![CDATA[User Interface]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Thu, 20 Feb 2025 11:04:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740049255733/b809a78a-457c-4b8d-a316-fde8cdec4b59.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>In .NET MAUI, we sometimes face the situation that we would like to evaluate a specific value, e.g. a property from a child object, against some logic from the ViewModel. I personally sometimes wish it were possible to bind directly to a method and pass a value to it so that I can decide how to display the value in the UI.</p>
<p>Normally, I would use a <em>trigger</em> when it comes to simply checking whether a property has a certain value and then update the UI accordingly. But, what if the evaluation isn’t as simple as checking for a specific value? What if we want to use a range of values or apply some more complex logic?</p>
<p>One thing that I would like to avoid when using the MVVM pattern is moving business logic to data objects or even into the UI layer, because that’s usually not where it belongs. I want my business logic to be separate from data and UI layers and I would like it to be unit testable, so having evaluation functions for objects or arguments is desired.</p>
<p>In this blog post I’ll demonstrate how we can use a trick in order to bind to functions from XAML by leveraging the capabilities of <em>data triggers</em>, <em>multi-bindings</em>, <em>multi-value converters</em> and <em>function delegates</em>.</p>
<p>As always, I’ve added the code used here to my <a target="_blank" href="https://github.com/ewerspej/maui-samples">MAUI Samples repository</a>, so that you can follow along.</p>
<h1 id="heading-the-goal">The Goal</h1>
<p>The idea is to check whether the <code>Count</code> property of type <code>int</code> of the already existing <a target="_blank" href="https://github.com/ewerspej/maui-samples/blob/main/MauiSamples/MauiSamples/Models/BindingItem.cs"><code>BindingItem</code></a> class is a prime number, and if it is, display the row the object resides in in a different color, in our case light green, without modifying the data class:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BindingItem</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Count { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p>Currently, adding items simply displays a new row with the <code>Count</code> value for the new item:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739961843055/aa225d06-a72c-466c-bfb0-1aa2b00c7b28.png" alt class="image--center mx-auto" /></p>
<p>However, we would like to highlight all rows that contain a prime number, like so:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739961869013/cfb73ecd-e119-42dd-aac2-a2d2a13bc933.png" alt class="image--center mx-auto" /></p>
<p>Things like this can usually be done using triggers. Let’s have a look.</p>
<h1 id="heading-data-triggers">Data Triggers</h1>
<p>A <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/triggers#data-triggers">data trigger</a> uses a binding expression and a value to compare the property against and then applies a setter to update a specified set of visual properties of a control, like the background color:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Label</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Label.Triggers</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">DataTrigger</span>
      <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Label"</span>
      <span class="hljs-attr">Binding</span>=<span class="hljs-string">"{Binding Count}"</span>
      <span class="hljs-attr">Value</span>=<span class="hljs-string">"3"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BackgroundColor"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"LightGreen"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">DataTrigger</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Label.Triggers</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Label</span>&gt;</span>
</code></pre>
<p>The flaw of this approach is that we would need to know the value that we want to evaluate against for each item beforehand, but we don’t. In order to check whether an integer is actually a prime number requires applying some logic, which we usually cannot directly do using a data trigger, and, as mentioned above, this logic doesn’t belong into the data layer.</p>
<p>However, we can still make use of a data trigger to accomplish what we want, if we combine this with a converter that evaluates to <code>true</code> or <code>false</code> based on an evaluation function for the given property:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">converters:PrimeNumberToBoolConverter</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"IsPrimeConverter"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.Resources</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">Label</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Label.Triggers</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">DataTrigger</span>
      <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Label"</span>
      <span class="hljs-attr">Binding</span>=<span class="hljs-string">"{Binding Count, Converter={StaticResource IsPrimeConverter}}"</span>
      <span class="hljs-attr">Value</span>=<span class="hljs-string">"True"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BackgroundColor"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"LightGreen"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">DataTrigger</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Label.Triggers</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Label</span>&gt;</span>
</code></pre>
<p>We could implement a simple <code>IValueConverter</code> that contains the logic for checking for prime numbers, but this would potentially mean moving logic into a converter, which is not desirable in most scenarios, because we may want to apply some business logic from the ViewModel, as well. So, this logic doesn’t belong into the UI layer, either.</p>
<p>💡 Yet, we can create a generalized converter that allows us to bind to a function delegate, which can then execute any kind of business logic from the ViewModel. This is exciting, let’s have a look.</p>
<h1 id="heading-funcvaluetoboolconverter">FuncValueToBoolConverter</h1>
<p>The heart of our desired functionality is the <em>FuncValueToBoolConverter</em> which is an <a target="_blank" href="https://learn.microsoft.com/dotnet/api/microsoft.maui.controls.imultivalueconverter">IMultiValueConverter</a> implementation.</p>
<p>In our case it only takes two values: The first one is a function delegate of type <code>Func&lt;object, bool&gt;</code>, and the second value is an object which serves as the argument to be be passed to the function delegate:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">FuncValueToBoolConverter</span> : <span class="hljs-title">IMultiValueConverter</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">object</span> <span class="hljs-title">Convert</span>(<span class="hljs-params"><span class="hljs-keyword">object</span>[] values, Type targetType, <span class="hljs-keyword">object</span> parameter, CultureInfo culture</span>)</span>
    {
        <span class="hljs-keyword">if</span> (values[<span class="hljs-number">0</span>] <span class="hljs-keyword">is</span> Func&lt;<span class="hljs-keyword">object</span>, <span class="hljs-keyword">bool</span>&gt; func)
        {
            <span class="hljs-keyword">return</span> func(values[<span class="hljs-number">1</span>]);
        }

        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">object</span>[] <span class="hljs-title">ConvertBack</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> <span class="hljs-keyword">value</span>, Type[] targetTypes, <span class="hljs-keyword">object</span> parameter, CultureInfo culture</span>)</span>
    {
        <span class="hljs-keyword">return</span> [];
    }
}
</code></pre>
<p>This converter is a general-purpose <em>multi-value converter</em>. Its purpose is to take a function delegate that can evaluate an object based on some business logic and either returns <code>true</code> or <code>false</code>.</p>
<p>In our case, we will use a method from the <a target="_blank" href="https://github.com/ewerspej/maui-samples/blob/main/MauiSamples/MauiSamples/ViewModels/BindingsViewModel.cs">BindingsViewModel</a> class, which we don’t have yet.</p>
<h1 id="heading-prime-number-function">Prime number function</h1>
<p>Let’s add a method which checks whether a number is prime to the ViewModel:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BindingsViewModel</span> : <span class="hljs-title">ObservableObject</span>
{ 
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">IsPrime</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> number</span>)</span>
    {
        <span class="hljs-keyword">if</span> (number <span class="hljs-keyword">is</span> not <span class="hljs-keyword">int</span> intNumber || intNumber &lt; <span class="hljs-number">2</span>)
        {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">2</span>; i &lt;= Math.Sqrt(intNumber); i++)
        {
            <span class="hljs-keyword">if</span> (intNumber % i == <span class="hljs-number">0</span>)
            {
                <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
            }
        }

        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }
}
</code></pre>
<blockquote>
<p><strong>Note</strong>: The <code>IsPrime()</code> method takes in an argument of type <code>object</code>, because that way, the <em>FuncValueToBoolConverter</em> can be reused in different contexts. We could also create a <em>FuncIntToBoolConverter</em> or similar in order to avoid boxing the integer.</p>
</blockquote>
<p>⁉️ But wait, we cannot bind to methods, and, apart from that, this static method is <code>private</code>. Fortunately, C# allows us to expose this method as a <code>public</code> function delegate auto-property <em>(which means that we can use it in a binding expression)</em>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BindingsViewModel</span> : <span class="hljs-title">ObservableObject</span>
{
    <span class="hljs-comment">// expose the IsPrime() function as a delegate,</span>
    <span class="hljs-comment">// note that this property must not be static</span>
    <span class="hljs-keyword">public</span> Func&lt;<span class="hljs-keyword">object</span>, <span class="hljs-keyword">bool</span>&gt; IsPrimeFunc =&gt; IsPrime;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">IsPrime</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> number</span>)</span>
    {
        <span class="hljs-comment">// see above</span>
    }
}
</code></pre>
<p>Time to put it all together and make the magic happen. 🪄</p>
<h1 id="heading-putting-it-together">Putting it together</h1>
<p>In our page, we first add the <a target="_blank" href="https://github.com/ewerspej/maui-samples/blob/main/MauiSamples/MauiSamples/Converters/FuncValueToBoolConverter.cs">FuncValueToBoolConverter</a> as a static resource:</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">converters:FuncValueToBoolConverter</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"FuncValueToBoolConverter"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
</code></pre>
<p>Then, we can apply the converter together with a data trigger inside the <em>DataTemplate</em> of a <em>CollectionView</em>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">CollectionView</span> <span class="hljs-attr">ItemsSource</span>=<span class="hljs-string">"{Binding Items}"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">CollectionView.ItemTemplate</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">DataTemplate</span> <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"models:BindingItem"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
        <span class="hljs-attr">Padding</span>=<span class="hljs-string">"8"</span>
        <span class="hljs-attr">ColumnDefinitions</span>=<span class="hljs-string">"*,*"</span>&gt;</span>
        <span class="hljs-comment">&lt;!-- skipping irrelevant parts for brevity --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Grid.Triggers</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">DataTrigger</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Grid"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"True"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">DataTrigger.Binding</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">MultiBinding</span> <span class="hljs-attr">Converter</span>=<span class="hljs-string">"{StaticResource FuncValueToBoolConverter}"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Binding</span>
                  <span class="hljs-attr">Path</span>=<span class="hljs-string">"IsPrimeFunc"</span>
                  <span class="hljs-attr">Source</span>=<span class="hljs-string">"{RelativeSource AncestorType={x:Type viewModels:BindingsViewModel}}"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Binding</span> <span class="hljs-attr">Path</span>=<span class="hljs-string">"Count"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">MultiBinding</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">DataTrigger.Binding</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">DataTrigger.Setters</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BackgroundColor"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"LightGreen"</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">DataTrigger.Setters</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">DataTrigger</span>&gt;</span>

        <span class="hljs-tag">&lt;/<span class="hljs-name">Grid.Triggers</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">DataTemplate</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">CollectionView.ItemTemplate</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">CollectionView</span>&gt;</span>
</code></pre>
<p>Note the two separate bindings in the multi-binding. The first one must be pointing to the <code>IsPrimeFunc</code> property of the ViewModel <em>(using a</em> <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/relative-bindings"><em>relative binding</em></a><em>)</em> and the second one binds to the <code>Count</code> property of the current item.</p>
<p>Running the app now, we will be greeted with green rows for every prime number when adding new items to the collection:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739961869013/cfb73ecd-e119-42dd-aac2-a2d2a13bc933.png" alt class="image--center mx-auto" /></p>
<p>🤩 Isn’t this awesome? We managed to bind to a function to apply complex logic within a data binding context. I’m very happy with the result.</p>
<blockquote>
<p>You might be asking yourself whether this will also work with objects that have observable properties that frequently can change. The answer is: <strong>Yes, absolutely.</strong> Multi-bindings are evaluated everytime one of the properties of the child bindings changes, as long as it’s an observable property.</p>
</blockquote>
<h1 id="heading-conclusions-and-next-steps">Conclusions and next steps</h1>
<p>I’ve demonstrated how we can take advantage of MAUI’s built-in capabilities in order to bind to a ViewModel method. We’ve done this by exposing it as a function delegate property and then using it in the context of a multi-binding expression together with a multi-value converter that executes the method for us. This let’s us evaluate values of data objects without having to add logic to the data class and without having to add critical business logic to converters.</p>
<p>If you enjoyed this blog <a target="_blank" href="https://hashnode.com/@ewerspej">post</a>, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>GitHub repository</strong></a> for this post so you don't miss out on any future posts and developments. And remember that sharing is caring.</p>
]]></content:encoded></item><item><title><![CDATA[White Labeling .NET MAUI Apps]]></title><description><![CDATA[Introduction
Have you ever come across two (or more) products that looked almost exactly the same and only differed in things like color and the company logo printed on them? This is a common practice called white labeling where one company develops ...]]></description><link>https://blog.ewers-peters.de/white-labeling-net-maui-apps</link><guid isPermaLink="true">https://blog.ewers-peters.de/white-labeling-net-maui-apps</guid><category><![CDATA[white-labeling]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[#maui]]></category><category><![CDATA[Rebranding]]></category><category><![CDATA[customization]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Wed, 17 Jul 2024 20:00:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720778506691/48fecad5-d947-44b2-8446-b1db5657502a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p><em>Have you ever come across two (or more) products that looked almost exactly the same and only differed in things like color and the company logo printed on them?</em> This is a common practice called <strong><em>white labeling</em></strong> where one company develops a product and then rebrands it for other companies to sell it under their own brand. A lot of products you can find in popular online shops are actually white-labeled.</p>
<p>So, let me rephrase my initial question: <em>Have you ever come across two (or more)</em> <strong><em>apps</em></strong> <em>that looked almost exactly the same and only differed in things like color and the company logo on them?</em> What you saw might indeed have been the same app just with a different icon, different images and different colors but based on exactly the same code base.</p>
<blockquote>
<p>This blog post is my contribution to Matt Goldman's <a target="_blank" href="https://goforgoldman.com/posts/mauiuijuly-24/">MAUI UI July</a> 2024. Check it out, there are many great blog posts about .NET MAUI to be found there.</p>
</blockquote>
<p>Today, I will show you how you can develop a single .NET MAUI app and easily rebrand it for different clients that share the same <em>(or very similar)</em> requirements. We'll see an example setup for two clients: <em>ClientA</em> and <em>ClientB</em>, as well as an unbranded "default" version of the app. We'll cover client-specific <em>logos</em>, <em>images</em>, <em>colors</em>, <em>styles</em>, <em>app names</em> and <em>identifiers</em>, as well as <em>fonts</em>, and also quickly touch on <em>custom behavior</em>.</p>
<p>The goal is achieve two or three differently styled apps with the same code base under the hood:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720768928477/e4231f02-272f-47cb-a12a-6d008567827d.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>Disclaimer:</strong> This blog post can only serve as a starting point, because this topic is too large to cover all aspects in a single write-up. For example, customer-specific resource files (for strings and translations) are a topic that might be addressed in a follow-up post. I do not guarantee that the concepts presented here will work for every scenario. There are various different ways to achieve white labeling and this is just one of many approaches.</p>
</blockquote>
<p>As always, there is a <a target="_blank" href="https://github.com/ewerspej/MauiWhiteLabelling">companion repository</a> where you can find the full source code for this blog post <em>(and more)</em>. To quote a popular YouTuber: <em>"it is enough talking, so let's do it!"</em></p>
<h1 id="heading-project-setup">Project Setup</h1>
<p>First, let's look at the project structure that we'll work with. A new MAUI single project app created using one of the available templates will usually result in a project structure that looks similar to this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720692927218/963f37ff-65c1-44ff-923a-edaeaf5de0bb.png" alt class="image--center mx-auto" /></p>
<p>Things like the app identifier, app name, app icon, images, fonts, etc. are all defined or referenced in the SDK-style project <em>(.csproj)</em> file of a MAUI app project. This is typically done between <code>&lt;PropertyGroup&gt;</code> and <code>&lt;ItemGroup&gt;</code> tags:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Display name --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ApplicationTitle</span>&gt;</span>MauiSamples<span class="hljs-tag">&lt;/<span class="hljs-name">ApplicationTitle</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- App Identifier --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ApplicationId</span>&gt;</span>com.companyname.mauisamples<span class="hljs-tag">&lt;/<span class="hljs-name">ApplicationId</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- App Icon --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiIcon</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\AppIcon\appicon.svg"</span> <span class="hljs-attr">ForegroundFile</span>=<span class="hljs-string">"Resources\AppIcon\appiconfg.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#512BD4"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Splash Screen --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiSplashScreen</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Splash\splash.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#512BD4"</span> <span class="hljs-attr">BaseSize</span>=<span class="hljs-string">"128,128"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Images --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiImage</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Images\*"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Custom Fonts --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiFont</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Fonts\*"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Raw Assets (also remove the "Resources\Raw" prefix) --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiAsset</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Raw\**"</span> <span class="hljs-attr">LogicalName</span>=<span class="hljs-string">"%(RecursiveDir)%(Filename)%(Extension)"</span>/&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<blockquote>
<p><strong>Note:</strong> To keep things simple, I'm only showing relevant bits of the <em>.csproj</em> file</p>
</blockquote>
<p>This structure assumes that there's ever only a single source of truth when it comes to icons, images, styles and so on, which in this context usually is the <em>Resources</em> folder. For single-client apps, this is perfect, but for white labeling this let's us face a couple of problems. For example, how would we replace the app icons or fonts for each client?</p>
<p>We can work with this, but we need to make some changes, that I will describe in the following paragraphs.</p>
<h2 id="heading-resources-structure">Resources Structure</h2>
<p>For white labeling, I suggest using a slightly altered project structure with an additional level of subfolders right under <em>Resources</em> with the usual folders for icons, fonts, images and so on separated by client:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720598242306/97fcc8de-ac2d-4a47-9ef2-89f8613c4a1d.png" alt class="image--center mx-auto" /></p>
<p>The main benefit of this structure is that we can keep all the client-specific assets separate without losing the logical grouping we're used to from other MAUI apps.</p>
<p>Now, this will not do anything on its own yet. In a MAUI single project app, the different asset types have their own specific build actions, as we've already seen above:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- excerpt from .csproj file --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- App Icon --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiIcon</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\AppIcon\appicon.svg"</span> <span class="hljs-attr">ForegroundFile</span>=<span class="hljs-string">"Resources\AppIcon\appiconfg.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#512BD4"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Splash Screen --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiSplashScreen</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Splash\splash.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#512BD4"</span> <span class="hljs-attr">BaseSize</span>=<span class="hljs-string">"128,128"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Images --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiImage</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Images\*"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Custom Fonts --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiFont</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Fonts\*"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Raw Assets (also remove the "Resources\Raw" prefix) --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiAsset</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Raw\**"</span> <span class="hljs-attr">LogicalName</span>=<span class="hljs-string">"%(RecursiveDir)%(Filename)%(Extension)"</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
</code></pre>
<p>Now, you might be asking yourself <em>"but, how do I get the client-specific resources in there?"</em>. Well, we could of course replace the paths to each resource subfolder each time we build the app for a different client. This, however, would quickly become a massive hassle and wouldn't work with automated build pipelines.</p>
<p>A common solution to this type of problem is to use different build configurations, two for each client (one <em>Debug</em> and one <em>Release</em> configuration) and then use build <em>conditions</em> to select the correct set of resources to be included in the build process. We'll have a look at that next before coming back to using the correct assets.</p>
<h2 id="heading-build-configurations">Build Configurations</h2>
<p>To add new build configurations, we need to select the drop down with the active build configuration:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720600037466/d929ed24-875b-4f9d-8ef4-d37eb615a012.png" alt class="image--center mx-auto" /></p>
<p>In the drop down that appears, we then select "Configuration Manager", which opens the following dialog:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720600157889/24de12b1-004f-4d98-a4b4-3d2076a0da00.png" alt class="image--center mx-auto" /></p>
<p>Now, we select the "Active solution configuration" drop down and then hit "New", which will open the "New Solution Configuration" dialog:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720600277630/3badab97-36d2-46d9-9396-732f3129fbaa.png" alt class="image--center mx-auto" /></p>
<p>We can now provide a name to the new configuration and copy the settings from an existing one. We also need to create new project configurations <em>(make sure to tick the checkbox)</em>. When we're done, we hit "OK" and come back to the "Configuration Manager" dialog.</p>
<p>Now, we need to select the newly created solution configuration and select the companion project configuration that we created, so that it looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720600597077/45337367-f9ea-40b8-bcec-26fe38516257.png" alt class="image--center mx-auto" /></p>
<p>We repeat these steps for every client and for both <em>Debug</em> and <em>Release</em> configurations. Provided that we use two clients <em>(ClientA, ClientB)</em> and a <em>default</em> configuration in this example, we will end up with the following six configurations:</p>
<ul>
<li><p>Debug</p>
</li>
<li><p>Debug-ClientA</p>
</li>
<li><p>Debug-ClientB</p>
</li>
<li><p>Release</p>
</li>
<li><p>Release-ClientA</p>
</li>
<li><p>Release-ClientB</p>
</li>
</ul>
<p>Note how these conditions also get added to your project's <em>.csproj</em> file:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">TargetFrameworks</span>&gt;</span>net8.0-android;net8.0-ios;net8.0-maccatalyst<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFrameworks</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- skipping other properties --&gt;</span>

    <span class="hljs-comment">&lt;!-- new configurations we just added in the Configuration Manager --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Configurations</span>&gt;</span>Debug;Release;Debug-ClientA;Debug-ClientB;Release-ClientA;Release-ClientB<span class="hljs-tag">&lt;/<span class="hljs-name">Configurations</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
</code></pre>
<blockquote>
<p><strong>Note:</strong> The <em>.sln</em> file will also get, too, some new GUIDs and configurations are added in there, as well. <strong><em>Please do not meddle with this file, as it's very easy to break the entire solution this way.</em></strong></p>
</blockquote>
<p>Now, we can use these configurations together with build conditions in order to decide which assets to include in the build process.</p>
<h2 id="heading-build-conditions-and-properties">Build Conditions and Properties</h2>
<p>When working with a default MAUI app template, we will eventually come across some build conditions. If you have a little bit of experience with MAUI, you will have seen lines like this in the <em>.csproj</em> file of other apps plenty of times already:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">TargetFrameworks</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$([MSBuild]::IsOSPlatform('windows'))"</span>&gt;</span>$(TargetFrameworks);net8.0-windows10.0.19041.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFrameworks</span>&gt;</span>
</code></pre>
<p>The <code>Condition</code> in the above example will ensure that the <em>Windows</em> target is only used when the operating system on which the solution is compiled is <em>Windows</em>, because MAUI apps for Windows cannot be compiled on a Mac.</p>
<p>We can use this same mechanism also for our white labeling purposes, which is what we created the build configurations for in the previous step. We can filter the assets to be included using the active build configuration as follows:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"'$(Configuration)' == 'Debug' OR '$(Configuration)' == 'Release'"</span>&gt;</span>
   <span class="hljs-comment">&lt;!-- define default properties --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$(Configuration.EndsWith('ClientA'))"</span>&gt;</span>
   <span class="hljs-comment">&lt;!-- define ClientA properties --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$(Configuration.EndsWith('ClientB'))"</span>&gt;</span>
   <span class="hljs-comment">&lt;!-- define ClientA properties --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
</code></pre>
<p>Above, we use the <code>Configuration</code> build property, which will have the value of one of the six build configurations we created earlier <em>(e.g. "Debug", "Debug-ClientA" or "Release-ClientB", ...)</em>. The value of this property can be evaluated. There are even methods that can be called, such as <code>EndsWith()</code> to check for a substring, e.g. <code>Condition="$(Configuration.EndsWith('ClientB'))"</code>.</p>
<p>Now, if we would define the properties and project items all in the <em>.csproj</em> file, things would get messy pretty quickly. Having a clean, manageable structure is key when it comes to rebranding. Therefore, instead of having all client-specific data in one massive <em>.csproj</em> file, we can split this into separate project property <em>(.props)</em> files, one for each of the clients, which for ClientA could look like this, for example:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- ClientA.props file --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Project</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Assembly Name --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ApplicationAssemblyName</span>&gt;</span>SuperDuperApp<span class="hljs-tag">&lt;/<span class="hljs-name">ApplicationAssemblyName</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Display name --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ApplicationTitle</span>&gt;</span>Super Duper App<span class="hljs-tag">&lt;/<span class="hljs-name">ApplicationTitle</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- App Identifier --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ApplicationId</span>&gt;</span>com.ClientA.SuperDuperApp<span class="hljs-tag">&lt;/<span class="hljs-name">ApplicationId</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- App Icon --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiIcon</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\ClientA\AppIcon\appicon.svg"</span> <span class="hljs-attr">ForegroundFile</span>=<span class="hljs-string">"Resources\ClientA\AppIcon\appiconfg.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#123456"</span> <span class="hljs-attr">ForegroundScale</span>=<span class="hljs-string">"0.65"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Splash Screen --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiSplashScreen</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\ClientA\Splash\splash.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#123456"</span> <span class="hljs-attr">BaseSize</span>=<span class="hljs-string">"128,128"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Images --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiImage</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\ClientA\Images\*"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Custom Fonts --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiFont</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\ClientA\Fonts\*"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Raw Assets (also remove the "Resources\Raw" prefix) --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">MauiAsset</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\ClientA\Raw\**"</span> <span class="hljs-attr">LogicalName</span>=<span class="hljs-string">"%(RecursiveDir)%(Filename)%(Extension)"</span>/&gt;</span>
    <span class="hljs-comment">&lt;!-- Privacy Manifest for iOS --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">BundleResource</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'"</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Platforms\iOS\PrivacyInfo.xcprivacy"</span> <span class="hljs-attr">LogicalName</span>=<span class="hljs-string">"PrivacyInfo.xcprivacy"</span>/&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>This file contains all the client-specific properties and items that should be included only when building the app for ClientA. It is referencing the files and folders from the <em>Resources/ClientA</em> subfolder for the app icon, splash screen, etc., and it also defines the client-specific app title and package identifier <em>(= ApplicationId)</em>.</p>
<p>If we do this for all clients, we can then include the correct <em>.props</em> file based on the selected configuration in our <em>.csproj</em> file using our build conditions:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">Import</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"'$(Configuration)' == 'Debug' OR '$(Configuration)' == 'Release'"</span> <span class="hljs-attr">Project</span>=<span class="hljs-string">"Resources\Default\default.props"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Import</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$(Configuration.EndsWith('ClientA'))"</span> <span class="hljs-attr">Project</span>=<span class="hljs-string">"Resources\ClientA\ClientA.props"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Import</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$(Configuration.EndsWith('ClientB'))"</span> <span class="hljs-attr">Project</span>=<span class="hljs-string">"Resources\ClientB\ClientB.props"</span> /&gt;</span>

  <span class="hljs-comment">&lt;!-- this is required to give the output files (.dll) a different assembly name per client --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">AssemblyName</span>&gt;</span>$(ApplicationAssemblyName)<span class="hljs-tag">&lt;/<span class="hljs-name">AssemblyName</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>This just looks so much cleaner than having one massive <em>.csproj</em> file with lots of client-specific stuff in it, don't you agree? It also makes the onboarding of new clients more straightforward.</p>
<h2 id="heading-intermediate-build">Intermediate Build</h2>
<p>If we would build and deploy each of the three different client apps to a device now, we will see three different installed apps each with a unique app icon and name, all based on the same code base:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720605397731/d907e780-b42b-492f-a8f1-f069ac8017db.png" alt class="image--center mx-auto" /></p>
<p>This is already pretty cool! However, we're not done yet, because if we would run each of the apps, apart from the app icon and app name, their content would still look identical. This is because we haven't made any other changes so far.</p>
<p><strong>Important:</strong> For this to work, the <em>App.xaml</em> file needs to reference valid paths to the <em>Colors.xaml</em> and <em>Styles.xaml</em> resource dictionaries. However, in our setup above, these files have been moved to a different location and we haven't modified the <em>App.xaml</em> file. <strong>However, I don't actually want to change the <em>App.xaml</em> file at all, it should remain exactly the way it is</strong>. We will take a look at this next.</p>
<h1 id="heading-styles-and-colors">Styles and Colors</h1>
<p>In a MAUI app, the <em>App.xaml</em> file typically references the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/resource-dictionaries">resource dictionaries</a> that contain definitions for the colors and styles like this:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Application</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
             <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
             <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiWhiteLabelling.App"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Application.Resources</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary.MergedDictionaries</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary</span> <span class="hljs-attr">Source</span>=<span class="hljs-string">"Resources/Styles/Colors.xaml"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary</span> <span class="hljs-attr">Source</span>=<span class="hljs-string">"Resources/Styles/Styles.xaml"</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">ResourceDictionary.MergedDictionaries</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ResourceDictionary</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Application.Resources</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Application</span>&gt;</span>
</code></pre>
<p>Note that there is a relative path to each of the XAML resource dictionaries. This path is problematic, because, unfortunately, XAML does not support conditional compilation. Therefore, we cannot provide different paths for different clients and unfortunately, MAUI also doesn't allow us to set the <code>Source</code> property from the code-behind, either.</p>
<p>So, how can we reference the correct colors and styles from our separate client asset subfolders? We shouldn't have to modify the <em>App.xaml</em> file every time we build an app for a specific client. That's too risky and only makes things complicated.</p>
<p>A common <em>Xamarin.Forms</em> way for this was to load these resource dictionaries <em>dynamically</em> during the app start. However, this introduces a new problem: The <code>StaticResource</code> markup will not be usable, because the styles are not known at the time of construction. Hence, we would have to resort to using the <code>DynamicResource</code> markup instead, which would introduce a performance penalty and we would lose the preview capabilities of the XAML editor <em>(at least when using Visual Studio on Windows)</em>.</p>
<p>💡 However, fret not, I found a solution for this, as well. Since we have the <em>.props</em> files for each client in place already, we can use this mechanism to also copy some assets like the XAML resource dictionaries to a shared location, more specifically the location where the <em>App.xaml</em> file expects those resources to be: the <em>Resources/Styles</em> subfolder.</p>
<p>All we need to do for this to work is to update the <em>.props</em> files and add the <code>InitialTargets</code> attribute to the <code>&lt;Project&gt;</code> tag that points to a named <code>Target</code> with an action that copies the required files to the correct location:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">InitialTargets</span>=<span class="hljs-string">"CopyResourceFiles"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- skipping PropertyGroups and ItemGroups --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Target</span> <span class="hljs-attr">Name</span>=<span class="hljs-string">"CopyResourceFiles"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Copy</span> <span class="hljs-attr">SourceFiles</span>=<span class="hljs-string">"Resources\ClientA\Styles\Styles.xaml;Resources\ClientA\Styles\Colors.xaml"</span> <span class="hljs-attr">DestinationFolder</span>=<span class="hljs-string">"Resources\Styles"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Target</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>Basically, with this approach, we replace the two resource dictionary files every time we select a different build configuration. This is fine, because it's very fast and any build targets included in the <code>InitialTargets</code> will run <em>before</em> compilation. This is what allows us to still use the <code>StaticResource</code> markup, thus avoiding sweeping changes and performance hits.</p>
<p>I recommend adding the paths of the shared <em>Styles.xaml</em> and <em>Colors.xaml</em> file locations under <em>Resources/Styles</em> to the <em>.gitignore</em> file of your repository, so that they don't get included in any commits, similar to any auto-generated files that also don't need to be included, either:</p>
<pre><code class="lang-plaintext"># Explicit file exclusions
MauiWhiteLabelling/Resources/Styles/Colors.xaml
MauiWhiteLabelling/Resources/Styles/Styles.xaml
</code></pre>
<p>Styles and colors: Check ✅.</p>
<p>Let's have a look at custom fonts before finally running the app for the first time.</p>
<h1 id="heading-fonts">Fonts</h1>
<p>In order to use custom fonts, we usually need to add the font files to the project and set the build action to <code>MauiFont</code>. Normally, this is already the case, as long as the font files are located in the directory specified for the <code>&lt;MauiFont&gt;</code> tag, e.g.:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">MauiFont</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Fonts\*"</span>/&gt;</span>
</code></pre>
<p>For white labeling to work, a simple trick to use different fonts per client is to add the font files using the <em>exact same name</em> for all clients inside the client-specific folders and then to register them using the <em>exact same alias:</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720691150063/a289fa98-4fef-4ce3-a1de-6f874ee7dd4a.png" alt class="image--center mx-auto" /></p>
<p>In the custom <em>.props</em> file we already specified the path to the client-specific font files:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- ClientA.props --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">MauiFont</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\ClientA\Fonts\*"</span>/&gt;</span>
</code></pre>
<p>This way, we can reference a font directly by its shared alias and the correct client font file will always be used. We then don't have to specify which client we're registering which font for, we only need to modify the font registration in the <em>MauiProgram.cs</em> file as follows:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MauiApp <span class="hljs-title">CreateMauiApp</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> builder = MauiApp.CreateBuilder();
    builder
        .UseMauiApp&lt;App&gt;()
        .UseMauiCommunityToolkit()
        .ConfigureFonts(fonts =&gt;
        {
            <span class="hljs-comment">// use the same font filename and alias for every client</span>
            fonts.AddFont(<span class="hljs-string">"FontRegular.ttf"</span>, <span class="hljs-string">"FontRegular"</span>);
            fonts.AddFont(<span class="hljs-string">"FontSemibold.ttf"</span>, <span class="hljs-string">"FontSemibold"</span>);
        });

    <span class="hljs-keyword">return</span> builder.Build();
}
</code></pre>
<p>As you can see, we don't use the actual font name here, we just use a shared naming convention for the font file and alias.</p>
<p>This approach is not mandatory, the <code>FontFamily</code> can also be specified in a style definition in the client-specific <em>Styles.xaml</em> resource dictionary using the actual names of the fonts. However the approach described here will make our lives <em>a lot easier</em>, since it allows us to use fonts by a shared naming convention everywhere in the app equally. We can simply specify the <code>FontFamily</code> like we normally would anywhere in our app and see the text in a different font depending on the selected client configuration:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
    <span class="hljs-attr">Text</span>=<span class="hljs-string">"Welcome!"</span>
    <span class="hljs-attr">FontFamily</span>=<span class="hljs-string">"FontSemibold"</span>
    <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"36"</span> /&gt;</span>
</code></pre>
<p>In the next section we'll see what it all looks like when we actually run the app after quickly setting up a page that uses the client-specific resources we've supplied.</p>
<h1 id="heading-running-the-app">Running the app</h1>
<p>Let's put it all together with a simple <em>ContentPage</em> that has the client's app logo, a label and a button, with a custom font and a client-specific background color:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage</span>
  <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiWhiteLabelling.Views.MainPage"</span>
  <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
  <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
  <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"{StaticResource Primary}"</span>
  <span class="hljs-attr">Shell.BackgroundColor</span>=<span class="hljs-string">"{StaticResource Primary}"</span>
  <span class="hljs-attr">Shell.NavBarIsVisible</span>=<span class="hljs-string">"False"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">VerticalStackLayout</span>
      <span class="hljs-attr">Padding</span>=<span class="hljs-string">"20,40"</span>
      <span class="hljs-attr">Spacing</span>=<span class="hljs-string">"40"</span>
      <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Start"</span>
      <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Image</span>
        <span class="hljs-attr">Source</span>=<span class="hljs-string">"logo.png"</span>
        <span class="hljs-attr">WidthRequest</span>=<span class="hljs-string">"80"</span>
        <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"80"</span>
        <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
        <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
        <span class="hljs-attr">Text</span>=<span class="hljs-string">"Welcome!"</span>
        <span class="hljs-attr">FontFamily</span>=<span class="hljs-string">"FontSemibold"</span>
        <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"36"</span>
        <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
        <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">Text</span>=<span class="hljs-string">"Press me"</span> <span class="hljs-attr">Clicked</span>=<span class="hljs-string">"Button_OnClicked"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">VerticalStackLayout</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage</span>&gt;</span>
</code></pre>
<p>Running this app now will yield three different results for the different client configurations, which is exactly what we wanted:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720768955727/f1353dd1-d22f-434a-9f01-97d4cb702d3b.png" alt class="image--center mx-auto" /></p>
<p>🎉 Isn't this awesome? With a simple setup and a few tricks, we managed to white-label an app for three different client configurations. Rock and roll 🤘!</p>
<p>However, sometimes, clients also want some unique or custom behavior. Now, that can be done just as well. Let me show you how in the next section.</p>
<h1 id="heading-custom-behavior">Custom Behavior</h1>
<p>We can create custom behavior for every client, e.g. by using compile constants or feature toggles. Compile constants can be defined in the client <em>.props</em> file as follows:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">InitialTargets</span>=<span class="hljs-string">"CopyResourceFiles"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- skipping other stuff for brevity --&gt;</span>
    <span class="hljs-comment">&lt;!-- Compilation Constants for Client A --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">DefineConstants</span>&gt;</span>$(DefineConstants);CLIENT_A<span class="hljs-tag">&lt;/<span class="hljs-name">DefineConstants</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- skipping assets for brevity --&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>With this in place, we can then instruct the compiler to modify the behavior of a method or anything else (even enable or disable entire features altogether) for a specific client. To keep things simple, I'll only show a small example here. The following code shows an event handler for a button's <code>Clicked</code> event on the MainPage:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MainPage</span> : <span class="hljs-title">ContentPage</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainPage</span>(<span class="hljs-params"></span>)</span>
    {
        InitializeComponent();
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Button_OnClicked</span>(<span class="hljs-params"><span class="hljs-keyword">object</span>? sender, EventArgs e</span>)</span>
    {
<span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> DEFAULT_APP</span>
        <span class="hljs-keyword">await</span> DisplayAlert(<span class="hljs-string">"Default App"</span>, <span class="hljs-string">"This is the default app"</span>, <span class="hljs-string">"OK"</span>);
<span class="hljs-meta">#<span class="hljs-meta-keyword">elif</span> CLIENT_A</span>
        <span class="hljs-keyword">await</span> DisplayAlert(<span class="hljs-string">"Client A"</span>, <span class="hljs-string">"This is client A"</span>, <span class="hljs-string">"OK"</span>);
<span class="hljs-meta">#<span class="hljs-meta-keyword">elif</span> CLIENT_B</span>
        <span class="hljs-keyword">await</span> DisplayAlert(<span class="hljs-string">"Client B"</span>, <span class="hljs-string">"This is client B"</span>, <span class="hljs-string">"OK"</span>);
<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span>
    }
}
</code></pre>
<p>When the button is clicked, it will show a different text message based on the selected client configuration:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720769212036/d6e77c69-7619-49c0-b7ee-4f847b2d0291.png" alt class="image--center mx-auto" /></p>
<p>✨Whoohoo, we have a fully customized app!</p>
<h1 id="heading-summary-and-next-steps">Summary and next steps</h1>
<p>I'm super excited about how easy it was to white-label a .NET MAUI app once I figured out all the necessary steps. I've demonstrated that it's possible to rebrand an app for different clients while maintaining the same code base and without having to frequently modify files and configurations.</p>
<p>A next step would be to include client-specific translation resources using <em>.resx</em> files, which could either be achieved by also copying the resource files into a single, shared location using the <code>InitialTargets</code> step or by using some kind of lookup mechanism (e.g. with a custom markup extension). If I find the time, I might investigate how to achieve this in the most straightforward way I can think of.</p>
<blockquote>
<p>If you're wondering how I've set the client-specific status bar color on Android without having modified or included the <em>AndroidManifest.xml</em> file in my demonstration, check out the <a target="_blank" href="https://github.com/ewerspej/MauiWhiteLabelling/blob/main/MauiWhiteLabelling/App.xaml.cs"><em>App.xaml.cs</em></a> file in the repository that goes along with this post.</p>
</blockquote>
<p>I hope this is useful to you. If you enjoyed this blog post, then you may follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters/">LinkedIn</a>, subscribe to this <a target="_blank" href="https://blog.ewers-peters.de/">blog</a> and star the <a target="_blank" href="https://github.com/ewerspej/MauiWhiteLabelling">GitHub repository</a> for this post as well as my <a target="_blank" href="https://github.com/ewerspej/maui-samples/">MAUI Samples</a> repository, so you don't miss out on any future posts and developments.</p>
<p>Thanks for reading until here, and remember: sharing is caring!</p>
<h2 id="heading-attributions"><strong>Attributions</strong></h2>
<p><em>MauiWhiteLabelling</em> project created using Matt Lacey's <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=MattLaceyLtd.MauiAppAccelerator">MAUI App Accelerator</a>.</p>
<p><em>Jar with while label</em> image generated using Microsoft Copilot/DALL-E.</p>
]]></content:encoded></item><item><title><![CDATA[Easily add a video reel to your MAUI app (like Instagram)]]></title><description><![CDATA[Introduction
Social media apps like Instagram use full-page scrollable video controls called "reels" (named after the device used to store film - or garden hoses) which allow the user to swipe or scroll up and down in order to switch between videos (...]]></description><link>https://blog.ewers-peters.de/maui-video-reel</link><guid isPermaLink="true">https://blog.ewers-peters.de/maui-video-reel</guid><category><![CDATA[instagram]]></category><category><![CDATA[reels]]></category><category><![CDATA[#maui]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[User Interface]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Wed, 15 May 2024 08:32:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715761226824/743882bb-d579-43fa-a9a3-624cf033738f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Social media apps like Instagram use full-page scrollable video controls called "reels" <em>(named after the device used to store film - or garden hoses)</em> which allow the user to swipe or scroll up and down in order to switch between videos <em>(or other content)</em>.</p>
<p>In this blog post, I will demonstrate how you can easily add a feature like a video reel to your own .NET MAUI app in a few simple steps. The idea is that we can scroll up and down and the video that becomes visible will start playing while the previously playing video gets stopped. All we need for this is a <em>CollectionView</em> and the <a target="_blank" href="https://learn.microsoft.com/dotnet/communitytoolkit/maui/views/mediaelement">MediaElement</a> from the <em>MAUI Community Toolkit (MCT)</em>.</p>
<p>After defining the model, we'll first have a look at how we can accomplish the desired layout, which is a full-page view that allows to swipe up and down and always stops at the next element. Afterwards, we'll implement the automatic play/stop functionality.</p>
<blockquote>
<p>This post is based on a <a target="_blank" href="https://stackoverflow.com/a/78408360/4308455">Stack Overflow answer</a> that I recently wrote.</p>
</blockquote>
<p>As always, you can have a look at the full code in the <a target="_blank" href="https://github.com/ewerspej/maui-samples">sample repository</a> on GitHub.</p>
<p>Let's do it!</p>
<h1 id="heading-model">Model</h1>
<p>The model for the reel is quite simple, we just need to create a <code>VideoModel</code> with a few properties, e.g. <code>Title</code>, <code>VideoUri</code> and <code>IsPlaying</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> class <span class="hljs-title">VideoModel</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> title, <span class="hljs-keyword">string</span> videoUri</span>) : ObservableObject</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; } = title;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> VideoUri { <span class="hljs-keyword">get</span>; } = videoUri;

    [<span class="hljs-meta">ObservableProperty</span>]
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> _isPlaying;
}
</code></pre>
<p>The <code>IsPlaying</code> property is an <a target="_blank" href="https://blog.ewers-peters.de/introduction-to-mvvm-source-generators-for-dotnet">auto-generated</a> observable property, which we will need later on to control the automatic play/stop functionality.</p>
<h1 id="heading-viewmodel">ViewModel</h1>
<p>In the ViewModel, we can define several instances of <code>VideoModel</code> using some sample video files that are stored locally in the repository, like so:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ReelViewModel</span> : <span class="hljs-title">ObservableObject</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> FrogVideo = <span class="hljs-string">"https://github.com/ewerspej/maui-samples/blob/main/assets/frog.mp4?raw=true"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> BuckVideo = <span class="hljs-string">"https://github.com/ewerspej/maui-samples/blob/main/assets/bigbuckbunny.mp4?raw=true"</span>;

    [<span class="hljs-meta">ObservableProperty</span>]
    <span class="hljs-keyword">private</span> ObservableCollection&lt;VideoModel&gt; _videos;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ReelViewModel</span>(<span class="hljs-params"></span>)</span>
    {
        Videos =
        [<span class="hljs-meta">
            new VideoModel(<span class="hljs-meta-string">"First"</span>, FrogVideo),
            new VideoModel(<span class="hljs-meta-string">"Second"</span>, BuckVideo),
            new VideoModel(<span class="hljs-meta-string">"Third"</span>, FrogVideo),
            new VideoModel(<span class="hljs-meta-string">"Fourth"</span>, BuckVideo),
            new VideoModel(<span class="hljs-meta-string">"Fifth"</span>, FrogVideo),
            new VideoModel(<span class="hljs-meta-string">"Sixth"</span>, BuckVideo)
        </span>];
    }
}
</code></pre>
<p>Later on, we can then bind to the <code>Videos</code> collection in XAML.</p>
<h1 id="heading-basic-layout">Basic Layout</h1>
<p>In order to create the reel, we first need to add a <em>CollectionView</em> inside a <em>Grid</em>. The <em>CollectionView</em> will host the <code>VideoModel</code> instances. We want only a single item to be visible at a time, so we'll add a <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/user-interface/controls/collectionview/layout#vertical-list">LinearItemsLayout</a> and give the layout in the DataTemplate the <strong><em>same</em></strong><code>HeightRequest</code> as the <em>CollectionView</em>. In order to make the scrolling of the <em>CollectionView</em> stop at the next element, we'll also need to use <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/user-interface/controls/collectionview/scrolling#snap-points">snap points</a>.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">CollectionView</span>
    <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"500"</span>
    <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Fill"</span>
    <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
    <span class="hljs-attr">ItemsSource</span>=<span class="hljs-string">"{Binding Videos}"</span>
    <span class="hljs-attr">Scrolled</span>=<span class="hljs-string">"ItemsView_OnScrolled"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">CollectionView.ItemsLayout</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">LinearItemsLayout</span>
        <span class="hljs-attr">Orientation</span>=<span class="hljs-string">"Vertical"</span>
        <span class="hljs-attr">SnapPointsType</span>=<span class="hljs-string">"MandatorySingle"</span>
        <span class="hljs-attr">SnapPointsAlignment</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">CollectionView.ItemsLayout</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">CollectionView.ItemTemplate</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">DataTemplate</span> <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"models:VideoModel"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
          <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"500"</span>
          <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Fill"</span>
          <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"LightGreen"</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
            <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Title}"</span>
            <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
            <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
            <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Title"</span> /&gt;</span>

        <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">DataTemplate</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">CollectionView.ItemTemplate</span>&gt;</span>

  <span class="hljs-tag">&lt;/<span class="hljs-name">CollectionView</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
</code></pre>
<p>The result should something like this and we can scroll back and forth between the elements, with always only a single item visible at a time:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNWkxNnMzMmM2dmZrc2Q5bmpjemE2MXliZ3A4enVxeHZ0Z21uNjhpNyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/YOon2PAVhJlyFbSQYM/giphy.gif">https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNWkxNnMzMmM2dmZrc2Q5bmpjemE2MXliZ3A4enVxeHZ0Z21uNjhpNyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/YOon2PAVhJlyFbSQYM/giphy.gif</a></div>
<p> </p>
<p>That's already pretty neat. Next, let's add the video player and the automatic play/stop functionality.</p>
<h1 id="heading-automatic-playstop">Automatic Play/Stop</h1>
<p>Before we can implement the play/stop functionality, we need to install the <a target="_blank" href="https://www.nuget.org/packages/CommunityToolkit.Maui.MediaElement">CommunityToolkit.Maui.MediaElement</a> package from <em>nuget.org</em> and initialize the <em>MediaElement</em> in our <em>MauiProgram</em> class before we can use it:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> builder = MauiApp.CreateBuilder();
builder
    .UseMauiApp&lt;App&gt;()
    .UseMauiCommunityToolkitMediaElement() <span class="hljs-comment">// add this line</span>
</code></pre>
<p>In order to automatically play a video that is hosted in a <em>MediaElement</em>, we need to work with a little trick. Since the <em>MediaElement</em> doesn't expose any bindable and settable <code>IsPlaying</code> property, we need to add that ourselves by extending the class with a <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/bindable-properties">BindableProperty</a>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ExtendedMediaElement</span> : <span class="hljs-title">MediaElement</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> IsPlaying
    {
        <span class="hljs-keyword">get</span> =&gt; (<span class="hljs-keyword">bool</span>)GetValue(IsPlayingProperty);
        <span class="hljs-keyword">set</span> =&gt; SetValue(IsPlayingProperty, <span class="hljs-keyword">value</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> BindableProperty IsPlayingProperty = BindableProperty.Create(<span class="hljs-keyword">nameof</span>(IsPlaying), <span class="hljs-keyword">typeof</span>(<span class="hljs-keyword">bool</span>), <span class="hljs-keyword">typeof</span>(ExtendedMediaElement), <span class="hljs-literal">false</span>, propertyChanged: OnIsPlayingPropertyChanged);

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnIsPlayingPropertyChanged</span>(<span class="hljs-params">BindableObject bindable, <span class="hljs-keyword">object</span> oldValue, <span class="hljs-keyword">object</span> newValue</span>)</span>
    {
        <span class="hljs-keyword">var</span> mediaElement = (ExtendedMediaElement)bindable;

        <span class="hljs-keyword">if</span> (newValue <span class="hljs-keyword">is</span> <span class="hljs-literal">true</span>)
        {
            mediaElement.Play();
        }
        <span class="hljs-keyword">else</span>
        {
            mediaElement.Stop();
        }
    }
}
</code></pre>
<p>By using the <code>OnIsPlayingPropertyChanged</code> event handler, we can decide whether to play or stop the video whenever the <code>IsPlaying</code> property gets updated (via a binding).</p>
<p>With this in place, we can now add our <em>ExtendedMediaElement</em> to the XAML:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">DataTemplate</span> <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"models:VideoModel"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
    <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"{Binding Height, Source={x:Reference Reels}}"</span>
    <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Fill"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">media:ExtendedMediaElement</span>
      <span class="hljs-attr">Aspect</span>=<span class="hljs-string">"Fill"</span>
      <span class="hljs-attr">Source</span>=<span class="hljs-string">"{Binding VideoUri}"</span>
      <span class="hljs-attr">ShouldAutoPlay</span>=<span class="hljs-string">"False"</span>
      <span class="hljs-attr">ShouldLoopPlayback</span>=<span class="hljs-string">"True"</span>
      <span class="hljs-attr">ShouldShowPlaybackControls</span>=<span class="hljs-string">"False"</span>
      <span class="hljs-attr">IsPlaying</span>=<span class="hljs-string">"{Binding IsPlaying}"</span> /&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
      <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Title}"</span>
      <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
      <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
      <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Title"</span> /&gt;</span>

  <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">DataTemplate</span>&gt;</span>
</code></pre>
<p>Now, a video is only played and stopped whenever the <code>IsPlaying</code> property of the associated <code>VideoModel</code> instance changes.</p>
<p>However, we're not entirely done yet, one important part is still missing. We want the videos to automatically start and stop whenever an element is scrolled into and out of view. For this, we need to hook up an event handler for the <code>Scrolled</code> event of the <em>CollectionView</em>, where we then update the <code>IsPlaying</code> property of each item in the underlying collection:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">CollectionView</span>
  <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"{Binding Height, Source={x:Reference Reels}}"</span>
  <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Fill"</span>
  <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
  <span class="hljs-attr">ItemsSource</span>=<span class="hljs-string">"{Binding Videos}"</span>
  <span class="hljs-attr">Scrolled</span>=<span class="hljs-string">"ItemsView_OnScrolled"</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- ... --&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">CollectionView</span>&gt;</span>
</code></pre>
<p>The event handler lives in the code-behind of our page and iterates through the <code>Videos</code> collection to update the <code>IsPlaying</code> property of the <code>VideoModel</code> instances:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ReelPage</span> : <span class="hljs-title">ContentPage</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ReelViewModel _vm;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ReelPage</span>(<span class="hljs-params">ReelViewModel vm</span>)</span>
    {
        InitializeComponent();
        BindingContext = _vm = vm;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ItemsView_OnScrolled</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> sender, ItemsViewScrolledEventArgs e</span>)</span>
    {
        <span class="hljs-keyword">var</span> itemIndex = e.CenterItemIndex;

        _vm.Videos[itemIndex].IsPlaying = <span class="hljs-literal">true</span>;

        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> myModel <span class="hljs-keyword">in</span> _vm.Videos)
        {
            <span class="hljs-keyword">if</span> (myModel != _vm.Videos[itemIndex])
            {
                myModel.IsPlaying = <span class="hljs-literal">false</span>;
            }
        }
    }
}
</code></pre>
<p>Almost there. Now, the automatic play/stop should already be working. Let's touch up the XAML a little bit and have a look at the final result next.</p>
<h1 id="heading-final-full-page-layout">Final Full-Page Layout</h1>
<p>Last, but not least, in order to have the <em>CollectionView</em> take up the entire available space of the page, we can simply bind the <code>HeightRequest</code> of the <em>CollectionView</em> as well as the <em>Grid</em> in the <em>DataTemplate</em> to the rendered <code>Height</code> of the page to end up with the following final layout:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ContentPage</span>
  <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiSamples.Views.ReelPage"</span>
  <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
  <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
  <span class="hljs-attr">xmlns:models</span>=<span class="hljs-string">"clr-namespace:MauiSamples.Models"</span>
  <span class="hljs-attr">xmlns:viewModels</span>=<span class="hljs-string">"clr-namespace:MauiSamples.ViewModels"</span>
  <span class="hljs-attr">xmlns:media</span>=<span class="hljs-string">"clr-namespace:MauiSamples.CustomControls.Media"</span>
  <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"Reels"</span>
  <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"viewModels:ReelViewModel"</span>
  <span class="hljs-attr">Shell.NavBarIsVisible</span>=<span class="hljs-string">"False"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">CollectionView</span>
      <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"{Binding Height, Source={x:Reference Reels}}"</span>
      <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Fill"</span>
      <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
      <span class="hljs-attr">ItemsSource</span>=<span class="hljs-string">"{Binding Videos}"</span>
      <span class="hljs-attr">Scrolled</span>=<span class="hljs-string">"ItemsView_OnScrolled"</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">CollectionView.ItemsLayout</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">LinearItemsLayout</span>
          <span class="hljs-attr">Orientation</span>=<span class="hljs-string">"Vertical"</span>
          <span class="hljs-attr">SnapPointsType</span>=<span class="hljs-string">"MandatorySingle"</span>
          <span class="hljs-attr">SnapPointsAlignment</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">CollectionView.ItemsLayout</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">CollectionView.ItemTemplate</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">DataTemplate</span> <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"models:VideoModel"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
            <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"{Binding Height, Source={x:Reference Reels}}"</span>
            <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Fill"</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">media:ExtendedMediaElement</span>
              <span class="hljs-attr">Aspect</span>=<span class="hljs-string">"AspectFill"</span>
              <span class="hljs-attr">Source</span>=<span class="hljs-string">"{Binding VideoUri}"</span>
              <span class="hljs-attr">ShouldAutoPlay</span>=<span class="hljs-string">"False"</span>
              <span class="hljs-attr">ShouldLoopPlayback</span>=<span class="hljs-string">"True"</span>
              <span class="hljs-attr">ShouldShowPlaybackControls</span>=<span class="hljs-string">"False"</span>
              <span class="hljs-attr">IsPlaying</span>=<span class="hljs-string">"{Binding IsPlaying}"</span> /&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
              <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Title}"</span>
              <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
              <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
              <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Title"</span> /&gt;</span>

          <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">DataTemplate</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">CollectionView.ItemTemplate</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">CollectionView</span>&gt;</span>

  <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage</span>&gt;</span>
</code></pre>
<p>Now, when we run the app, we have a full-page reel control:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExN3JsOXY0cTcxaTQ3bXQ2cWtjNDgzaXg0eWJ0anVzMWw3bmhvMXo3ZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EEJz7teOtuJBC22nou/giphy.gif">https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExN3JsOXY0cTcxaTQ3bXQ2cWtjNDgzaXg0eWJ0anVzMWw3bmhvMXo3ZCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EEJz7teOtuJBC22nou/giphy.gif</a></div>
<p> </p>
<p>🏆 Fantastic, I'm excited about the result.</p>
<h1 id="heading-conclusions-and-next-steps">Conclusions and next steps</h1>
<p>As I demonstrated here, adding a scrollable video control similar to Instagram's reels to your .NET MAUI app is quite simple. A neat addition to this would be to make the entire page fullscreen, but that would be too much for a single blog post. The main focus here was supposed to be on how to combine the <em>CollectionView</em> and the <em>MediaElement</em> to create a reel and that's surprisingly easy to achieve.</p>
<blockquote>
<p><strong>Note:</strong> I didn't touch on any issues regarding caching or performance in this post. When the list of videos gets very long or when items are incrementally loaded, e.g. when using the infinite scroll capabilities of CollectionView, we might observe performance degradation, which should then be addressed separately.<br />Thanks to <a target="_blank" href="https://jfversluis.dev/">Gerald Versluis</a> for pointing this out.</p>
</blockquote>
<p>If you enjoyed this blog <a target="_blank" href="https://hashnode.com/@ewerspej">post</a>, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>GitHub repository</strong></a> for this post so you don't miss out on any future posts and developments. And remember that sharing is caring.</p>
<h2 id="heading-attributions"><strong>Attributions</strong></h2>
<p>Title photo by <a target="_blank" href="https://unsplash.com/@markusspiske?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Markus Spiske</a> on <a target="_blank" href="https://unsplash.com/photos/a-pair-of-sunglasses-on-a-table-1C202gyp2P0?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Add automatic route registration to your .NET MAUI app]]></title><description><![CDATA[Introduction
In December of 2023, I introduced the epj.RouteGenerator package which provides a facility to have route identifiers for .NET-based frontend applications auto-generated for you, taking away a few pain points with string-based route navig...]]></description><link>https://blog.ewers-peters.de/add-automatic-route-registration-to-your-net-maui-app</link><guid isPermaLink="true">https://blog.ewers-peters.de/add-automatic-route-registration-to-your-net-maui-app</guid><category><![CDATA[dotnet]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[sourcegenerators]]></category><category><![CDATA[navigation]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Fri, 16 Feb 2024 15:35:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1708090435324/af960263-03f5-42b7-a84c-91ec60ae442a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>In December of 2023, I introduced the <a target="_blank" href="https://github.com/ewerspej/epj.RouteGenerator">epj.RouteGenerator</a> package which provides a facility to have route identifiers for .NET-based frontend applications auto-generated for you, taking away a few pain points with string-based route navigation and maintenance hassles.</p>
<blockquote>
<p>This is a follow up post. You can find the original blog post <a target="_blank" href="https://blog.ewers-peters.de/introducing-route-generator-for-net">here</a>. If you're unfamiliar with Route Generator, please read that first. Gerald Versluis also made a great <a target="_blank" href="https://www.youtube.com/watch?v=XNLKyEPWqws">YouTube video</a> showing the Route Generator in action.</p>
</blockquote>
<p>Since then, one thing that I have been frequently asked was whether it would be possible to add automatic registration of routes to the route generator, as to further simplify and reduce the amount of manually written code in .NET apps.</p>
<p>Now, my intention is to keep the route generator independent from specific UI frameworks, so that it can be used with any kind of .NET-based application that uses routes for navigation. Therefore, in order to provide full support of automatic route registration, say in .NET MAUI, it would be required to do two things:</p>
<ol>
<li><p>Gather the type information, e.g. the fully qualified type name</p>
</li>
<li><p>Add a MAUI-specific layer with a registration method</p>
</li>
</ol>
<p>For now, I decided to focus on the first step only, because it will already make things a lot easier while remaining framework-independent. The second step is a little bit more tricky as the generator shouldn't have MAUI-specific knowledge. To overcome this limitation, creating another library that builds on top of <a target="_blank" href="https://github.com/ewerspej/epj.RouteGenerator"><em>epj.RouteGenerator</em></a>, for example something like <em>epj.RouteGenerator.Maui</em>, would be required. Writing such library that exposes all kinds of useful utility methods specifically target at .NET MAUI will require some time and design considerations.</p>
<p>Here are the good news though: The first step is already done with the latest preview version of the <em>Route Generator</em> <em>(1.0.1-alpha2 at the time of writing)</em>, which is already available for <a target="_blank" href="https://www.nuget.org/packages/epj.RouteGenerator/1.0.1-alpha2">download</a> on nuget.org.</p>
<p>Let's see what that means and how to use it 🤩.</p>
<h1 id="heading-routetype-map">Route/Type Map</h1>
<p>On top of the read-only <code>AllRoutes</code> collection, the generated <code>Routes</code> class now also exposes a read-only dictionary called <code>RouteTypeMap</code> which maps the routes to their respective <code>Type</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// &lt;auto-generated/&gt;</span>
<span class="hljs-keyword">using</span> System.Collections.ObjectModel;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">RouteGeneratorSample</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Routes</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> MainPage = <span class="hljs-string">"MainPage"</span>;
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> VolvoPage = <span class="hljs-string">"VolvoPage"</span>;
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> AudiPage = <span class="hljs-string">"AudiPage"</span>;

        <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> List&lt;<span class="hljs-keyword">string</span>&gt; allRoutes = <span class="hljs-keyword">new</span>()
        {
            MainPage,
            VolvoPage,
            AudiPage,
        };

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ReadOnlyCollection&lt;<span class="hljs-keyword">string</span>&gt; AllRoutes =&gt; allRoutes.AsReadOnly();

        <span class="hljs-comment">// NEW:</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Dictionary&lt;<span class="hljs-keyword">string</span>, Type&gt; routeTypeMap = <span class="hljs-keyword">new</span>()
        {
            { MainPage, <span class="hljs-keyword">typeof</span>(RouteGeneratorSample.MainPage) },
            { VolvoPage, <span class="hljs-keyword">typeof</span>(RouteGeneratorSample.Cars.VolvoPage) },
            { AudiPage, <span class="hljs-keyword">typeof</span>(RouteGeneratorSample.Cars.AudiPage) },
        };

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ReadOnlyDictionary&lt;<span class="hljs-keyword">string</span>, Type&gt; RouteTypeMap =&gt; routeTypeMap.AsReadOnly();
    }
}
</code></pre>
<p>Now, this type information can be used to register routes, for example in .NET MAUI, without the source generator requiring specific knowledge about the UI framework.</p>
<h1 id="heading-route-registration-in-net-maui">Route Registration in .NET MAUI</h1>
<p>Usually, we would need to register each route manually by typing out each registration call for every route and type. This is typically done in <em>AppShell.xaml.cs</em> similar to this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System.Diagnostics;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">RouteGeneratorSample</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AppShell</span> : <span class="hljs-title">Shell</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">AppShell</span>(<span class="hljs-params"></span>)</span>
    {
        InitializeComponent();

        Routing.RegisterRoute(<span class="hljs-keyword">nameof</span>(MainPage), <span class="hljs-keyword">typeof</span>(MainPage));
        Routing.RegisterRoute(<span class="hljs-keyword">nameof</span>(VolvoPage), <span class="hljs-keyword">typeof</span>(VolvoPage));
        Routing.RegisterRoute(<span class="hljs-keyword">nameof</span>(AudiPage), <span class="hljs-keyword">typeof</span>(AudiPage));
    }
}
</code></pre>
<p>With the new <code>Routes.RouteTypeMap</code> dictionary, we can now register routes <em>(semi-)</em> automatically - all you need to do is iterate over the map like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> route <span class="hljs-keyword">in</span> Routes.RouteTypeMap)
{
    Routing.RegisterRoute(route.Key, route.Value);
}
</code></pre>
<p>💪 That's it! No more manual typing of each route name and type for registration.</p>
<h1 id="heading-extra-routes">Extra Routes</h1>
<p>What about extra routes? For example, if a route name doesn't match a type name, then what? <em>Route Generator</em> has got you covered, as well! The <code>[ExtraRoute]</code> attribute now features a second argument which can be used to specify the type that should be used for the route using <code>typeof(ClassName)</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">RouteGeneratorSample</span>;

<span class="hljs-comment">// pass the type for the route to the ExtraRoute attribute,</span>
<span class="hljs-comment">// this will make sure to include the type in the Routes.RouteTypeMap;</span>
<span class="hljs-comment">// the key will be "YetAnotherRoute" and the value the type of MainPage</span>

[<span class="hljs-meta">AutoRoutes(<span class="hljs-meta-string">"Page"</span>)</span>]
[<span class="hljs-meta">ExtraRoute(<span class="hljs-meta-string">"YetAnotherRoute"</span>, typeof(MainPage))</span>] 
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MauiProgram</span>
{
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Like this, the <code>Routes.YetAnotherRoute</code> identifier is used to associate a route to the <code>MainPage</code> in the example above. The resulting auto-generated <em>Routes.g.cs</em> class will then look as follows:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// &lt;auto-generated/&gt;</span>
<span class="hljs-keyword">using</span> System.Collections.ObjectModel;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">RouteGeneratorSample</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Routes</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> MainPage = <span class="hljs-string">"MainPage"</span>;
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> VolvoPage = <span class="hljs-string">"VolvoPage"</span>;
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> AudiPage = <span class="hljs-string">"AudiPage"</span>;
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> YetAnotherRoute = <span class="hljs-string">"YetAnotherRoute"</span>;

        <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> List&lt;<span class="hljs-keyword">string</span>&gt; allRoutes = <span class="hljs-keyword">new</span>()
        {
            MainPage,
            VolvoPage,
            AudiPage,
            YetAnotherRoute
        };

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ReadOnlyCollection&lt;<span class="hljs-keyword">string</span>&gt; AllRoutes =&gt; allRoutes.AsReadOnly();

        <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Dictionary&lt;<span class="hljs-keyword">string</span>, Type&gt; routeTypeMap = <span class="hljs-keyword">new</span>()
        {
            { MainPage, <span class="hljs-keyword">typeof</span>(RouteGeneratorSample.MainPage) },
            { VolvoPage, <span class="hljs-keyword">typeof</span>(RouteGeneratorSample.Cars.VolvoPage) },
            { AudiPage, <span class="hljs-keyword">typeof</span>(RouteGeneratorSample.Cars.AudiPage) },
            { YetAnotherRoute, <span class="hljs-keyword">typeof</span>(RouteGeneratorSample.MainPage) },
        };

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ReadOnlyDictionary&lt;<span class="hljs-keyword">string</span>, Type&gt; RouteTypeMap =&gt; routeTypeMap.AsReadOnly();
    }
}
</code></pre>
<p>Now, the <code>Routes.YetAnotherRoute</code> identifier can be used to navigate to the <code>MainPage</code> using <code>await Shell.Current.GoToAsync(Routes.YetAnotherRoute)</code> or similar.</p>
<blockquote>
<p>Please note that if you specify an extra route that doesn't match with an existing type name without explicitly specifying the target type, that extra route will not be included in the <code>Routes.RouteTypeMap</code>, because the type would be undefined. The Route Generator will emit a warning in this case.</p>
</blockquote>
<h1 id="heading-conclusions-and-next-steps"><strong>Conclusions and next steps</strong></h1>
<p>In this follow-up post I have demonstrated some of the further development possibilities for the Route Generator for .NET. Being able to register route names and types automatically can further reduce manually typed code and offering a way to facilitate this seemed like a wonderful idea, so that's what I did.</p>
<p>If you have feedback or suggestions for improvements or additional features, please don't hesitate to get in touch or post an issue in the GitHub repository.</p>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a> and subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> so you don't miss out on any future posts. If this is useful to you, maybe also consider starring the <a target="_blank" href="https://github.com/ewerspej/epj.RouteGenerator"><strong>GitHub repository of the Route Generator</strong></a>. Thank you very much 💖.</p>
]]></content:encoded></item><item><title><![CDATA[Introducing Route Generator for .NET]]></title><description><![CDATA[Introduction
Modern .NET applications have adopted the concept of routes for navigation. Usually, these routes are string-based, which has a couple of implications. One of the problems is the volatility of route names, which I am addressing in this b...]]></description><link>https://blog.ewers-peters.de/introducing-route-generator-for-net</link><guid isPermaLink="true">https://blog.ewers-peters.de/introducing-route-generator-for-net</guid><category><![CDATA[sourcegenerators]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[routes]]></category><category><![CDATA[navigation]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Tue, 19 Dec 2023 10:18:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702829964430/9154da3d-da17-4209-875f-a2df4b69e6a9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Modern .NET applications have adopted the concept of routes for navigation. Usually, these routes are string-based, which has a couple of implications. One of the problems is the volatility of route names, which I am addressing in this blog post.</p>
<p>A while ago, when working on a .NET MAUI application with some colleagues, I realized that I could simplify our job and at the same time gain more stability and confidence in our string-based route navigation by having route identifiers automatically generated for us. This is how the idea for the Route Generator was born, which I am going to introduce here today.</p>
<blockquote>
<p>Although this post and the sample project focus on .NET MAUI, the concepts and the Route Generator can be applied to different contexts and UI frameworks.</p>
</blockquote>
<p>First, we'll have a look at which problems the Route Generator is supposed to solve and then we'll see how simple it is to use the Route Generator in your own project.</p>
<p>If you're interested in seeing the generator in action or want to have a look at the implementation, it's open source and the <a target="_blank" href="https://github.com/ewerspej/epj.RouteGenerator">repository including the sample project</a> can be found on GitHub.</p>
<h1 id="heading-problems">Problems</h1>
<p>Since routes are often string-based, route names are volatile by nature. For example, if a route is called <code>"SomePage"</code>, but later gets renamed to <code>"XyzPage"</code>, then this may create a problem, because it potentially breaks the navigation when the new name isn't used in routing calls. Often, developers resort to using <code>nameof(SomePage)</code> to circumvent this issue. Therefore, instead of registering a route using a verbatim string</p>
<pre><code class="lang-csharp">Routing.RegisterRoute(<span class="hljs-string">"SomePage"</span>, <span class="hljs-keyword">typeof</span>(SomePage));
</code></pre>
<p>we can often see something like this:</p>
<pre><code class="lang-csharp">Routing.RegisterRoute(<span class="hljs-keyword">nameof</span>(SomePage), <span class="hljs-keyword">typeof</span>(SomePage));
</code></pre>
<p>where <code>SomePage</code> is a class that represents some type of page that can be navigated to.</p>
<p>This is usually done, because it avoids issues when <code>SomePage</code> gets renamed or deleted. Changes are instantly noticeable, because the project doesn't compile anymore if an unknown symbol is used in the <code>nameof(SomePage)</code> and <code>typeof(SomePage)</code> calls. However, since renaming operations usually would also update the symbol usages, changing the name of a page shouldn't actually produce any immediate problems. And if for some reason it does, then the compiler lets us know that something is broken and we can go ahead and fix it.</p>
<p>All this is not an issue when it comes to route <em>registration</em>. However, it very much becomes a problem when performing string-based route <em>navigation</em>, especially when applying the MVVM pattern. If we want to perform navigation in the ViewModel (or even in the code-behind), the calling code must either have knowledge of the type and call <code>nameof(SomePage)</code> or, again, use a verbatim string instead, i.e. <code>"SomePage"</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Approach #1. Problem: caller depends on SomePage type</span>
<span class="hljs-keyword">await</span> Shell.Current.GoToAsync(<span class="hljs-string">$"/<span class="hljs-subst">{<span class="hljs-keyword">nameof</span>(SomePage)}</span>"</span>);

<span class="hljs-comment">// Approach #2. Problem: verbatim route is volatile</span>
<span class="hljs-keyword">await</span> Shell.Current.GoToAsync(<span class="hljs-string">"/SomePage"</span>);
</code></pre>
<p>In the first approach, we would introduce a dependency on the <code>SomePage</code> class for the caller. This usually is less of an issue when performing navigation exclusively from within the code-behind of a XAML-based UI application <em>(depending on the app architecture)</em>. It definitely is a violation of the MVVM pattern, though, when doing this inside of a ViewModel and potentially makes the ViewModel untestable <em>(with regards to unit tests)</em>, because the ViewModel would have a dependency on the View.</p>
<p>The second approach uses a verbatim string instead of using <code>nameof(SomePage)</code>. This seems fine at first glance, but introduces another issue: Routes aren't reliable (volatile). Imagine you have an app with tens or even hundreds of potential routes and you use verbatim strings for route navigation, then you may quickly end up with hours of debugging and looking for the culprit, when the route name has changed from <code>"SomePage"</code> to <code>"XyzPage"</code>. The following code would still compile, but navigation will certainly fail:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// compiles, but the route "SomePage" won't be found during runtime</span>
<span class="hljs-comment">// if it has previously been renamed to "XyzPage"</span>
<span class="hljs-keyword">await</span> Shell.Current.GoToAsync(<span class="hljs-string">"/SomePage"</span>);
</code></pre>
<p>Typically, frameworks like .NET MAUI will emit an error informing the developer that a route couldn't be found. Developers sometimes also wrap navigation statements in try-catch-blocks to catch possible navigation exceptions, which only prevents the app from crashing, but it doesn't solve the navigation problem.</p>
<h1 id="heading-suboptimal-workarounds">Suboptimal workarounds</h1>
<p>We can attempt to work around this, e.g. by adding a static class that contains all route names by referencing the page classes inside of it:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MyApp</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Routes</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> SomePage = <span class="hljs-keyword">nameof</span>(SomeFeature.View.SomePage);
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> AnotherPage = <span class="hljs-keyword">nameof</span>(AnotherFeature.View.AnotherPage);
}
</code></pre>
<p>This seems to work even with the MVVM pattern, but note that we actually introduce a <em>transitive</em> dependency by using <code>nameof(SomePage)</code>, etc. in the static class.</p>
<blockquote>
<p>While <code>nameof()</code> actually produces a compile-time constant, there is an indirect dependency on the page classes during compilation. Therefore, we <em>technically</em> end up with a violation of the MVVM pattern, after all. The dependency on <code>SomePage</code> may be "gone" from the ViewModel and may not prevent unit tests anymore, but now it just has become <em>implicit</em> instead of <em>explicit</em>, which is even worse from a dependency management perspective, because it's hidden.</p>
</blockquote>
<p>If we do this, we could at least rename the <code>SomePage</code> class and our navigation would still work. This still is problematic, though, because essentially, if we rename the <code>SomePage</code> class to <code>XyzPage</code> and don't update the <code>Routes</code> constants, we would end up with a <code>Routes</code> class looking like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MyApp</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Routes</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> SomePage = <span class="hljs-keyword">nameof</span>(SomeFeature.View.XyzPage);
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> AnotherPage = <span class="hljs-keyword">nameof</span>(AnotherFeature.View.AnotherPage);
}
</code></pre>
<p>This is awkward, because we would still use the <code>Routes.SomePage</code> identifier for navigation, but it actually navigates to <code>XyzPage</code> - the route name doesn't match with the identifier anymore, which may lead to a lot of confusion:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// will navigate to XyzPage now...</span>
<span class="hljs-keyword">await</span> Shell.Current.GoToAsync(<span class="hljs-string">$"/<span class="hljs-subst">{Routes.SomePage}</span>"</span>);
</code></pre>
<p>This isn't pretty or advisable, because it's utterly inconsistent.</p>
<p>Now, another tempting approach would then be to just use constant strings directly instead of using <code>nameof(SomePage)</code> in order to remove the transitive dependencies:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Routes</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> SomePage = <span class="hljs-string">"SomePage"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> AnotherPage = <span class="hljs-string">"AnotherPage"</span>;
}
</code></pre>
<p>While this works, the route identifiers would be again be highly volatile, because renaming the <code>SomePage</code> class wouldn't be reflected in the static <code>Routes</code> class unless a search-and-replace operation is performed afterwards in order to also rename the symbol <em>and</em> update the constant string value.</p>
<p>Personally, I prefer that a project fails to compile when something changed over noticing changes only during runtime. If a breaking change <em>(renamings usually are breaking changes)</em> is introduced, I would like to know and be able to react to it directly instead of waiting for a bug to be found or an exception to occur during runtime.</p>
<p>So, how can we use constant route identifiers while avoiding invalid routes then in order to prevent ending up in navigation hell?</p>
<h1 id="heading-enter-route-generator">Enter Route Generator</h1>
<p>Enter <em>Route Generator</em>. It's an <a target="_blank" href="https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md">incremental source generator</a> that produces a static <code>Routes</code> class like above, always based on the currently available pages in a project. Now, in order to only generate routes for classes that actually represent pages, the generator takes an argument to specify a naming convention for routes.</p>
<p>"How is this different from manually typing a static <code>Routes</code> class?" you might ask. Well, that's a great question and the answer is simple:</p>
<p>Any changes are immediately reflected in the <code>Routes</code> class when a page gets added, removed or renamed. Renaming a page class will automatically lead to compiler errors in places where a constant route identifier was used before, because it won't exist anymore. This doesn't happen when you manually implement such a class.</p>
<p>For example, when renaming the <code>SomePage</code> <em>class</em> to <code>XyzPage</code>, the following</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Routes</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> SomePage = <span class="hljs-string">"SomePage"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> AnotherPage = <span class="hljs-string">"AnotherPage"</span>;
}
</code></pre>
<p>will become</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Routes</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> XyzPage = <span class="hljs-string">"XyzPage"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> AnotherPage = <span class="hljs-string">"AnotherPage"</span>;
}
</code></pre>
<p>This is perfect, because if we previously used</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// will fail to compile, because Routes.SomePage doesn't exist anymore</span>
<span class="hljs-comment">// after renaming it to XyzPage</span>
<span class="hljs-keyword">await</span> Shell.Current.GoToAsync(<span class="hljs-string">$"/<span class="hljs-subst">{Routes.SomePage}</span>"</span>);
</code></pre>
<p>we now will have to update the code to fix the resulting compiler error:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">await</span> Shell.Current.GoToAsync(<span class="hljs-string">$"/<span class="hljs-subst">{Routes.XyzPage}</span>"</span>);
</code></pre>
<p>Let's look at how you can add the Route Generator and use it in your project next.</p>
<h1 id="heading-how-to-use-route-generator">How to use Route Generator</h1>
<p>The Route Generator, which implements the <code>IIncrementalGenerator</code> interface, is still in preview at the time of writing and can be downloaded and installed from nuget.org. Just add the <a target="_blank" href="https://www.nuget.org/packages/epj.RouteGenerator/">epj.RouteGenerator</a> package to your project <em>(you may need to enable the "Include prerelease" check box in the Nuget Package Manager)</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702634757638/514ccac2-de8c-40a5-8fc0-1f2930216455.png" alt class="image--center mx-auto" /></p>
<p>Then, in order to have routes generated, all you need to do is use the <code>[AutoRoutes("SomeSuffix")]</code> attribute from the <code>epj.RouteGenerator</code> namespace anywhere in the target project where your pages are defined.</p>
<p>While the attribute can be added to any class within the target project, I highly recommend using it to decorate your <code>MauiProgram</code> class, if you use .NET MAUI:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> epj.RouteGenerator;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">RouteGeneratorSample</span>;

<span class="hljs-comment">// use this attribute to have Routes generated using the provided suffix</span>
[<span class="hljs-meta">AutoRoutes(<span class="hljs-meta-string">"Page"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MauiProgram</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MauiApp <span class="hljs-title">CreateMauiApp</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp&lt;App&gt;()
            .ConfigureFonts(fonts =&gt;
            {
                fonts.AddFont(<span class="hljs-string">"OpenSans-Regular.ttf"</span>, <span class="hljs-string">"OpenSansRegular"</span>);
                fonts.AddFont(<span class="hljs-string">"OpenSans-Semibold.ttf"</span>, <span class="hljs-string">"OpenSansSemibold"</span>);
            });

        <span class="hljs-keyword">return</span> builder.Build();
    }
}
</code></pre>
<p>Note that you can only use a <strong><em>single instance</em></strong> of the <code>[AutoRoutes("Suffix")]</code> attribute. When the Route Generator finds this attribute, it uses the provided suffix as a naming convention and looks for any classes whose names end in this suffix.</p>
<p>A common way to name pages is to use the <code>"Page"</code> suffix like above, but you can use any other naming convention, as well. The generator does not provide a default, so you will <em>always</em> have to specify your suffix with the attribute. Once added, route identifiers are automatically generated.</p>
<p>The generated static <code>Routes</code> class will be located in the same <em>root</em> namespace of the class that was decorated with it:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// &lt;auto-generated/&gt;</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">RouteGeneratorSample</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Routes</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> SomePage = <span class="hljs-string">"SomePage"</span>;
    }
}
</code></pre>
<p>While you can also use the identifiers of the auto-generated <code>Routes</code> class to register routes <em>(usually done in AppShell.xaml.cs when using .NET MAUI)</em>, this is not mandatory or necessary:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// both ways to register routes work and are equally valid</span>
Routing.RegisterRoute(<span class="hljs-keyword">nameof</span>(SomePage), <span class="hljs-keyword">typeof</span>(SomePage));
Routing.RegisterRoute(Routes.AnotherPage, <span class="hljs-keyword">typeof</span>(AnotherPage));
</code></pre>
<p>The important thing is that you can now use the generated identifiers for <em>navigation</em> without having to worry about volatile route names anymore:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">await</span> Shell.Current.GoToAsync(<span class="hljs-string">$"/<span class="hljs-subst">{Routes.SomePage}</span>"</span>);
</code></pre>
<p>This works, because as soon as you rename a class, the route identifiers are regenerated.</p>
<h1 id="heading-extra-routes">Extra Routes</h1>
<p>Now, you might encounter situations where routes do not follow the specified naming convention. To cover for this scenario, the Route Generator actually provides a second attribute that allows you to specify extra route names manually.</p>
<p>Imagine you have a page that is simply called <code>Dashboard</code> and thus doesn't follow the naming convention using the <code>"Page"</code> suffix of the remaining pages, then you can specify this route identifier explicitly:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// generate routes based on "Page" suffix</span>
<span class="hljs-comment">// and also add "Dashboard" as an extra route explicitly</span>
[<span class="hljs-meta">AutoRoutes(<span class="hljs-meta-string">"Page"</span>)</span>]
[<span class="hljs-meta">ExtraRoute(<span class="hljs-meta-string">"Dashboard"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MauiProgram</span>
{
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Now, the <code>Dashboard</code> identifier will also be included in the generated routes:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Routes</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> SomePage = <span class="hljs-string">"SomePage"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> AnotherPage = <span class="hljs-string">"AnotherPage"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> Dashboard = <span class="hljs-string">"Dashboard"</span>;
}
</code></pre>
<p>Although this is the same as manually typing the identifiers and strings, the Route Generator makes sure that the Routes are always up-to-update and you'll see compiler errors whenever a page gets renamed or removed.</p>
<h1 id="heading-a-quick-note-about-using-shellcurrent">A quick note about using Shell.Current</h1>
<p>If you're using .NET MAUI or Xamarin.Forms, then you may be inclined to directly access <code>Shell.Current</code> to perform any string-based route navigation:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">await</span> Shell.Current.GoToAsync(<span class="hljs-string">$"/<span class="hljs-subst">{Routes.SomePage}</span>"</span>);
</code></pre>
<p>While this is acceptable when performing navigation exclusively in the code-behind, I recommend hiding any calls to the different overloads of the <code>GoToAsync()</code> method behind an interface and inject that into your ViewModel. This will remove the <code>Shell.Current</code> dependency and your ViewModel remains MVVM-compliant and unit-testable, because you'll be able to mock the navigation:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> INavigationService _navigationService;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MyViewModel</span>(<span class="hljs-params">INavigationService navigationService</span>)</span>
{
    _navigationService = navigationService;
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">OpenSomePageAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">await</span> _navigationService.GoToAsync(<span class="hljs-string">$"/<span class="hljs-subst">{Routes.SomePage}</span>"</span>);
}
</code></pre>
<p>You can find an example of this <code>INavigationService</code> and its implementation in the <a target="_blank" href="https://github.com/ewerspej/epj.RouteGenerator/tree/main/RouteGeneratorSample/Navigation">sample project</a> of the Route Generator repository.</p>
<h1 id="heading-conclusions-and-next-steps">Conclusions and next steps</h1>
<p>In my quest for maintainable and less boilerplate code, I have come across source generators a while ago and this blog post is my latest addition during this journey. This time I have written about the first source generator that I have developed and published myself. <em>(It turns out that writing source generators is pretty straightforward when you know what you need.)</em></p>
<p>String-based route navigation can be a finicky thing and changing route names and adjusting navigation calls can be quite tedious. If approached naively with verbatim strings, it can also be highly error-prone, which is why I have developed the Route Generator. I have shown you how to use it and what problems you can solve with it. I hope this is useful for you and reduces the same pain that I have been experiencing when using string-based route navigation in the past.</p>
<p>A big <strong>thank you</strong> to <a target="_blank" href="https://jfversluis.dev/"><strong>Gerald Versluis</strong></a> <em>(who also made a cool</em> <a target="_blank" href="https://www.youtube.com/watch?v=XNLKyEPWqws"><em>video</em></a> <em>about the Route Generator)</em> for proof-reading this blog post for me (again). 💝</p>
<blockquote>
<p><strong><em>This blog post was written and published as part of the</em></strong> <a target="_blank" href="https://dotnet.christmas/2023"><strong><em>2023 .NET Advent Calendar</em></strong></a> <strong><em>by</em></strong> <a target="_blank" href="https://dusted.codes/"><strong><em>Dustin Moris</em></strong></a><strong><em>.</em></strong></p>
</blockquote>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a> and subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> so you don't miss out on any future posts. If this is useful to you, maybe also consider starring the <a target="_blank" href="https://github.com/ewerspej/epj.RouteGenerator">GitHub repository of the Route Generator</a>. Thank you very much.</p>
]]></content:encoded></item><item><title><![CDATA[Platform-specific XAML in .NET MAUI]]></title><description><![CDATA[Introduction
Cross-platform development is amazing, you only need to write the code once and then you can deploy to multiple platforms all from the same code base. Or so the theory goes. In reality, each platform has specific, native capabilities and...]]></description><link>https://blog.ewers-peters.de/platform-specific-xaml-in-net-maui</link><guid isPermaLink="true">https://blog.ewers-peters.de/platform-specific-xaml-in-net-maui</guid><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[Cross Platform]]></category><category><![CDATA[xaml]]></category><category><![CDATA[user experience]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Mon, 23 Oct 2023 10:19:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1698055694991/3eb97dac-2629-4df5-a7df-4d651728073c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Cross-platform development is amazing, you only need to write the code once and then you can deploy to multiple platforms all from the same code base. Or so the theory goes. In reality, each platform has specific, native capabilities and APIs which sometimes need to be handled separately.</p>
<p>In my <a target="_blank" href="https://blog.ewers-peters.de/series/maui-multi-targeting">series about multi-targeting</a> I've already touched on this topic, but the focus was on <em>platform-specific APIs</em>. Each platform has a different <em>look-and-feel</em> and sometimes you want things to look native or you want UI elements to look a certain way on a specific platform, like having a green underline on Android and a red underline on iOS or different icons and logos. There's a plethora of ways to customize how your app looks, such as <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/handlers/customize">control customization using handlers</a> and <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/xaml/fundamentals/markup-extensions">markup extensions</a> to name just a few.</p>
<p>Sometimes, styling or customizing individual controls just isn't enough, though. What if you need an entire part of a page to look very different on each platform <em>(or device type)</em> like someone asked in <a target="_blank" href="https://stackoverflow.com/questions/74815868/net-maui-can-we-also-have-platform-specific-xaml">this Stack Overflow question</a>: <em>"Can we also have platform-specific XAML?"</em> The answer is simple: <strong><em>Yes, we certainly can!</em></strong></p>
<p>In this blog post, I'll show you a few ways how you can use custom XAML files in your app based on the platform or device type. One of the obvious choices would be to use multi-targeting. Unfortunately, .NET's multi-targeting currently doesn't support conditional XAML compilation or proper exclusion of XAML files based on the target platform (yet). Therefore, I'll show other approaches that work well for me.</p>
<p>To keep things simple, I'll only focus on custom views. Custom platform-specific pages can also be instantiated, which can be seen in my <a target="_blank" href="https://stackoverflow.com/a/74823801/4308455">answer to the Stack Overflow question</a> above as well as in my <a target="_blank" href="https://github.com/ewerspej/maui-samples">sample repository</a>.</p>
<p>First, we'll look into two code-behind approaches, one using a runtime decision and another one using <em>conditional compilation</em>. After that, we'll explore a purely XAML-based approach. For the latter, we'll use different XAML files than for the first two approaches, so that the different approaches remain distinct in the sample code.</p>
<h1 id="heading-code-behind-approaches">Code-behind approaches</h1>
<p>Since this post is about XAML, let's start with a simple layout. Here, we have a <em>VerticalStackLayout</em> and it has an <code>x:Name</code> set so that we can manipulate the contents of it by accessing it via the <code>VerticalLayout</code> identifier in the code-behind.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Grid</span> <span class="hljs-attr">RowDefinitions</span>=<span class="hljs-string">"*,4*"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">VerticalStackLayout</span>
    <span class="hljs-attr">Grid.Row</span>=<span class="hljs-string">"0"</span>
    <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"VerticalLayout"</span> /&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
</code></pre>
<p>In the next two subsections, we'll see how we can add different XAML views to this layout based on the target platform.</p>
<h2 id="heading-runtime-decision">Runtime decision</h2>
<p>The simplest way to show platform-specific views is to make the appropriate calls in the code-behind based on the current runtime platform:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">if</span> (DeviceInfo.Platform == DevicePlatform.Android)
{
    VerticalLayout.Add(<span class="hljs-keyword">new</span> Android.ViewAndroid());
}
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (DeviceInfo.Platform == DevicePlatform.iOS)
{
    VerticalLayout.Add(<span class="hljs-keyword">new</span> iOS.ViewiOS());
}
</code></pre>
<p>The downside of this approach is that the resulting code will be available on all target platforms.</p>
<h2 id="heading-conditional-compilation">Conditional compilation</h2>
<p>An alternative and slightly better approach compared to the runtime decision is to use conditional compilation instead:</p>
<pre><code class="lang-csharp"><span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> ANDROID</span>
    VerticalLayout.Add(<span class="hljs-keyword">new</span> Android.ViewAndroid());
<span class="hljs-meta">#<span class="hljs-meta-keyword">elif</span> IOS</span>
    VerticalLayout.Add(<span class="hljs-keyword">new</span> iOS.ViewiOS());
<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span>
</code></pre>
<p>The advantage of this is that only the appropriate calls end up in the compiled app code for each target platform and no decision needs to be taken during runtime.</p>
<h1 id="heading-xaml-only-approach">XAML-only approach</h1>
<p>It's also possible to show only the relevant parts of the UI based on the current runtime platform by only using XAML and no C# code by taking advantage of <code>&lt;OnPlatform&gt;</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Grid</span> <span class="hljs-attr">RowDefinitions</span>=<span class="hljs-string">"*,4*"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ContentView</span>
    <span class="hljs-attr">Grid.Row</span>=<span class="hljs-string">"1"</span>
    <span class="hljs-attr">Padding</span>=<span class="hljs-string">"20"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">OnPlatform</span> <span class="hljs-attr">x:TypeArguments</span>=<span class="hljs-string">"View"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">On</span> <span class="hljs-attr">Platform</span>=<span class="hljs-string">"Android"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">android:ImageViewAndroid</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">On</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">On</span> <span class="hljs-attr">Platform</span>=<span class="hljs-string">"iOS"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">iOs:ImageViewiOS</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">On</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">OnPlatform</span>&gt;</span>

  <span class="hljs-tag">&lt;/<span class="hljs-name">ContentView</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
</code></pre>
<p>Here, we have a <em>ContentView</em> that serves as a container and we control its content by using the <code>&lt;OnPlatform&gt;</code> class and providing different views for each platform. It's important to include the <code>x:TypeArguments="View"</code> attribute, because we need to tell <code>&lt;OnPlatform&gt;</code> what the return type is as the <code>ContentView</code> class only accepts <code>View</code> instances as its <code>Content</code>. It's really as simple as that!</p>
<blockquote>
<p><strong>Note:</strong> This is equivalent to the runtime decision approach in the code-behind, meaning that all views of all platforms will be included in the app bundle.</p>
</blockquote>
<p>It's also possible to use <code>&lt;OnIdiom&gt;</code> to provide different views depending on the device type <em>(e.g. tablet, phone, desktop)</em>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Grid</span> <span class="hljs-attr">RowDefinitions</span>=<span class="hljs-string">"*,4*"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ContentView</span>
    <span class="hljs-attr">Grid.Row</span>=<span class="hljs-string">"1"</span>
    <span class="hljs-attr">Padding</span>=<span class="hljs-string">"20"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">OnIdiom</span> <span class="hljs-attr">x:TypeArguments</span>=<span class="hljs-string">"View"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">On</span> <span class="hljs-attr">Idiom</span>=<span class="hljs-string">"Tablet"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tablet:TabletView</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">On</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">On</span> <span class="hljs-attr">Idiom</span>=<span class="hljs-string">"Phone"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">phone:PhoneView</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">On</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">OnIdiom</span>&gt;</span>

  <span class="hljs-tag">&lt;/<span class="hljs-name">ContentView</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
</code></pre>
<h1 id="heading-result">Result</h1>
<p>This is what it will look like on iOS and Android using the different approaches I demonstrated above. The "Hello from iOS" and "Hello from Android" parts come from the code-behind approaches while the logos come from the XAML-only approach.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1698053681575/de01523e-d4e8-4a9d-955c-dee083e77b7b.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-notes">Notes</h1>
<p>As I have mentioned already in the introduction, multi-targeting specifically for XAML isn't really supported (yet), unfortunately. I have spent a fair share of hours trying to figure out how to accomplish this and although I have found a potential solution, it comes with several drawbacks, which is why I decided not to show it as of now. If future versions of .NET support multi-targeting for XAML or if I manage to figure out a well-working solution, I will write a follow-up on this topic.</p>
<p>Bear in mind that due to the nature of the implementation of the approaches above and the fact that multi-targeting currently isn't supported for XAML, only the code-behind is subject to conditional compilation. This means that all XAML code of your app project will be shipped to all target platforms, even the unused XAML which is specific to other target platforms (or device types).</p>
<h1 id="heading-conclusions-and-next-steps">Conclusions and next steps</h1>
<p>As we have seen, it's actually quite easy to have platform-specific XAML in our .NET MAUI applications by using runtime decisions (either in C# or XAML) or conditional compilation.</p>
<p>Although it's not possible (yet) to use conditional compilation for XAML or even filename and folder-based multi-targeting for XAML files, the presented approaches can help you elevate the user experience of your cross-platform app by providing platform-specific views with ease. I hope you learned something useful today. Enjoy building awesome cross-platform apps with platform-specific views with .NET MAUI!</p>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>GitHub</strong> <strong>repository</strong></a> for this post so you don't miss out on any future posts and developments.</p>
]]></content:encoded></item><item><title><![CDATA[Three ways to implement an accordion control]]></title><description><![CDATA[Introduction
Accordions are brilliant, and I am not talking about the Austrian-developed musical instrument, which is popular for singing sea shanties in Germany (whether those are 'brilliant' is a matter of musical taste). I am talking about the typ...]]></description><link>https://blog.ewers-peters.de/three-ways-to-implement-an-accordion-control</link><guid isPermaLink="true">https://blog.ewers-peters.de/three-ways-to-implement-an-accordion-control</guid><category><![CDATA[dotnet]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[Accordion]]></category><category><![CDATA[user experience]]></category><category><![CDATA[expander]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Mon, 21 Aug 2023 10:11:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1692609982866/45ec5ee3-1d4f-407b-b913-bbe75f565ed5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Accordions are brilliant, and I am <strong>not</strong> talking about the Austrian-developed <em>musical instrument</em>, which is popular for singing sea shanties in Germany <em>(whether those are 'brilliant' is a matter of musical taste)</em>. I am talking about the type of <em>user control</em> that can be used to expand and collapse sections of content in user interfaces, such as in mobile apps and websites. By allowing the user to navigate content quickly without having to scroll endlessly back and forth, they enhance the user experience of any application and website that features a lot of content that can be split up into sections or sub-sections.</p>
<p>What makes an <a target="_blank" href="https://en.wikipedia.org/wiki/Accordion_(GUI)">accordion</a>? Essentially, an accordion is just a collection of expanders that can show and hide their content through user interaction. Many accordions are implemented in such a way that only one single expander of the collection can be expanded at a time, while the remaining expanders are in their collapsed state. When the user selects one of the expanders, it will expand its content while any open expander will collapse its content in that moment.</p>
<p>In the following sections, I will demonstrate three ways how you can implement an accordion that you can tailor to your own needs using my recently published and highly customizable <a target="_blank" href="https://www.nuget.org/packages/epj.Expander.Maui/">expander control for .NET MAUI</a>. The result will look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692350380545/97b208c4-8b71-4f61-a156-a9f5736295df.gif" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>Note:</strong> You can also use any other Expander control as the base of an accordion as long as it allows you to expand and collapse it programmatically. So, although this post focusses on how this can be done using my own Expander control, the general idea applies to any other appropriate implementation of an Expander.</p>
</blockquote>
<p>You can find the sample code from this blog post in the <a target="_blank" href="https://github.com/ewerspej/epj.Expander.Maui">GitHub repository</a> of the expander control.</p>
<blockquote>
<p><strong>Note</strong>: This blog post uses the <a target="_blank" href="https://blog.ewers-peters.de/introduction-to-mvvm-source-generators-for-dotnet">MVVM Source Generators</a> of the .NET Community Toolkit to keep the code samples short. Use of the Source Generators is entirely optional.</p>
</blockquote>
<h1 id="heading-excursion-why-implement-a-new-expander-control"><em>Excursion: Why implement a new Expander control?</em></h1>
<p>Recently, while porting an app from <em>Xamarin.Forms</em> to <em>.NET MAUI</em>, I came across a problem with the Expander control of the <em>MAUI Community Toolkit (MCT)</em>. The Expander of the MCT behaves differently than the Expander of the Xamarin.Forms Community Toolkit (XCT).</p>
<p>This difference imposed a problem, because I couldn't use the Expander for an accordion control anymore in the way that I needed it. Usually, this calls for a bug report or a feature request. However, the differences in the control seem to be deliberate and the implementations differ quite a bit. Also, I needed a solution fast and it felt like a neat addition to my toolbox of open source controls for MAUI.</p>
<p>So, I whipped up my own little Expander control instead and using that, I am going to show you how you can use it to implement your own accordion control. Apart from being easy to use, one of the treats of this Expander is that it comes with simple collapse and expand animations built-in <em>(at the time of writing these are still experimental)</em>.</p>
<h1 id="heading-installing-the-expander">Installing the Expander</h1>
<p>The first thing we need to do before we can implement the accordion is adding the <a target="_blank" href="https://www.nuget.org/packages/epj.Expander.Maui/#readme-body-tab">epj.Expander.Maui</a> package <em>(version 1.0.2 at the time of writing)</em> from <em>nuget.org</em> to our MAUI app project:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692002136830/1b76d8fe-b09b-48a4-8594-f4675c27df1e.png" alt class="image--center mx-auto" /></p>
<p>Once that is done, you can optionally enable animations, which requires an explicit opt-in at the time of writing. In order to do this, simply put the following call anywhere in your app, ideally in <em>MauiProgram.cs</em> or <em>App.xaml.cs</em>:</p>
<pre><code class="lang-csharp">Expander.EnableAnimations();
</code></pre>
<p>Now, that the Expander is installed and configured, we can implement the accordion.</p>
<h1 id="heading-implementing-the-accordion">Implementing the accordion</h1>
<p>There are various use cases and different ways how to approach this. I will show three scenarios that I regard as the most common ones in the following sub-sections. As a general outline, all three accordion implementations will consist of a few instances of the Expander control contained inside of a <em>VerticalStackLayout</em>.</p>
<p>While the first approach will look at an implementation that works without data binding (not using a <em>ViewModel</em>), which is mainly useful for <em>View</em>-only scenarios, e.g. displaying static text, the second example will show a mix of data binding (using a <em>ViewModel</em>) and controlling the accordion's segments from the <em>View</em>'s code-behind. Last but not least, the third approach will demonstrate how to control the state of the accordion purely based on <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/">data binding</a> (using a <em>ViewModel</em>).</p>
<h2 id="heading-version-1-view-only-accordion">Version 1: View-only accordion</h2>
<p>In this first example, the accordion will be built using three explicit, static instances of the Expander. The entire presentation and interaction is fully controlled by the View. This is useful when working with static content, such as long text or images that are not dynamically loaded.</p>
<p>For this, we need to take the following steps:</p>
<ol>
<li><p>Add several <em>Expander</em> instances to a <em>VerticalStackLayout</em></p>
</li>
<li><p>Provide an <code>x:Name</code> to the <em>VerticalStackLayout</em> to be able to access it from the code-behind</p>
</li>
<li><p>Add an event handler for the <code>HeaderTapped</code> event to the code-behind to be used by each <em>Expander</em></p>
</li>
</ol>
<p>The XAML of our View could look something like this, for example:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">VerticalStackLayout</span>
  <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"AccordionLayout"</span>
  <span class="hljs-attr">Spacing</span>=<span class="hljs-string">"6"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">maui:Expander</span>
    <span class="hljs-attr">HeaderTapped</span>=<span class="hljs-string">"Expander_OnHeaderTapped"</span>
    <span class="hljs-attr">Animated</span>=<span class="hljs-string">"True"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">maui:Expander.HeaderContent</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
        <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"80"</span>
        <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"Orange"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
          <span class="hljs-attr">Text</span>=<span class="hljs-string">"Expander #1"</span>
          <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Title"</span>
          <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
          <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">maui:Expander.HeaderContent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
      <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"200"</span>
      <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"DarkSlateGray"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
        <span class="hljs-attr">Text</span>=<span class="hljs-string">"This is the content of the first expander!"</span>
        <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
        <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">maui:Expander</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">maui:Expander</span>
    <span class="hljs-attr">HeaderTapped</span>=<span class="hljs-string">"Expander_OnHeaderTapped"</span>
    <span class="hljs-attr">Animated</span>=<span class="hljs-string">"True"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">maui:Expander.HeaderContent</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
        <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"80"</span>
        <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"Orange"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
          <span class="hljs-attr">Text</span>=<span class="hljs-string">"Expander #2"</span>
          <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Title"</span>
          <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
          <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">maui:Expander.HeaderContent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
      <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"200"</span>
      <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"DarkSlateGray"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
        <span class="hljs-attr">Text</span>=<span class="hljs-string">"This is the content of the second expander!"</span>
        <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
        <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">maui:Expander</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">maui:Expander</span>
    <span class="hljs-attr">HeaderTapped</span>=<span class="hljs-string">"Expander_OnHeaderTapped"</span>
    <span class="hljs-attr">Animated</span>=<span class="hljs-string">"True"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">maui:Expander.HeaderContent</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
        <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"80"</span>
        <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"Orange"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
          <span class="hljs-attr">Text</span>=<span class="hljs-string">"Expander #3"</span>
          <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Title"</span>
          <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
          <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">maui:Expander.HeaderContent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
      <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"200"</span>
      <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"DarkSlateGray"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
        <span class="hljs-attr">Text</span>=<span class="hljs-string">"This is the content of the third expander!"</span>
        <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
        <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">maui:Expander</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">VerticalStackLayout</span>&gt;</span>
</code></pre>
<p>Each expander can already be expanded and collapsed separately. Now, in order to add the accordion-style functionality where only one expander is allowed to be in the expanded state at a time, all we need to do is adding the event handler for the <code>HeaderTapped</code> event, which is already set to a method name in the XAML above:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Expander_OnHeaderTapped</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> sender, ExpandedEventArgs e</span>)</span>
{
    <span class="hljs-keyword">if</span> (sender <span class="hljs-keyword">is</span> not Expander expander)
    {
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> child <span class="hljs-keyword">in</span> AccordionLayout.Children)
    {
        <span class="hljs-keyword">if</span> (child <span class="hljs-keyword">is</span> not Expander other)
        {
            <span class="hljs-keyword">continue</span>;
        }

        <span class="hljs-keyword">if</span> (other != expander)
        {
            other.IsExpanded = <span class="hljs-literal">false</span>;
        }
    }
}
</code></pre>
<p>In this event handler, we simply iterate through the child elements of the <em>VerticalStackLayout</em> (which we've called <code>AccordionLayout</code> using the <code>x:Name</code> attribute), and check if the current element is an expander. If it is an expander, we compare it to the expander instance that invoked the event. If the <code>other</code> expander is not the same as the current <code>expander</code>, we simply set its <code>IsExpanded</code> property to <code>false</code>. This will collapse all expanders except for the one that has just been selected by the user.</p>
<p>That's all we need to do, the final result should look something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692350288303/6a8d287b-1b6b-4e48-9d98-536da3920994.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-version-2-hybrid-accordion">Version 2: Hybrid accordion</h2>
<p>In this second example, the accordion will be built by dynamically populating the content using data binding while controlling the interaction from the View. This is useful when the content comes from some form of (external) data source and needs to be loaded dynamically. Here, we keep the user interaction and the business logic strictly separate.</p>
<p>For this approach, we will need to do the following:</p>
<ol>
<li><p>Create a simple Model and a ViewModel</p>
</li>
<li><p>Add a <em>VerticalStackLayout</em> and use MAUI's <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/layouts/bindablelayout">BindableLayout</a> to dynamically bind it to the ViewModel</p>
</li>
<li><p>Provide an <code>x:Name</code> to the <em>VerticalStackLayout</em> to be able to access it from the code-behind</p>
</li>
<li><p>Add a <em>DataTemplate</em> using the <em>Expander</em> as the templated control</p>
</li>
<li><p>Add an event handler for the <code>HeaderTapped</code> event to the code-behind which will be used by each <em>Expander</em> instance</p>
</li>
</ol>
<p>The Model and ViewModel can be very simple, we basically just need a List of objects to bind to. In this case, I chose to create a simple <code>PersonTuple</code> Model class as well as a <code>HybridAccordionViewModel</code> as the ViewModel. The <code>PersonTuple</code> is just a tuple of two strings and the <code>HybridAccordionViewModel</code> simply holds an observable property which is a <code>List&lt;PersonTuple&gt;</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">PersonTuple</span> : <span class="hljs-title">Tuple</span>&lt;<span class="hljs-title">string</span>, <span class="hljs-title">string</span>&gt;
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PersonTuple</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> item1, <span class="hljs-keyword">string</span> item2</span>) : <span class="hljs-title">base</span>(<span class="hljs-params">item1, item2</span>)</span> { }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HybridAccordionViewModel</span> : <span class="hljs-title">ObservableObject</span>
{
    [<span class="hljs-meta">ObservableProperty</span>]
    <span class="hljs-keyword">private</span> List&lt;PersonTuple&gt; _people;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">HybridAccordionViewModel</span>(<span class="hljs-params"></span>)</span>
    {
        People = <span class="hljs-keyword">new</span> List&lt;PersonTuple&gt;
        {
            <span class="hljs-keyword">new</span>(<span class="hljs-string">"Jane"</span>, <span class="hljs-string">"Wants to be an actress"</span>),
            <span class="hljs-keyword">new</span>(<span class="hljs-string">"John"</span>, <span class="hljs-string">"Wants to be a rock musician"</span>),
            <span class="hljs-keyword">new</span>(<span class="hljs-string">"Jasmine"</span>, <span class="hljs-string">"Wants to be a heart surgeon"</span>)
        };
    }
}
</code></pre>
<p>With this set up, the next thing we need is the View. The XAML in this example is much shorter, because we dynamically create the Expander instances via the <code>BindableLayout.ItemTemplate</code> which is attached to a <em>VerticalStackLayout</em>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">VerticalStackLayout</span>
  <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"AccordionLayout"</span>
  <span class="hljs-attr">Spacing</span>=<span class="hljs-string">"6"</span>
  <span class="hljs-attr">BindableLayout.ItemsSource</span>=<span class="hljs-string">"{Binding People}"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">BindableLayout.ItemTemplate</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">DataTemplate</span> <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"{x:Type hybrid:PersonTuple}"</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">maui:Expander</span>
        <span class="hljs-attr">Animated</span>=<span class="hljs-string">"True"</span>
        <span class="hljs-attr">HeaderTapped</span>=<span class="hljs-string">"Expander_OnHeaderTapped"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">maui:Expander.HeaderContent</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
            <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"80"</span>
            <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"Orange"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
              <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Item1}"</span>
              <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Title"</span>
              <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
              <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">maui:Expander.HeaderContent</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
          <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"200"</span>
          <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"DarkSlateGray"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
            <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Item2}"</span>
            <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
            <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">maui:Expander</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">DataTemplate</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">BindableLayout.ItemTemplate</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">VerticalStackLayout</span>&gt;</span>
</code></pre>
<p>Here, we're telling the <code>DataTemplate</code> that the data type (indicated by <code>x:DataType</code>) is the previously created <code>PersonTuple</code> class <em>(which is also useful for</em> <a target="_blank" href="https://blog.ewers-peters.de/a-quick-introduction-to-compiled-bindings"><em>compiled bindings</em></a><em>)</em>. Then, we can bind to <code>Item1</code> and <code>Item2</code> of the tuple, the first item holds the name and the second one some additional info about a person.</p>
<p>Last, but not least, we need to set up the event handler <em>(which is identical to the one from the previous example)</em>, and set the <code>BindingContext</code> in the code-behind of the Page to the ViewModel:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HybridAccordionPage</span> : <span class="hljs-title">ContentPage</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">HybridAccordionPage</span>(<span class="hljs-params"></span>)</span>
    {
        InitializeComponent();
        BindingContext = <span class="hljs-keyword">new</span> HybridAccordionViewModel();
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Expander_OnHeaderTapped</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> sender, ExpandedEventArgs e</span>)</span>
    {
        <span class="hljs-keyword">if</span> (sender <span class="hljs-keyword">is</span> not Expander expander)
        {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> child <span class="hljs-keyword">in</span> AccordionLayout.Children)
        {
            <span class="hljs-keyword">if</span> (child <span class="hljs-keyword">is</span> not Expander other)
            {
                <span class="hljs-keyword">continue</span>;
            }

            <span class="hljs-keyword">if</span> (other != expander)
            {
                other.IsExpanded = <span class="hljs-literal">false</span>;
            }
        }
    }
}
</code></pre>
<p>Just like that, we're already done with the hybrid approach, which combines data-binding and view-based control over the state of the Expander instances.</p>
<p>Like in the previous example, the finished accordion should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692351854548/388b124b-5ef0-4615-b7f0-8cf627311a4b.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-version-3-data-binding-controlled-accordion">Version 3: Data binding-controlled accordion</h2>
<p>In this third and last example, the accordion will be built by populating the content dynamically while also controlling the entire interaction from within the ViewModel using data binding only. This is useful when the user interaction directly affects the business logic, e.g. by updating the status of an object (such as item selection). The beauty of this approach is that the ViewModel still doesn't know anything about the user interaction or the View, at all - thanks to the <a target="_blank" href="https://learn.microsoft.com/dotnet/architecture/maui/mvvm">MVVM pattern</a> and data binding.</p>
<p>In order to achieve this, we need do these things:</p>
<ol>
<li><p>Create a Model with a <code>Selected</code> property</p>
</li>
<li><p>Create a simple ViewModel that holds a list of objects to bind to</p>
</li>
<li><p>Add a <code>SelectItemCommand</code> to the ViewModel to update the item selection</p>
</li>
<li><p>Add a <em>VerticalStackLayout</em> and use the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/layouts/bindablelayout">BindableLayout</a> to dynamically bind to the ViewModel</p>
</li>
<li><p>Add a <em>DataTemplate</em> using the <em>Expander</em> as the templated control</p>
</li>
<li><p>Bind the <code>IsExpanded</code> property of the Expander to the <code>Selected</code> property of the Model class</p>
</li>
</ol>
<p>The Model class I have created for this is called <code>PersonModel</code> and has three observable properties:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">PersonModel</span> : <span class="hljs-title">ObservableObject</span>
{
    [<span class="hljs-meta">ObservableProperty</span>]
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _name;

    [<span class="hljs-meta">ObservableProperty</span>]
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _description;

    [<span class="hljs-meta">ObservableProperty</span>]
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> _selected;
}
</code></pre>
<p>This is then used in the ViewModel, similar to the previous example. The ViewModel is called <code>DataBindingAccordionViewModel</code>, and it holds a <code>List&lt;PersonModel</code> and exposes the <code>SelectItemCommand</code> mentioned above:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DataBindingAccordionViewModel</span> : <span class="hljs-title">ObservableObject</span>
{
    [<span class="hljs-meta">ObservableProperty</span>]
    <span class="hljs-keyword">private</span> List&lt;PersonModel&gt; _people;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">DataBindingAccordionViewModel</span>(<span class="hljs-params"></span>)</span>
    {
        People = <span class="hljs-keyword">new</span> List&lt;PersonModel&gt;
        {
            <span class="hljs-keyword">new</span>()
            {
                Name = <span class="hljs-string">"Jane"</span>,
                Description = <span class="hljs-string">"Wants to be an actress"</span>
            },
            <span class="hljs-keyword">new</span>()
            {
                Name = <span class="hljs-string">"John"</span>,
                Description = <span class="hljs-string">"Wants to be a rock musician"</span>
            },
            <span class="hljs-keyword">new</span>()
            {
                Name = <span class="hljs-string">"Jasmine"</span>,
                Description = <span class="hljs-string">"Wants to be a heart surgeon"</span>
            },
        };
    }

    [<span class="hljs-meta">RelayCommand</span>]
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SelectItem</span>(<span class="hljs-params">PersonModel item</span>)</span>
    {
        item.Selected = !item.Selected;

        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> person <span class="hljs-keyword">in</span> People)
        {
            <span class="hljs-keyword">if</span> (person != item)
            {
                person.Selected = <span class="hljs-literal">false</span>;
            }
        }
    }
}
</code></pre>
<blockquote>
<p><strong>Note:</strong> The <code>SelectItemCommand</code> is <a target="_blank" href="https://blog.ewers-peters.de/introduction-to-mvvm-source-generators-for-dotnet"><em>auto-generated</em></a> from the <code>SelectItem()</code> method <em>via the</em> <code>[RelayCommand]</code> code generator attribute</p>
</blockquote>
<p>The XAML of the View looks quite similar to the previous example, but this time there is no event handler and instead the <code>Command</code> property of the Expander binds to the <code>SelectItemCommand</code> of the ViewModel and the current instance of the <code>PersonModel</code> is used as the argument for the <code>CommandParameter</code> property of the Expander. Additionally, the <code>IsExpanded</code> property of the Expander binds to the <code>Selected</code> property of the Model:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">VerticalStackLayout</span>
  <span class="hljs-attr">Spacing</span>=<span class="hljs-string">"6"</span>
  <span class="hljs-attr">BindableLayout.ItemsSource</span>=<span class="hljs-string">"{Binding People}"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">BindableLayout.ItemTemplate</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">DataTemplate</span> <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"dataBinding:PersonModel"</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">maui:Expander</span>
        <span class="hljs-attr">Animated</span>=<span class="hljs-string">"True"</span>
        <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding SelectItemCommand, Source={RelativeSource AncestorType={x:Type dataBinding:DataBindingAccordionViewModel}}}"</span>
        <span class="hljs-attr">CommandParameter</span>=<span class="hljs-string">"{Binding .}"</span>
        <span class="hljs-attr">IsExpanded</span>=<span class="hljs-string">"{Binding Selected}"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">maui:Expander.HeaderContent</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
            <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"80"</span>
            <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"Orange"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
              <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Name}"</span>
              <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Title"</span>
              <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
              <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">maui:Expander.HeaderContent</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
          <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"200"</span>
          <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"DarkSlateGray"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
            <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Description}"</span>
            <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
            <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">maui:Expander</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">DataTemplate</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">BindableLayout.ItemTemplate</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">VerticalStackLayout</span>&gt;</span>
</code></pre>
<p>In the code-behind, we only need to set the <code>BindingContext</code> to the ViewModel:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">DataBindingAccordionPage</span>(<span class="hljs-params"></span>)</span>
{
    InitializeComponent();
    BindingContext = <span class="hljs-keyword">new</span> DataBindingAccordionViewModel();
}
</code></pre>
<p>Now, when we run this, it should look like this, but this time, everything is entirely controlled by the ViewModel:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692354373412/b63fe2a7-933f-4b36-9832-b4fc153427a5.gif" alt class="image--center mx-auto" /></p>
<h1 id="heading-conclusions-and-next-steps"><strong>Conclusions and Next Steps</strong></h1>
<p>It's incredibly easy to enhance the user experience of your .NET MAUI app by using Expanders and Accordion controls. I have demonstrated three different approaches for the implementation of an Accordion that you can apply based on the requirements and use cases of your app, so that your app users will spend less time scrolling and can find the content they're looking for faster.</p>
<p>I hope this is useful for you. If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> and star the <a target="_blank" href="https://github.com/ewerspej/epj.Expander.Maui">GitHub repository</a> of this post and also check out my <a target="_blank" href="https://github.com/ewerspej/maui-samples">MAUI Samples repository</a>, so you don't miss out on any future posts and developments.</p>
<h2 id="heading-attributions">Attributions</h2>
<p>Title photo by <a target="_blank" href="https://unsplash.com/@dominik_photography?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Dominik Vanyi</a> on <a target="_blank" href="https://unsplash.com/photos/5Fxuo7x-eyg?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Are you using Dependency Injection in your .NET MAUI app yet?]]></title><description><![CDATA[Introduction
In this article, I will show you how to leverage MAUI's built-in dependency injection capabilities to provide dependencies to your Views and ViewModels.
I absolutely love the Dependency Inversion Principle (DIP), it is by far my favorite...]]></description><link>https://blog.ewers-peters.de/are-you-using-dependency-injection-in-your-net-maui-app-yet</link><guid isPermaLink="true">https://blog.ewers-peters.de/are-you-using-dependency-injection-in-your-net-maui-app-yet</guid><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[dependency injection]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[SOLID principles]]></category><category><![CDATA[maui-shell]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Fri, 09 Jun 2023 09:34:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1686212771011/47971f9e-4f4a-4918-b0e0-4a4076694889.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>In this article, I will show you how to leverage MAUI's built-in dependency injection capabilities to provide dependencies to your Views and ViewModels.</p>
<p>I absolutely love the <em>Dependency Inversion Principle (DIP)</em>, it is by far my favorite principle from <em>SOLID</em>, and <em>.NET MAUI</em> makes it very easy to apply <em>Inversion of Control (IoC)</em> with it, because as a first class-citizen, <em>Dependency Injection (DI)</em> already comes built-in with <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/shell/">Shell</a> apps. Like <em>ASP.NET Core</em>, <em>.NET MAUI</em> also uses <a target="_blank" href="https://learn.microsoft.com/dotnet/core/extensions/dependency-injection">.NET's Dependency Injection pattern</a>.</p>
<p>SOLID stands for:</p>
<ul>
<li><p><strong>S</strong>RP = Single Responsibility Principle</p>
</li>
<li><p><strong>O</strong>CP = Open/Closed Principle</p>
</li>
<li><p><strong>L</strong>SP = Liskov Substitution Principle</p>
</li>
<li><p><strong>I</strong>SP = Interface Segregation Principle</p>
</li>
<li><p><strong>D</strong>IP = Dependency Inversion Principle</p>
</li>
</ul>
<p>Uff, a lot of fancy acronyms and buzz words. While DIP, IoC and DI are not all exactly the same, they express and address different aspects of the same idea: Loose coupling. Instead of having hard dependencies in our code, which make it unflexible and difficult to maintain and test, the idea is to invert the control of the dependencies by hiding implementation details and only depending on interfaces or contracts instead.</p>
<p>Specific implementations of those dependencies are then injected. This can be done via constructor arguments, IoC containers, the builder pattern or specific setter methods. In this blog post, we will explore how to leverage Shell's built-in dependency injection, which uses a combination of the first three approaches.</p>
<p>This is entirely optional, of course. If you want, you can also use any other IoC container, there are plenty of great DI frameworks and IoC containers out there for .NET, like <a target="_blank" href="https://github.com/grumpydev/TinyIoC">TinyIoC</a>, <a target="_blank" href="https://simpleinjector.org/">SimpleInjector</a> and <a target="_blank" href="https://prismlibrary.com">Prism</a>.</p>
<p>As always, you can find the code from this blog post in the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>sample repository</strong></a> on GitHub.</p>
<blockquote>
<p><strong>Note</strong>: This post focusses on dependency injection in .NET MAUI and is not a general discussion or tutorial about what DI is. If you want to learn more about DI patterns (and anti-patterns) and good software design in general, I highly recommend reading "Dependency Injection - Principles, Practices and Patterns" by Steven van Deursen and Mark Seemann.</p>
</blockquote>
<h1 id="heading-setting">Setting</h1>
<p>For this example, we'll assume that we have a <code>MainPage</code>, a <code>MainViewModel</code> and an <code>IDeviceService</code> which is consumed by the <code>MainViewModel</code>. The <code>MainViewModel</code> somehow needs to be passed into the <code>MainPage</code> to serve as its <code>BindingContext</code> and the <code>IDeviceService</code> needs to be passed into the <code>MainViewModel</code>.</p>
<p>One of the most common ways to inject dependencies is via <em>constructor injection</em>, which we will mainly focus on today. Typically, this would look as follows:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainPage</span>(<span class="hljs-params">MainViewModel viewModel</span>)</span>
{
    InitializeComponent();
    BindingContext = viewModel;
}
</code></pre>
<p>Here, we have defined the <code>MainPage</code> constructor which takes in a <code>MainViewModel</code> as a parameter, so our dependency gets injected via the constructor and can then be used in the class.</p>
<p>Similarly, the <code>MainViewModel</code> constructor takes in an instance of an <code>IDeviceService</code> implementation:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IDeviceService _deviceService;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainViewModel</span>(<span class="hljs-params">IDeviceService deviceService</span>)</span>
{
    _deviceService = deviceService;
}
</code></pre>
<p>Without the use of a DI service, we would have to manually set up all the constructor calls and somehow manage which dependency goes where. For example, we would have to do the following in order to instantiate the <code>MainPage</code> in the <em>App.xaml.cs</em>:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>)</span>
{
    InitializeComponent();
    <span class="hljs-keyword">var</span> deviceService = DeviceService.Instance;
    <span class="hljs-keyword">var</span> viewModel = <span class="hljs-keyword">new</span> MainViewModel(deviceService);
    MainPage = <span class="hljs-keyword">new</span> MainPage(viewModel);
}
</code></pre>
<p>This works perfectly fine as long as the app is small and doesn't use Shell; but, what if we have many different pages and want to use Shell to build our app hierarchy using routes and <code>&lt;ShellContent&gt;</code> objects?</p>
<p>One thing we definitely couldn't do is the following, because in XAML you cannot provide constructor parameters to a <code>DataTemplate</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Shell</span>
  <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiSamples.AppShell"</span>
  <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
  <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
  <span class="hljs-attr">xmlns:views</span>=<span class="hljs-string">"clr-namespace:MauiSamples.Views"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">TabBar</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Tab</span> <span class="hljs-attr">Title</span>=<span class="hljs-string">"Home"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ShellContent</span>
        <span class="hljs-attr">Title</span>=<span class="hljs-string">"Home"</span>
        <span class="hljs-attr">ContentTemplate</span>=<span class="hljs-string">"{DataTemplate views:MainPage}"</span>
        <span class="hljs-attr">Route</span>=<span class="hljs-string">"MainPage"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Tab</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Shell</span>&gt;</span>
</code></pre>
<p>The <code>MainPage</code> takes a <code>MainViewModel</code> instance as a constructor parameter, but we cannot provide that in XAML.</p>
<p>This is where MAUI's built-in dependency injection comes in. It uses its own IoC container and performs the resolution of the dependencies for us automatically, provided that we register all the required dependencies.</p>
<p>So, let's have a look at how registration works.</p>
<h1 id="heading-registration">Registration</h1>
<p>In MAUI, we use the builder pattern, which is a common approach for app configuration in modern .NET applications and technologies. It makes the configuration of features and dependencies very straightforward and keeps it all in a single place.</p>
<p>The <em>MauiProgram.cs</em> is the place where the app is usually configured and built. There, you will find something like this:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MauiApp <span class="hljs-title">CreateMauiApp</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> builder = MauiApp.CreateBuilder();
    builder
        .UseMauiApp&lt;App&gt;()
        .ConfigureFonts(fonts =&gt;
        {
            fonts.AddFont(<span class="hljs-string">"OpenSans-Regular.ttf"</span>, <span class="hljs-string">"OpenSansRegular"</span>);
            fonts.AddFont(<span class="hljs-string">"OpenSans-Semibold.ttf"</span>, <span class="hljs-string">"OpenSansSemibold"</span>);
        })

    <span class="hljs-keyword">return</span> builder.Build();
}
</code></pre>
<p>The <code>builder</code> object provides a <code>Services</code> collection where all dependencies get registered. This is how we let MAUI know that we have classes that have dependencies or that are dependencies for other classes.</p>
<p>Registering a new dependency is very easy and just takes a single line of code anywhere before the <code>return builder.Build()</code> call:</p>
<pre><code class="lang-csharp">builder.Services.AddSingleton&lt;MainViewModel&gt;();

<span class="hljs-keyword">return</span> builder.Build();
</code></pre>
<p>The code above is used to register the <code>MainViewModel</code> class. However, we also need to register the <code>MainPage</code> as well for the automatic resolution to work:</p>
<pre><code class="lang-csharp">builder.Services.AddSingleton&lt;MainViewModel&gt;();
builder.Services.AddSingleton&lt;MainPage&gt;();

<span class="hljs-keyword">return</span> builder.Build();
</code></pre>
<p>So, how does resolution work?</p>
<h1 id="heading-resolution">Resolution</h1>
<p>There are two types of dependency resolution in MAUI apps: <em>automatic</em> and <em>explicit</em>.</p>
<p>Automatic resolution uses constructor injection without explicitly requesting the dependency from the IoC container, while explicit resolution occurs on demand by explicitly requesting a specific dependency from the IoC container.</p>
<h2 id="heading-automatic-resolution-with-shell">Automatic Resolution with Shell</h2>
<p>As we've already seen above, this type of resolution works <em>- as the name suggests -</em> completely automatically by providing the required dependencies to the constructor:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IDeviceService _deviceService;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainViewModel</span>(<span class="hljs-params">IDeviceService deviceService</span>)</span>
{
    _deviceService = deviceService;
}
</code></pre>
<p>Shell takes care of this for you as long as you register the dependency's type as well as the type that uses this dependency, e.g. <code>MainViewModel</code> and an implementation of <code>IDeviceService</code>.</p>
<blockquote>
<p><strong>Note:</strong> Only Shell apps support automatic constructor injection.</p>
</blockquote>
<p>This is pretty cool, because we do not have to pass around our dependencies ourselves, Shell takes care of it and manages the lifetime and handles the injection for us.</p>
<h2 id="heading-explicit-resolution">Explicit Resolution</h2>
<p>If your class only exposes a parameterless constructor, then Shell cannot inject dependencies automatically for you. Or if you don't use Shell, because you want to use a <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/user-interface/pages/flyoutpage">FlyoutPage</a> or implement navigation completely manually without using routes, then you also cannot use the automatic resolution mechanism. Therefore, we need an explicit way to resolve dependencies.</p>
<blockquote>
<p><strong>Note:</strong> I have included the following approaches to show that they are viable options. However, if you need to resolve dependencies manually in many different places or you have complex scenarios with deep dependency trees, you may want to use an independent DI framework or IoC container.</p>
</blockquote>
<h3 id="heading-accessing-the-iserviceprovider">Accessing the IServiceProvider</h3>
<p>Imagine our <code>MainViewModel</code> for some reason doesn't provide a constructor with the <code>IDeviceService</code> parameter:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IDeviceService _deviceService;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainViewModel</span>(<span class="hljs-params"></span>)</span> { }
</code></pre>
<p>In that case, we need to resolve the dependency manually by directly accessing the IoC container, e.g. in the constructor. One way to do this is the following, where we access the <code>IServiceProvider</code> (i.e. the <code>Services</code> object) via the <code>MauiContext</code> provided by our current <code>MainPage</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IDeviceService _deviceService;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainViewModel</span>(<span class="hljs-params"></span>)</span>
{
    _deviceService = Application.Current.MainPage
        .Handler
        .MauiContext
        .Services  <span class="hljs-comment">// IServiceProvider</span>
        .GetService&lt;IDeviceService&gt;();
}
</code></pre>
<p>One downside of this approach is that we have a dependency on the <code>Application</code> and the <code>MainPage</code> in our <code>MainViewModel</code>, which kind of defeats the purpose of using IoC in .NET applications following the MVVM pattern. This also makes automatic testing tricky, because if we want write unit tests for our <code>MainViewModel</code>, we shouldn't have any dependencies on the <code>Application</code> or the <code>MainPage</code>.</p>
<p>Alternatively, we can also pass down the <code>IServiceProvider</code> via the constructor <em>(either through automatic resolution or by manually injecting it in the constructor call)</em> and then resolve the required dependencies manually:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IDeviceService _deviceService;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IAudioService _audioService;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainViewModel</span>(<span class="hljs-params">IServiceProvider provider</span>)</span>
{
    _deviceService = provider.GetService&lt;IDeviceService&gt;();
    _audioService = provider.GetService&lt;IAudioService&gt;();
}
</code></pre>
<p>This slightly different approach allows us to keep the constructor signature short and allows us to use mocking frameworks with unit tests. This approach also works with Shell's automatic resolution without having to manually register the <code>IServiceProvider</code>.</p>
<p>😓 That'll work, but it's not pretty and we have to always pass down the <code>IServiceProvider</code>. Isn't there a better way? As you may have guessed: There is. 🤩</p>
<h3 id="heading-servicehelper-to-the-rescue">ServiceHelper to the rescue!</h3>
<p>If you don't want to or cannot use any constructor injection, then there is a better way to deal with the <code>IServiceProvider</code>, which is to create a static <code>ServiceHelper</code> class:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ServiceHelper</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IServiceProvider Services { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Initialize</span>(<span class="hljs-params">IServiceProvider serviceProvider</span>)</span> =&gt; 
        Services = serviceProvider;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> T <span class="hljs-title">GetService</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params"></span>)</span> =&gt; Services.GetService&lt;T&gt;();
}
</code></pre>
<p>Then, in your <em>MauiProgram.cs</em> you just need to call the Initialize method and pass in the <code>IServiceProvider</code> instance that the <code>MauiApp</code> class exposes once it was built:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MauiApp <span class="hljs-title">CreateMauiApp</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> builder = MauiApp.CreateBuilder();

    <span class="hljs-comment">// ...</span>

    builder.Services.AddSingleton&lt;IDeviceService&gt;(DeviceService.Instance);
    builder.Services.AddSingleton&lt;MainViewModel&gt;();
    builder.Services.AddSingleton&lt;MainPage&gt;();

    <span class="hljs-keyword">var</span> app = builder.Build();

    <span class="hljs-comment">//we must initialize our service helper before using it</span>
    ServiceHelper.Initialize(app.Services);

    <span class="hljs-keyword">return</span> app;
}
</code></pre>
<p>Then, in order to resolve the dependencies, you can access the static helper and call the <code>GetService&lt;T&gt;()</code> method:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IDeviceService _deviceService;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IAudioService _audioService;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainViewModel</span>(<span class="hljs-params"></span>)</span>
{
    _deviceService = ServiceHelper.GetService&lt;IDeviceService&gt;();
    _audioService = ServiceHelper.GetService&lt;IAudioService&gt;();
}
</code></pre>
<p>The beauty of this approach is that we don't need to pass down the <code>IServiceProvider</code> in every single constructor call when we're not using constructor parameters and at the same time we can configure the <code>ServiceHelper</code> using a fake or mock implementation of <code>IServiceProvider</code> when writing unit tests.</p>
<p>Awesome! 🏆</p>
<blockquote>
<p><strong>Note</strong>: I am aware that the ServiceHelper is an implementation of the Service Locator pattern, which is often regarded as an anti-pattern. However, in certain scenarios, especially with regards to .NET-baseed UI technologies, the Service Locator pattern is a necessity.</p>
</blockquote>
<h1 id="heading-lifetime-of-dependencies">Lifetime of Dependencies</h1>
<p>As you may have noticed before, we have so far registered the dependencies using a method called <code>AddSingleton&lt;T&gt;()</code>. This group of methods can be used to register a single instance (hence <em>singleton</em>) of a class, which means that every time a dependency gets resolved, the exact same instance will be provided by the IoC container during the entire lifetime of the app.</p>
<p>There are three different lifetime modes for dependencies:</p>
<ul>
<li><p>Singleton</p>
</li>
<li><p>Transient</p>
</li>
<li><p>Scoped</p>
</li>
</ul>
<p>The first one, as already mentioned, registers a single instance which will be kept <em>alive</em> during the entire lifetime of our app. This is done via the <code>AddSingleton&lt;T&gt;()</code> methods.</p>
<blockquote>
<p><strong>Note:</strong> Here, the term singleton does not mean the Singleton Design Pattern.</p>
</blockquote>
<p>The second mode is called <em>transient</em> and means that whenever a dependency registered with this mode gets resolved, a fresh instance of the registered type is provided. Transients can be registered using the <code>AddTransient&lt;T&gt;()</code> method(s).</p>
<p>Last but not least, there is the <em>scoped</em> mode, which is a little tricky to understand. Basically, <em>scoped</em> services or dependencies share the same lifetime as the Page or object that resolves them. If a non-singleton Page is closed or an object goes out of scope, so does the <em>scoped</em> dependency. This means resolving the same dependency multiple times within the same scope will always yield the same instance, while resolving the same dependency in different scopes will yield different instances. This can be particularly useful when components (e.g. Views) of the same Page share the same dependencies while different Pages should each have their own instance. <em>Scoped</em> dependencies can be registered using the <code>AddScoped&lt;T&gt;()</code> method(s).</p>
<blockquote>
<p><strong>Note:</strong> There are multiple methods for each type, because you can register contracts (e.g. interfaces and abstract base classes) together with a specific type as well as instances and factories.</p>
</blockquote>
<p>Personally, I do not need the <em>scoped</em> mode very often, I prefer the <em>singleton</em> and <em>transient</em> modes as they are sufficient for the more common scenarios.</p>
<h1 id="heading-common-pitfalls-and-gotchas">Common Pitfalls and <em>Gotchas!</em></h1>
<p>There are a couple of common mistakes and issues that I have noticed some people struggling with over the last months, there have been various Stack Overflow questions related to these problems. To make your life easier, I want to address a couple of them here.</p>
<h2 id="heading-always-register-all-dependencies">Always register all dependencies</h2>
<p>Make sure to always register <strong><em>all</em></strong> dependencies <em>including any classes that require these dependencies,</em> otherwise you may see strange exceptions and errors.</p>
<p>One common mistake that I see people make very often is that they register their ViewModels, but they don't register the Pages that use those ViewModels in their constructor and then they see the following runtime error:</p>
<blockquote>
<p>System.MissingMethodException: 'No parameterless constructor defined for type 'MauiSamples.Views.MainPage'.'</p>
</blockquote>
<p>This can be resolved by registering the <code>MainPage</code> along with the <code>MainViewModel</code>:</p>
<pre><code class="lang-csharp">builder.Services.AddSingleton&lt;MainViewModel&gt;();
<span class="hljs-comment">// do NOT forget to also register the MainPage!</span>
builder.Services.AddSingleton&lt;MainPage&gt;();
</code></pre>
<p>The same applies to any other dependencies that are automatically resolved. If you have a ViewModel that takes a dependency as a constructor parameter, then you need to register the ViewModel and all of its dependencies.</p>
<h2 id="heading-automatic-constructor-injection">Automatic constructor injection</h2>
<p>Automatic resolution only works in the context of Shell, because Shell takes care of construction for you. If you use constructor injection without Shell by manually creating instances of your classes through explicit constructor calls, then you need to specify the dependencies in the constructor call yourself.</p>
<p>For example, when you use automatic resolution in your MAUI Shell app, but you also write unit tests for your ViewModel, which has no <em>parameterless</em> constructor, then you need to provide the dependencies (e.g. by using mocks or fakes) yourself:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Test</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SomeMethod_ArgumentsAreValid_ReturnsTrue</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-comment">//arrange</span>
    <span class="hljs-keyword">var</span> deviceServiceMock = <span class="hljs-keyword">new</span> Mock&lt;IDeviceService&gt;();
    <span class="hljs-keyword">var</span> vm = <span class="hljs-keyword">new</span> MainViewModel(deviceServiceMock.Object);

    <span class="hljs-comment">//act</span>
    <span class="hljs-keyword">var</span> result = vm.SomeMethod(<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>);

    <span class="hljs-comment">//assert</span>
    Assert.That(result, Is.True);
}
</code></pre>
<p>The same applies when you manually create Page instances instead of using routes, which then leads to the creation of a <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/shell/navigation#perform-navigation">Navigation Stack</a>, which is not governed by Shell.</p>
<h2 id="heading-resolving-unknown-types">Resolving unknown types</h2>
<p>If you attempt to resolve a type that has not been registered, you will see an exception. My recommendation is to not "swallow" this exception by using a <em>catch-all</em> clause. Instead, you should let your app crash in this case, because mandatory dependencies are required for the correct functioning of an app and it's usually the developer's fault when the required dependencies don't get registered. <em>Fail early, fail often</em> is the premise here.</p>
<h2 id="heading-adding-dependencies-during-runtime">Adding dependencies during runtime</h2>
<p>Unfortunately, it's not possible to register, replace or update dependencies during the lifetime of a .NET MAUI app, because the <a target="_blank" href="https://stackoverflow.com/a/72554375/4308455">service collection cannot be modified</a> after the application is built. Bummer. If you have a scenario where you need to modify the dependencies frequently, e.g. based on user settings, etc. then you can either register a service provider that can handle this for you or you can use a third-party DI framework like the ones I've mentioned earlier.</p>
<h1 id="heading-conclusions-and-next-steps">Conclusions and Next Steps</h1>
<p>Dependency injection is a powerful first-class citizen in .NET MAUI and comes directly built-in, which is awesome, because we don't have to use any third-party frameworks or libraries, as long as we do not need to do anything beyond the restrictions and limitations of the built-in dependency injection mechanism.</p>
<p>Today, I have shown you how to leverage Shell's automatic constructor injection as well as ways to manually resolve dependencies. I might write another blog article on dependency injection and unit tests in .NET MAUI in the future. There are viable solutions to this problem, e.g. by mocking the <code>IServiceProvider</code> and using it with the static <code>ServiceHelper</code> class. Let me know if this is something you would like to read more about.</p>
<p>I hope this is useful for you. If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>GitHub</strong> <strong>repository</strong></a> for this post so you don't miss out on any future posts and developments.</p>
]]></content:encoded></item><item><title><![CDATA[Customize the Title Bar of a MAUI app with these simple steps]]></title><description><![CDATA[Introduction
Many mobile applications come with their own, custom title styling. This is important, because the title bar (or navigation bar, I will use the terms interchangeably here) plays a vital role in brand recognition since it is visible at th...]]></description><link>https://blog.ewers-peters.de/customize-the-title-bar-of-a-maui-app-with-these-simple-steps</link><guid isPermaLink="true">https://blog.ewers-peters.de/customize-the-title-bar-of-a-maui-app-with-these-simple-steps</guid><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[app development]]></category><category><![CDATA[navigation bar]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Thu, 13 Apr 2023 08:06:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1681373148272/ed9522ef-a7df-482d-8342-7ebb28081cff.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Many mobile applications come with their own, custom title styling. This is important, because the title bar <em>(or navigation bar, I will use the terms interchangeably here)</em> plays a vital role in brand recognition since it is visible at the top of an app.</p>
<p>In this blog post, I will show you how you can customize the navigation bar of a .NET MAUI Shell app. The goal is to add a custom font, title and some buttons to it. The final result will look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681293436143/f572902b-2e51-4688-b82c-862dee714c25.png" alt class="image--center mx-auto" /></p>
<p>You can find the example code for this blog post in my <a target="_blank" href="https://github.com/ewerspej/maui-samples">MAUI Samples</a> repository.</p>
<h1 id="heading-prerequisites">Prerequisites</h1>
<p>Before we can start with the customization of the <em>TitleView</em>, we need to add some fonts. I'm using <code>MaterialIconsOutlined-Regular.otf</code> for the icons <em>(available in the</em> <a target="_blank" href="https://github.com/google/material-design-icons/tree/master/font">Google Material Icons</a> <em>GitHub repository)</em> and <code>Strande2.ttf</code> for the Title.</p>
<p>Adding font files couldn't be simpler in .NET MAUI, we just need to add them to the <em>Resources/Fonts/</em> directory of our MAUI app project and register them:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681288208895/27d8b6c8-6c70-4141-be8d-101d1407b7ab.png" alt class="image--center mx-auto" /></p>
<p>It's not necessary to set the Build Action for each file to <code>MauiFont</code> separately, because this happens automatically as long as the files are added to the <em>Fonts</em> directory. We can see why this works when we edit the <em>.csproj</em> file, where we will find the following:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Custom Fonts --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">MauiFont</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Fonts\*"</span> /&gt;</span>
</code></pre>
<p>This means that adding the font files to the <em>Fonts</em> directory is all we need to do, they will be picked up as <code>MauiFont</code> files automatically.</p>
<p>Then, we just need to register the fonts in our <em>MauiProgram.cs</em> and we're good to go:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MauiProgram</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MauiApp <span class="hljs-title">CreateMauiApp</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp&lt;App&gt;()
            .ConfigureFonts(fonts =&gt;
            {
                fonts.AddFont(<span class="hljs-string">"OpenSans-Regular.ttf"</span>, <span class="hljs-string">"OpenSansRegular"</span>);
                fonts.AddFont(<span class="hljs-string">"OpenSans-Semibold.ttf"</span>, <span class="hljs-string">"OpenSansSemibold"</span>);
                fonts.AddFont(<span class="hljs-string">"MaterialIconsOutlined-Regular.otf"</span>, <span class="hljs-string">"MaterialIconsOutlined-Regular"</span>);
                fonts.AddFont(<span class="hljs-string">"MaterialIcons-Regular.ttf"</span>, <span class="hljs-string">"MaterialIcons-Regular"</span>);
                fonts.AddFont(<span class="hljs-string">"Strande2.ttf"</span>, <span class="hljs-string">"Strande2"</span>);
            });

        <span class="hljs-keyword">return</span> builder.Build();
    }
}
</code></pre>
<p>We can now access the fonts by their name (excluding the file extension). More on that a bit further down.</p>
<p>First, it's time to start with the customization of the <em>TitleView</em>.</p>
<h1 id="heading-customizing-the-title-bar">Customizing the Title Bar</h1>
<p>Let's start with the background color, then we'll customize the title and finally, we'll add some buttons.</p>
<h2 id="heading-background-color">Background Color</h2>
<p>In a MAUI Shell app, we can access and control the style of the navigation bar in the root of any <em>ContentPage</em>.</p>
<p>In order to change the background color of the navigation bar, we can simply set the <code>Shell.BackgroundColor</code> to any valid value, either a named color <em>(e.g. "DarkGreen")</em> or a hex code <em>(such as "#01AB67")</em>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage</span>
  <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiSamples.Views.MainPage"</span>
  <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
  <span class="hljs-attr">Shell.BackgroundColor</span>=<span class="hljs-string">"DarkGreen"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- ... --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage</span>&gt;</span>
</code></pre>
<p>This will give us the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681288958134/9079e657-6b1a-4650-83d1-11e0825d2c98.png" alt class="image--center mx-auto" /></p>
<p>Cool, we have a green background. Next, we'll change the font and text.</p>
<h2 id="heading-title-font-and-text">Title Font and Text</h2>
<p>In a MAUI Shell app, you can set an entirely custom <em>TitleView</em>, which means that you can add <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/shell/pages#display-views-in-the-navigation-bar">all kinds of views into the <em>TitleView</em></a>, such as <em>Layouts</em>, <em>Labels</em>, <em>Buttons</em>, <em>Images</em>, and so on. In this blog post, I'll keep it simple, so we'll just add a <em>HorizontalStackLayout</em> with a single <em>Label</em>.</p>
<p>Since we've already added our custom font, we can now use that in the <em>Label</em> by setting the <code>FontFamily="Strande2"</code>. We can set the custom <em>TitleView</em> like this by setting the <code>Shell.TitleView</code> property:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Shell.TitleView</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">HorizontalStackLayout</span> <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Fill"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
      <span class="hljs-attr">Text</span>=<span class="hljs-string">"Welcome to MAUI"</span>
      <span class="hljs-attr">FontFamily</span>=<span class="hljs-string">"Strande2"</span>
      <span class="hljs-attr">TextColor</span>=<span class="hljs-string">"White"</span>
      <span class="hljs-attr">VerticalTextAlignment</span>=<span class="hljs-string">"Center"</span>
      <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
      <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"50"</span>
      <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Medium"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">HorizontalStackLayout</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Shell.TitleView</span>&gt;</span>
</code></pre>
<p>Our <em>TitleView</em> now looks like this with a green background, as well as a custom font and title:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681289293837/a2615dd5-61b2-402b-95ea-eb0f292003e7.png" alt class="image--center mx-auto" /></p>
<p>The only thing missing are the buttons. Let's do that next using <em>ToolbarItems</em>.</p>
<h2 id="heading-toolbar-items">Toolbar Items</h2>
<p>MAUI gives us different options to add buttons to the navigation bar. One of them would be to add more elements to the <em>HorizontalStackLayout</em> inside our custom <em>TitleView</em>. Another way is to use <em>ToolbarItems</em>, which is what we'll do for the buttons.</p>
<p>While adding <em>Buttons</em> and <em>Images</em> to the <em>TitleView</em> directly gives us more control over their appearance, especially their position, <em>ToolbarItems</em> have the advantage that they position themselves according to the order they are defined in. They are right-aligned by default, with the first item being the left-most. This means that new items will be added to the right, while existing items are shifted to the left.</p>
<p><em>ToolbarItems</em> can either use text or icons. For icons, the <code>ToolbarItem</code> class comes with an <code>IconImageSource</code> property, which is of type <code>ImageSource</code>. This means that we can also use a <code>FontImageSource</code> to provide the icon for our buttons based on a custom font.</p>
<blockquote>
<p>You can find more information on <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/user-interface/fonts#display-font-icons">Icon Fonts in MAUI</a> in the official documentation.</p>
</blockquote>
<p>Let's add the first button using such a <code>FontImageSource</code> by using the <code>MaterialIconsOutlined-Regular</code> icon font:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.ToolbarItems</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem</span> <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding SetLowBrightnessCommand}"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem.IconImageSource</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">FontImageSource</span>
        <span class="hljs-attr">FontFamily</span>=<span class="hljs-string">"MaterialIconsOutlined-Regular"</span>
        <span class="hljs-attr">Glyph</span>=<span class="hljs-string">"<span class="hljs-symbol">&amp;#xe51c;</span>"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem.IconImageSource</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.ToolbarItems</span>&gt;</span>
</code></pre>
<p>The <code>FontFamily</code> property is used to select the icon font and the <code>Glyph</code> property is used to set select the specific icon. The <code>ToolbarItem</code> also supports binding to a <code>Command</code> from our <em>ViewModel</em>.</p>
<blockquote>
<p><strong>Note:</strong> Google offers a user-friendly cheat sheet to view the <a target="_blank" href="https://fonts.google.com/icons">Material Symblos and Icons</a> including their code points. The code point of the Glyph in this example is <code>e51c</code>. Glyphs are individual characters from a character set or font and use the <code>"&amp;#xCODE;"</code> notation.</p>
</blockquote>
<p>Adding the first <code>ToolbarItem</code> gives us the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681291250668/895a8505-5f92-4b5c-a540-7d4655cc42d3.png" alt class="image--center mx-auto" /></p>
<p>As you can see, the <code>ToolbarItem</code> is completely right-aligned, we didn't have to specify the size or position for that.</p>
<p>Let's add another <code>ToolbarItem</code> below the first one in the XAML code to see what happens:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.ToolbarItems</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem</span> <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding SetLowBrightnessCommand}"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem.IconImageSource</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">FontImageSource</span>
        <span class="hljs-attr">FontFamily</span>=<span class="hljs-string">"MaterialIconsOutlined-Regular"</span>
        <span class="hljs-attr">Glyph</span>=<span class="hljs-string">"<span class="hljs-symbol">&amp;#xe51c;</span>"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem.IconImageSource</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem</span> <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding SetHighBrightnessCommand}"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem.IconImageSource</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">FontImageSource</span>
        <span class="hljs-attr">FontFamily</span>=<span class="hljs-string">"MaterialIconsOutlined-Regular"</span>
        <span class="hljs-attr">Glyph</span>=<span class="hljs-string">"<span class="hljs-symbol">&amp;#xe518;</span>"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem.IconImageSource</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.ToolbarItems</span>&gt;</span>
</code></pre>
<p>This gives us the following result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681291415457/46013c74-5e41-4644-8417-b89725727e41.png" alt class="image--center mx-auto" /></p>
<p>As expected, the first <code>ToolbarItem</code> shifted to the left, the second one got added right to it and the entire collection of <em>ToolbarItems</em> remains right-aligned.</p>
<h2 id="heading-customizing-the-toolbar-items">Customizing the Toolbar Items</h2>
<p>Each <code>ToolbarItem</code> can be configured based on your needs. For example, you can set different colors for each <code>FontIconSource</code> and even change the size of each:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.ToolbarItems</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem</span> <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding SetLowBrightnessCommand}"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem.IconImageSource</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">FontImageSource</span>
        <span class="hljs-attr">FontFamily</span>=<span class="hljs-string">"MaterialIconsOutlined-Regular"</span>
        <span class="hljs-attr">Glyph</span>=<span class="hljs-string">"<span class="hljs-symbol">&amp;#xe51c;</span>"</span>
        <span class="hljs-attr">Color</span>=<span class="hljs-string">"LightBlue"</span>
        <span class="hljs-attr">Size</span>=<span class="hljs-string">"20"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem.IconImageSource</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem</span> <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding SetHighBrightnessCommand}"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem.IconImageSource</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">FontImageSource</span>
        <span class="hljs-attr">FontFamily</span>=<span class="hljs-string">"MaterialIconsOutlined-Regular"</span>
        <span class="hljs-attr">Glyph</span>=<span class="hljs-string">"<span class="hljs-symbol">&amp;#xe518;</span>"</span>
        <span class="hljs-attr">Color</span>=<span class="hljs-string">"LightPink"</span>
        <span class="hljs-attr">Size</span>=<span class="hljs-string">"32"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem.IconImageSource</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.ToolbarItems</span>&gt;</span>
</code></pre>
<p>This will result in something like below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681292576399/c8f33e02-6037-498e-8754-7f60f9788296.png" alt class="image--center mx-auto" /></p>
<p>As you can see, those items have different sizes and colors - pretty cool!</p>
<h2 id="heading-secondary-toolbar-items">Secondary Toolbar Items</h2>
<p>Adding more and more items would eventually leave no more space for the title, which would at some point get covered by <em>ToolbarItems</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681370848199/d605b124-6bc2-4600-88ab-d260365943d0.png" alt class="image--center mx-auto" /></p>
<p>Therefore, if we wanted to add more items, we can do so by placing them into a secondary toolbar menu. <em>Secondary ToolbarItems</em> can be defined by setting their <code>Order</code> property to <code>Secondary</code>:</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.ToolbarItems</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- ... --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem</span>
      <span class="hljs-attr">Order</span>=<span class="hljs-string">"Secondary"</span>
      <span class="hljs-attr">Text</span>=<span class="hljs-string">"Item 3"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ToolbarItem</span>
      <span class="hljs-attr">Order</span>=<span class="hljs-string">"Secondary"</span>
      <span class="hljs-attr">Text</span>=<span class="hljs-string">"Item 4"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ToolbarItem</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.ToolbarItems</span>&gt;</span>
</code></pre>
<p>If secondary <em>ToolbarItems</em> are defined, a little burger menu icon appears on the right of the tool bar:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681292889360/66807f27-b1bb-40cb-836f-34cd55b3a85a.png" alt class="image--center mx-auto" /></p>
<p>The secondary items only become visible after tapping the burger menu:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681292934150/c0f7150d-9244-4e33-a7b7-2b7ffeeb5649.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong><em>Note:</em></strong> <em>Secondary-level ToolbarItems do not support icons, only text will be shown.</em></p>
</blockquote>
<p>And that's it, we're done. 🏆 Awesome, you just learned how to customize the TitleView of a .NET MAUI Shell app! 💪</p>
<h1 id="heading-conclusion-and-next-steps">Conclusion and next steps</h1>
<p>Using custom fonts, a custom <em>TitleView</em> and some <em>ToolbarItems</em>, we can quickly mesh up our own customizable title bar to make our app stand out. I hope you learned something new today.</p>
<p>There are several different ways to customize the look and feel of the navigation bar (or title bar) in a MAUI app and I've just shown you one way of doing it with the least amount of effort. Platform-specific customizations are also possible, but that would require an entire article on its own <em>(let me know if you're interested in such an article!)</em>.</p>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>GitHub</strong> <strong>repository</strong></a> for this post so you don't miss out on any future posts and developments.</p>
]]></content:encoded></item><item><title><![CDATA[Boost Your App's User Experience with Responsive Layouts for Portrait and Landscape Modes with .NET MAUI]]></title><description><![CDATA[Introduction
When developing mobile apps user experience (UX) is key. Today, we will have a look at the role device orientation plays for user experience and how you can take advantage of .NET MAUI's built-in capabilities.
While Portrait mode has bec...]]></description><link>https://blog.ewers-peters.de/add-responsive-layouts-to-your-maui-app</link><guid isPermaLink="true">https://blog.ewers-peters.de/add-responsive-layouts-to-your-maui-app</guid><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[responsive designs]]></category><category><![CDATA[app development]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[user experience]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Thu, 02 Mar 2023 10:15:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677752040342/dae696e5-7bf9-4250-8a71-1fb92da2e9f4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>When developing mobile apps user experience (UX) is key. Today, we will have a look at the role device orientation plays for user experience and how you can take advantage of <strong>.NET MAUI</strong>'s built-in capabilities.</p>
<p>While <em>Portrait</em> mode has become the default mode for mobile apps, because we often stare at the screen while holding the device in one hand, it may be a good idea to offer a <em>Landscape</em> version of your User Interface (UI) as well. <em>Landscape</em> mode greatly increases the user experience of an app, because the screen width is just too small in <em>Portrait</em> mode when dealing with images, videos and other types of horizontally organized graphics and data. <em>Landscape</em> mode enables your users to view such information and data in a more user-friendly and orientation optimized way.</p>
<p>In this blog article, I will how you how to respond to the device orientation in your .NET MAUI app using <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/triggers#state-triggers">state triggers</a> and update the layout <strong>entirely in XAML markup</strong> - no C# code required. As always, you can find the full code for this post in my <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>sample repository</strong></a>.</p>
<blockquote>
<p><strong>Note:</strong> The concepts in this article are largely applicable to Xamarin.Forms as well.</p>
</blockquote>
<h1 id="heading-portrait-and-landscape">Portrait and Landscape</h1>
<p>Let's have a look at the sample app, which I have extended with a page that displays a video (<em>using the</em> <a target="_blank" href="https://learn.microsoft.com/dotnet/communitytoolkit/maui/views/mediaelement"><em>MediaElement</em></a> <em>from the Community Toolkit)</em> and two custom buttons, one to play and another one to pause the video.</p>
<p><strong>Portrait Mode:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677745814933/bf245baf-3198-49ee-9519-26534bc8b2e3.png" alt="App in Landscape Mode with Video view, Play and Pause buttons below Video" class="image--center mx-auto" /></p>
<p>When the device is in <em>Portrait</em> mode, the page looks fine, the video takes up the entire width of the screen and the buttons are located below the video, as expected.</p>
<p><strong>Landscape Mode:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677746240811/ebe20001-4bfe-4874-8e88-bea9c0a56f19.png" alt="App in Landscape mode with small Video view, Play and Pause buttons below Video" class="image--center mx-auto" /></p>
<p>In <em>Landscape</em> mode, however, the video is extremely small and the buttons are still stacked horizontally below the video, which doesn't look great and provides a poor user experience.</p>
<p>This happens, because the same Layout is used for both <em>Portrait</em> and <em>Landscape</em>. Instead, let's move the video to the left side of the screen and stack the buttons vertically on the right side when the device is in Landscape mode to look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677746367428/1a54f65b-247e-4da8-b047-82429426f69f.png" alt="App in appropriate Landscape mode with large Video view on the left and stacked Button panel on the right" class="image--center mx-auto" /></p>
<p>First, let's have a look at the layout of the page and then make a few changes to it to achieve the desired result.</p>
<h1 id="heading-page-layout">Page Layout</h1>
<p>I have used a <code>Grid</code> to place the video at the top and the buttons at the bottom of the page, where the top row takes up one third of the available space and the bottom row takes up two thirds of the available space by defining them as follows: <code>RowDefinitions="*,2*"</code>.</p>
<p>The <code>MediaElement</code>, which acts as the video player, is placed into the first row, first column of the <code>Grid</code> by setting the attached properties <code>Grid.Row="0"</code> and <code>Grid.Column="0"</code>. This will be useful later on.</p>
<p>The buttons are located inside of a <code>StackLayout</code> which has its <code>Orientation</code> property set to <code>"Horizontal"</code>, which means that they will be placed next to each other horizontally. The <code>StackLayout</code> itself is placed in the second row, first column of the <code>Grid</code>.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ContentPage</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
             <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
             <span class="hljs-attr">xmlns:views</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2022/maui/toolkit"</span>
             <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiSamples.Views.VideoPage"</span>
             <span class="hljs-attr">Shell.NavBarIsVisible</span>=<span class="hljs-string">"False"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
    <span class="hljs-attr">RowDefinitions</span>=<span class="hljs-string">"*,2*"</span>
    <span class="hljs-attr">ColumnDefinitions</span>=<span class="hljs-string">"*"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">views:MediaElement</span>
      <span class="hljs-attr">Grid.Row</span>=<span class="hljs-string">"0"</span>
      <span class="hljs-attr">Grid.Column</span>=<span class="hljs-string">"0"</span>
      <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"VideoPlayer"</span>
      <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Fill"</span>
      <span class="hljs-attr">Source</span>=<span class="hljs-string">"https://github.com/ewerspej/maui-samples/blob/main/assets/frog113403.mp4?raw=true"</span>
      <span class="hljs-attr">ShouldShowPlaybackControls</span>=<span class="hljs-string">"False"</span> /&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">StackLayout</span>
      <span class="hljs-attr">Grid.Row</span>=<span class="hljs-string">"1"</span>
      <span class="hljs-attr">Grid.Column</span>=<span class="hljs-string">"0"</span>
      <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
      <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
      <span class="hljs-attr">Spacing</span>=<span class="hljs-string">"20"</span>
      <span class="hljs-attr">Orientation</span>=<span class="hljs-string">"Horizontal"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
        <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
        <span class="hljs-attr">Text</span>=<span class="hljs-string">"Play"</span>
        <span class="hljs-attr">Pressed</span>=<span class="hljs-string">"OnPlayPressed"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
        <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
        <span class="hljs-attr">Text</span>=<span class="hljs-string">"Pause"</span>
        <span class="hljs-attr">Pressed</span>=<span class="hljs-string">"OnPausePressed"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">StackLayout</span>&gt;</span>

  <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage</span>&gt;</span>
</code></pre>
<p>This layout currently only looks great in <em>Portrait</em> mode, but not in <em>Landscape</em> as we've seen above. Let's fix it so that it looks prettier and more user friendly.</p>
<p>The best part is, we don't need to write a single line of C# code for this and we also don't need completely separate layouts, either. Instead, we can simply modify the existing layout based on the device orientation using styles and triggers. Let me show you how to do that next.</p>
<h1 id="heading-hello-orientationstatetrigger">Hello, <em>OrientationStateTrigger</em>!</h1>
<p>Time to introduce you to a type of trigger called <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/triggers#orientation-state-trigger">OrientationStateTrigger</a>. This trigger comes built-in with .NET MAUI <em>(and Xamarin.Forms)</em> and allows you to use <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/user-interface/visual-states">Visual States</a> and <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/user-interface/styles/xaml">Styles</a> to modify the visual appearance as well as the layout properties of a View.</p>
<h2 id="heading-defining-styles-and-triggers">Defining Styles and Triggers</h2>
<p>For each <a target="_blank" href="https://learn.microsoft.com/dotnet/api/microsoft.maui.controls.visualelement">Visual Element</a> that we need to update based on the device orientation, we can add a Style inside of a <code>ResourceDictionary</code> which we add to our <code>VideoPage</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Grid"</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"VideoGridStyle"</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"StackLayout"</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"ButtonStackStyle"</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ResourceDictionary</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
</code></pre>
<p>Inside of these styles, we can define the different <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/user-interface/visual-states">Visual States</a> that we want to use and attach an <code>OrientationStateTrigger</code> to each. These triggers are then used to apply the associated styles.</p>
<p>Each style receives a setter that defines the different visual states, in our case those visual states will be <code>"Portrait"</code> and <code>"Landscape"</code>. Then, inside of each visual state, we can add the appropriate <code>OrientationStateTrigger</code> for the applicable device orientation:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Grid"</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"VideoGridStyle"</span>&gt;</span><span class="xml">
      <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"VisualStateManager.VisualStateGroups"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">VisualStateGroupList</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">VisualStateGroup</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">VisualState</span> <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"Portrait"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">OrientationStateTrigger</span> <span class="hljs-attr">Orientation</span>=<span class="hljs-string">"Portrait"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">VisualState</span> <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"Landscape"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">OrientationStateTrigger</span> <span class="hljs-attr">Orientation</span>=<span class="hljs-string">"Landscape"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">VisualStateGroup</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">VisualStateGroupList</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Setter</span>&gt;</span>
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- ... --&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ResourceDictionary</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
</code></pre>
<p>Once the triggers are setup, the setters of the visual states for our <em>Layout</em> elements can be added. We'll do this for the <code>Grid</code> and the <code>StackLayout</code> styles, one after the other, next.</p>
<h2 id="heading-visual-state-setters-for-the-grid">Visual State Setters for the Grid</h2>
<p>For our <code>Grid</code>, we need to change the <code>RowDefinitions</code> and <code>ColumnDefinitions</code> based on the device orientation. Instead of having two rows and a single column, we now need a single row and two columns, which we can achieve by adding the following setters:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Grid"</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"VideoGridStyle"</span>&gt;</span><span class="xml">
      <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"VisualStateManager.VisualStateGroups"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">VisualStateGroupList</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">VisualStateGroup</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">VisualState</span> <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"Portrait"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">OrientationStateTrigger</span> <span class="hljs-attr">Orientation</span>=<span class="hljs-string">"Portrait"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.Setters</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"RowDefinitions"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"*,2*"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"ColumnDefinitions"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"*"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.Setters</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">VisualState</span> <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"Landscape"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">OrientationStateTrigger</span> <span class="hljs-attr">Orientation</span>=<span class="hljs-string">"Landscape"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.Setters</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"RowDefinitions"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"*"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"ColumnDefinitions"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"2*,*"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.Setters</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">VisualStateGroup</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">VisualStateGroupList</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Setter</span>&gt;</span>
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- ... --&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ResourceDictionary</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
</code></pre>
<h2 id="heading-visual-state-setters-for-the-stacklayout">Visual State Setters for the StackLayout</h2>
<p>The <code>StackLayout</code> on the other hand needs to change not only its orientation, but also its location <em>inside</em> of the <code>Grid</code> based on the device orientation.</p>
<p>For <em>Portrait</em> mode, the <code>Orientation</code> property must be set to <code>"Horizontal"</code> while it needs to be set to <code>"Vertical"</code> in <em>Landscape</em> mode. In <em>Portrait</em> mode, the <code>StackLayout</code> will be in the second row, first column of the <code>Grid</code>, while it needs to be in the first row, second column of the <code>Grid</code> in <em>Landscape</em> mode. We can achieve this by defining the following setters for the <code>StackLayout</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- ... --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"StackLayout"</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"ButtonStackStyle"</span>&gt;</span><span class="xml">
      <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"VisualStateManager.VisualStateGroups"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">VisualStateGroupList</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">VisualStateGroup</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">VisualState</span> <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"Portrait"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">OrientationStateTrigger</span> <span class="hljs-attr">Orientation</span>=<span class="hljs-string">"Portrait"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.Setters</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Orientation"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"Horizontal"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Grid.Row"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"1"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Grid.Column"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"0"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.Setters</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">VisualState</span> <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"Landscape"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">OrientationStateTrigger</span> <span class="hljs-attr">Orientation</span>=<span class="hljs-string">"Landscape"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.StateTriggers</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.Setters</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Orientation"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"Vertical"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Grid.Row"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"0"</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Grid.Column"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"1"</span> /&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.Setters</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">VisualStateGroup</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">VisualStateGroupList</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Setter</span>&gt;</span>
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ResourceDictionary</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
</code></pre>
<h2 id="heading-applying-the-styles">Applying the Styles</h2>
<p>Last, but not least, we need to apply the styles that we just defined to the <code>Grid</code> and the <code>StackLayout</code> in our page. Note that the styles are named, which means that they must be explicitly assigned to a visual element in order to take effect:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
  <span class="hljs-attr">Style</span>=<span class="hljs-string">"{StaticResource VideoGridStyle}"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">views:MediaElement</span>
    <span class="hljs-attr">Grid.Row</span>=<span class="hljs-string">"0"</span>
    <span class="hljs-attr">Grid.Column</span>=<span class="hljs-string">"0"</span>
    <span class="hljs-attr">Margin</span>=<span class="hljs-string">"0"</span>
    <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"VideoPlayer"</span>
    <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Fill"</span>
    <span class="hljs-attr">Source</span>=<span class="hljs-string">"https://github.com/ewerspej/maui-samples/blob/main/assets/frog113403.mp4?raw=true"</span>
    <span class="hljs-attr">ShouldShowPlaybackControls</span>=<span class="hljs-string">"False"</span> /&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">StackLayout</span>
    <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
    <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
    <span class="hljs-attr">Spacing</span>=<span class="hljs-string">"20"</span>
    <span class="hljs-attr">Style</span>=<span class="hljs-string">"{StaticResource ButtonStackStyle}"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
      <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
      <span class="hljs-attr">Text</span>=<span class="hljs-string">"Play"</span>
      <span class="hljs-attr">Pressed</span>=<span class="hljs-string">"OnPlayPressed"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
      <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
      <span class="hljs-attr">Text</span>=<span class="hljs-string">"Pause"</span>
      <span class="hljs-attr">Pressed</span>=<span class="hljs-string">"OnPausePressed"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">StackLayout</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
</code></pre>
<blockquote>
<p><strong>Important:</strong> In order to apply the styles correctly, we need to make some modifications to the existing views in our page:</p>
<p>The <code>RowDefinitions</code> and <code>ColumnDefinitions</code> assignments of the <code>Grid</code> will be removed, just like the <code>Orientation</code> as well as the <code>Grid.Row</code> and <code>Grid.Column</code> settings of the <code>StackLayout</code>. This is necessary, because property setters that are applied directly on a <code>VisualElement</code> always take precedence over any applied styles, which would prevent the OrientationStateTriggers to update these properties using the defined styles.</p>
</blockquote>
<p>The <code>MediaElement</code> itself does <strong>not</strong> receive any styles or additional property setters, because it already is set up correctly for both device orientations, which is why it keeps its original settings for <code>Grid.Row</code> and <code>Grid.Column</code> <em>(remember when I wrote further up that setting both row and column properties will be useful later on - this is later on)</em>. It will always remain in the first row, first column - independent of the device orientation. This way, we can keep things simple.</p>
<h1 id="heading-result">Result</h1>
<p>Now, when we run the app again and rotate the device into <em>Landscape</em> mode, the video will be located on the left side taking up much of the available vertical space, while the buttons are now stacked vertically on the right:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677746391384/c11e20aa-0d51-4c93-a190-a43f945d9d0f.png" alt="App in appropriate Landscape mode with large Video view on the left and stacked Button panel on the right" class="image--center mx-auto" /></p>
<p>🏆 <strong>Awesome</strong>, this looks so much better! <code>OrientationStateTrigger</code> FTW! 💪</p>
<h1 id="heading-conclusion-and-next-steps">Conclusion and next steps</h1>
<p>Using state triggers and styles, we can easily add responsive layouts to our app based on the device orientation and thus offer a rich user experience for both Portrait and Landscape modes - entirely in XAML, no C# code required.</p>
<p>Combining this approach with some other tricks as well as some platform-specific code, we can even build immersive user experiences, such as full-screen mode, which requires some additional work and will be covered in a separate article.</p>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>GitHub</strong> <strong>repository</strong></a> for this post so you don't miss out on any future posts and developments.</p>
]]></content:encoded></item><item><title><![CDATA[Easily control the executability of Commands using MVVM Source Generators]]></title><description><![CDATA[Introduction
Welcome to the third part of my mini-series about MVVM Source Generators for C# .NET and the awesomeness of the MVVM Community Toolkit. So far, we have seen how we can save ourselves from writing too much boilerplate code when applying t...]]></description><link>https://blog.ewers-peters.de/easily-control-the-executability-of-commands-using-mvvm-source-generators</link><guid isPermaLink="true">https://blog.ewers-peters.de/easily-control-the-executability-of-commands-using-mvvm-source-generators</guid><category><![CDATA[MVVM]]></category><category><![CDATA[sourcegenerators]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[C#]]></category><category><![CDATA[ViewModel]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Fri, 03 Feb 2023 09:05:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1675337175243/6e8bf610-75fb-4dd3-980e-22406eefc1c6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Welcome to the third part of my <a target="_blank" href="https://ewerspej.hashnode.dev/series/mvvm-goodness">mini-series</a> about <a target="_blank" href="https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/generators/overview"><strong>MVVM Source Generators</strong></a> for C# .NET and the awesomeness of the <a target="_blank" href="https://learn.microsoft.com/windows/communitytoolkit/mvvm/introduction"><strong>MVVM Community Toolkit</strong></a>. So far, we have seen how we can save ourselves from writing too much boilerplate code when applying the MVVM pattern in .NET-based app technologies and UI frameworks.</p>
<p>In this part, I will give a short overview of yet another two amazing Source Generators which can be used to <strong>control the executability of <em>Commands</em></strong>. In classic MVVM without Source Generators this would usually be done using the <em>CanExecute</em> predicate of a <em>Command</em>, which enables or disables it. Even with Source Generators you can still achieve exactly the same behavior as you could with classic MVVM implementations.</p>
<p>In this scenario, we do not actually save as much boilerplate code as with the other Source Generators that I have explored previously. However, the attributes we will use are required in situations where you need to enable and disable <em>Commands</em> based on specific conditions, such as the validity of the <em>CommandParameter</em> or of properties in the <em>ViewModel</em> when using Source Generators.</p>
<blockquote>
<p>Special thanks to my colleague and friend <a target="_blank" href="https://www.linkedin.com/in/marco-seraphin-2785b61/">Marco Seraphin</a> for pointing out that this topic would be a great addition to my blog. Marco has inspired this article and has helped by reviewing it.</p>
</blockquote>
<p>Like in my previous posts on the topic, I am using a <em>.NET MAUI</em> project (check out the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>sample repository</strong></a>) to demonstrate the functionality, but since the MVVM Community Toolkit is independent from UI frameworks, the same things apply to technologies like <em>Windows Presentation Foundation</em> (WPF) and <em>Xamarin.Forms</em>.</p>
<h1 id="heading-setting">Setting</h1>
<p>I am reusing the project from the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>sample repository</strong></a> with the address label printing functionality. In the <a target="_blank" href="https://ewerspej.hashnode.dev/mvvm-source-generators-advanced-scenarios">previous post</a> I have added an <code>ActivityIndicator</code> as well as a <code>Stepper</code> control to the project to demonstrate <em>busyness</em> using <code>AsyncRelayCommand</code>.</p>
<p>If you remember from before, the <code>Button</code> to trigger the <code>PrintAddressCommand</code> was always enabled, but nothing would happen if the number of copies was set to 0. Since <em>Commands</em> can have a <code>CanExecute</code> predicate, which is used to determine whether they can be executed or not, they can also be used to enable and disable buttons automatically.</p>
<p>So, for the updated setting, I want the <code>Button</code> only to be enabled when the <em>Command</em> is actually executable, otherwise it should be disabled and thus be grayed out when the number of copies is 0 and the address is empty:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675247214273/9d9adb36-5b7e-41d7-b4cf-9e65cc3645e7.png" alt class="image--center mx-auto" /></p>
<p>Therefore, I have added a new method to the <code>AddressViewModel</code> which can be used as the <code>CanExecute</code> predicate for the <code>PrintAddressCommand</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">CanPrint</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> address</span>)</span> =&gt; Copies &gt; <span class="hljs-number">0</span> &amp;&amp; !<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(address);
</code></pre>
<p>This method checks if the argument that is passed into the <em>Command</em> is valid <em>(for simplicity, I am only checking for null and whitespaces or an empty string)</em> and if the number of copies to print is larger than 0.</p>
<p>When using the <code>CanPrint()</code> method as a predicate in the classic version of the <code>AddressViewModel</code>, the <em>Command</em> would then look like follows:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> IAsyncRelayCommand _printAddressCommand;
<span class="hljs-keyword">public</span> IAsyncRelayCommand PrintAddressCommand =&gt; _printAddressCommand ??= <span class="hljs-keyword">new</span> AsyncRelayCommand&lt;<span class="hljs-keyword">string</span>&gt;(PrintAddressAsync, canExecute: CanPrint);
</code></pre>
<p>Our <code>Button</code> in the XAML of our UI has also been updated to pass in the address as a <em>CommandParameter</em>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
    <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding PrintAddressCommand}"</span>
    <span class="hljs-attr">CommandParameter</span>=<span class="hljs-string">"{Binding FullAddress}"</span>
    <span class="hljs-attr">Text</span>=<span class="hljs-string">"Print Address"</span> /&gt;</span>
</code></pre>
<p>In this case, I am simply passing in the <code>FullAddress</code> property.</p>
<blockquote>
<p><strong>Note:</strong> I have used the <code>FullAddress</code> property as a CommandParameter only for demonstration purposes. Since it's a property of the same ViewModel as the Command, I could have also directly accessed the property inside of the <code>CanPrint()</code> method instead of passing it in.</p>
</blockquote>
<p>This already suffices to enable and disable the <code>Button</code> automatically, we <strong><em>cannot</em></strong> use the <code>IsEnabled</code> property of the <code>Button</code> in this setting and it's also not required.</p>
<blockquote>
<p><strong>Important:</strong> In .NET MAUI and Xamarin.Forms, you should not use the IsEnabled property when using Commands, because it will automatically be overridden when binding to a Command. Find more information in the official <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/commanding#icommands">documentation</a>.</p>
</blockquote>
<h1 id="heading-controlling-executability">Controlling Executability</h1>
<p>Generally, the <code>CanExecute</code> predicate is only evaluated by the <em>Command</em> during instantiation or when an argument is passed to the <em>Command</em> via the <em>CommandParameter</em> property. In the latter case, the <code>CanExecute</code> predicate is evaluated each time that the <em>CommandParameter</em>, which our <code>Button</code> binds to, changes.</p>
<blockquote>
<p><strong>Important:</strong> The method which is used for the predicate must return a <code>bool</code> and optionally may have exactly one input parameter which must correspond to the CommandParameter.</p>
</blockquote>
<p>However, there are also scenarios in which the predicate depends on properties which are not passed in as an argument of the <em>CommandParameter</em>. In these situations, we need to manually inform the <em>Command</em> that something has changed and that its executability should be re-evaluated.</p>
<p>Let's explore how this is usually done using classic MVVM in .NET and then let's have a look at how this can be done using the previously introduced Source Generators.</p>
<h2 id="heading-using-classic-mvvm">Using Classic MVVM</h2>
<p>When using the classic MVVM approach to update the <em>Command</em> and re-evaluate its executability, the ViewModel <em>(stripped down to the relevant bits only)</em> would look as follows:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> IAsyncRelayCommand _printAddressCommand;
<span class="hljs-keyword">public</span> IAsyncRelayCommand PrintAddressCommand =&gt; _printAddressCommand ??= <span class="hljs-keyword">new</span> AsyncRelayCommand&lt;<span class="hljs-keyword">string</span>&gt;(PrintAddressAsync, canExecute: CanPrint);

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> address</span>)</span>
{
    <span class="hljs-keyword">await</span> Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>));
    OnPrintAddress?.Invoke(address);
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">CanPrint</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> address</span>)</span> =&gt; Copies &gt; <span class="hljs-number">0</span> &amp;&amp; !<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(address);

<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _copies;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Copies
{
    <span class="hljs-keyword">get</span> =&gt; _copies;
    <span class="hljs-keyword">set</span>
    {
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">value</span> == _copies)
        {
            <span class="hljs-keyword">return</span>;
        }

        OnPropertyChanging();
        _copies = <span class="hljs-keyword">value</span>;
        OnPropertyChanged();

        PrintAddressCommand.NotifyCanExecuteChanged();
    }
}
</code></pre>
<p>Here, we pass the <code>CanPrint()</code> method, which exists in both versions equally, as an argument to the Command's <code>canExecute</code> parameter. The <em>CanExecute</em> predicate of the <em>Command</em> is evaluated each time when the <em>CommandParameter</em>, the <code>address</code> string, changes and it is also re-evaluated when the <code>Copies</code> property changes, because the <em>Command</em> gets notified by calling <code>NotifyCanExecuteChanged()</code> on it.</p>
<p>This already isn't much code thanks to the <code>NotifyCanExecuteChanged()</code> method that the <code>IRelayCommand</code> interface provides.</p>
<h2 id="heading-using-mvvm-source-generators">Using MVVM Source Generators</h2>
<p>Since the Source Generators largely take care of the implementation of properties and commands for us, we need a way to pass in the <code>canExecute</code> parameter to the <em>Command</em>. We also need a way to trigger an update on the <em>Command</em> to re-evaluate its executability. The first attribute might look familiar from prior usage and the second one also has similarities with another attribute that we've already explored before. It's time to look at <code>[RelayCommand]</code> again and then we'll do a short dive into <code>[NotifyCanExecuteChangedFor]</code>.</p>
<h3 id="heading-revisiting-relaycommand">Revisiting [RelayCommand]</h3>
<p>In order to configure and update the executability of a <em>Command</em> via the <code>canExecute</code> parameter, the <code>[RelayCommand]</code> attribute comes with an optional property of type <code>string?</code>, which conveniently is called <code>CanExecute</code> and which is used to provide the name of the method that is used to evaluate the executability of the <em>Command</em>:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">RelayCommand(CanExecute = nameof(CanPrint))</span>]
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> address</span>)</span> { <span class="hljs-comment">/* ... */</span> }
</code></pre>
<p>The main difference compared to the classic approach is that we pass in the <em>name</em> of the method to the Source Generator instead of the method itself.</p>
<p>Under the hood, the Source Generator actually generates a <em>Command</em> which looks remarkably similar to the one from the ViewModel without Source Generators:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AddressViewModelSourceGen</span>
{
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>The backing field for <span class="hljs-doctag">&lt;see cref="PrintAddressCommand"/&gt;</span>.<span class="hljs-doctag">&lt;/summary&gt;</span></span>
    [<span class="hljs-meta">global::System.CodeDom.Compiler.GeneratedCode(<span class="hljs-meta-string">"CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator"</span>, <span class="hljs-meta-string">"8.1.0.0"</span>)</span>]
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">global</span>::CommunityToolkit.Mvvm.Input.AsyncRelayCommand&lt;<span class="hljs-keyword">string</span>&gt;? printAddressCommand;
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>Gets an <span class="hljs-doctag">&lt;see cref="global::CommunityToolkit.Mvvm.Input.IAsyncRelayCommand{T}"/&gt;</span> instance wrapping <span class="hljs-doctag">&lt;see cref="PrintAddressAsync"/&gt;</span>.<span class="hljs-doctag">&lt;/summary&gt;</span></span>
    [<span class="hljs-meta">global::System.CodeDom.Compiler.GeneratedCode(<span class="hljs-meta-string">"CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator"</span>, <span class="hljs-meta-string">"8.1.0.0"</span>)</span>]
    [<span class="hljs-meta">global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">global</span>::CommunityToolkit.Mvvm.Input.IAsyncRelayCommand&lt;<span class="hljs-keyword">string</span>&gt; PrintAddressCommand =&gt; printAddressCommand ??= <span class="hljs-keyword">new</span> <span class="hljs-keyword">global</span>::CommunityToolkit.Mvvm.Input.AsyncRelayCommand&lt;<span class="hljs-keyword">string</span>&gt;(<span class="hljs-keyword">new</span> <span class="hljs-keyword">global</span>::System.Func&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">global</span>::System.Threading.Tasks.Task&gt;(PrintAddressAsync), CanPrint);
}
</code></pre>
<p>As we can see, the name of the method is simply used by the Source Generator to insert it as text into the generated code file and serves as the argument for the <code>canExecute</code> parameter <em>(all the way at the end, where it says</em> <code>CanPrint</code><em>)</em>.</p>
<p>Since the generated <em>Command</em> is of type <code>AsyncRelayCommand&lt;string&gt;</code>, any arguments passed into a <em>Command</em> as a <em>CommandParameter</em> are forwarded to the <code>PrintAddressAsync()</code> and <code>CanPrint()</code> methods respectively.</p>
<p>With this in place, the executability of the <em>Command</em> will be re-evaluated every time that the <em>CommandParameter</em> of our <code>Button</code> changes. But how do we notify the <em>Command</em> about changes to the <code>Copies</code> property from the ViewModel?</p>
<h3 id="heading-introducing-notifycanexecutechangedfor">Introducing [NotifyCanExecuteChangedFor]</h3>
<p>When a property changes and we want to notify subscribers that another property, <em>e.g. a getter-only one</em>, likely has changed as well, we can use the <code>[NotifyPropertyChangedFor]</code> attribute when using Source Generators. Fortunately, a similar attribute exists for commands: <code>[NotifyCanExecuteChangedFor]</code>, which also works in the same fashion.</p>
<p>The <code>[NotifyCanExecuteChangedFor]</code> attribute is used to decorate any property which may be required to determine the executability of a <em>Command</em> and takes the name of the <em>Command</em> as an argument:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ObservableProperty</span>]
[<span class="hljs-meta">NotifyCanExecuteChangedFor(nameof(PrintAddressCommand))</span>]
<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _copies;
</code></pre>
<p>This is all we need to do in order to trigger a re-evaluation of the <code>CanExecute</code> predicate attached to the <code>PrintAddressCommand</code>. Every time that the auto-generated <code>Copies</code> property gets updated, the re-evaluation takes place.</p>
<p>Under the hood, this also looks highly familiar: At the end of the auto-generated property's setter, the <code>NotifyCanExecuteChanged()</code> method is called on the auto-generated <code>PrintAddressCommand</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;inheritdoc cref="_copies"/&gt;</span></span>
[<span class="hljs-meta">global::System.CodeDom.Compiler.GeneratedCode(<span class="hljs-meta-string">"CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator"</span>, <span class="hljs-meta-string">"8.1.0.0"</span>)</span>]
[<span class="hljs-meta">global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Copies
{
    <span class="hljs-keyword">get</span> =&gt; _copies;
    <span class="hljs-keyword">set</span>
    {
        <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">global</span>::System.Collections.Generic.EqualityComparer&lt;<span class="hljs-keyword">int</span>&gt;.Default.Equals(_copies, <span class="hljs-keyword">value</span>))
        {
            OnCopiesChanging(<span class="hljs-keyword">value</span>);
            OnPropertyChanging(<span class="hljs-keyword">global</span>::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Copies);
            _copies = <span class="hljs-keyword">value</span>;
            OnCopiesChanged(<span class="hljs-keyword">value</span>);
            OnPropertyChanged(<span class="hljs-keyword">global</span>::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Copies);
            PrintAddressCommand.NotifyCanExecuteChanged();
        }
    }
}
</code></pre>
<p>This is exactly what I have also done in the ViewModel that does not use the Source Generators of the MVVM Community Toolkit: First, the equality comparison of the <em>backing field</em> and the new <em>value</em> takes place, followed by the notification that the property <em>is about to</em> change, then the backing field gets updated, which is then followed by the notification that the property <em>has</em> <em>just</em> changed and finally, the <code>CanExecuteChanged</code> event of the <code>PrintAddressCommand</code> is raised by calling <code>NotifyCanExecuteChanged()</code>.</p>
<h3 id="heading-viewmodel-with-source-generators">ViewModel with Source Generators</h3>
<p>The completed version of the ViewModel <em>(again stripped down to the relevant bits only)</em> using Source Generators looks as follows:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">RelayCommand(CanExecute = nameof(CanPrint))</span>]
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> address</span>)</span>
{
    <span class="hljs-keyword">await</span> Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>));
    OnPrintAddress?.Invoke(address);
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">CanPrint</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> address</span>)</span> =&gt; Copies &gt; <span class="hljs-number">0</span> &amp;&amp; !<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(address);

[<span class="hljs-meta">ObservableProperty</span>]
[<span class="hljs-meta">NotifyCanExecuteChangedFor(nameof(PrintAddressCommand))</span>]
<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _copies;
</code></pre>
<p>Amazing, this looks (c)lean and beautiful. My developer heart is happy - again 💖.</p>
<h1 id="heading-running-the-sample">Running the sample</h1>
<p>In both versions, we can now run the sample app and see that the <code>Button</code> is only enabled when the address is not empty and at least one copy is set to be printed:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675247242167/6e224685-13f6-4dd5-8246-deaa913805b1.png" alt class="image--center mx-auto" /></p>
<p>Perfect! 💪 There's no need to do without the functionality that <em>CanExecute</em> provides when using auto-generated commands, because the MVVM Source Generators have us covered here, as well.</p>
<h1 id="heading-conclusions-and-next-steps">Conclusions and next steps</h1>
<p>Evaluating the executability of commands is still possible like before even with Source Generators. We can still write exactly the same kind of logic like we did before while saving loads of valuable time and tremendously reducing the risk of bugs, especially when dealing with a multitude of different properties and commands.</p>
<p>Source Generators can help you with decreasing the amount of boilerplate code and make ViewModels much more legible and comprehensible, <em>provided you have a basic understanding of the MVVM pattern.</em> At the same time, you do not have to live without your favorite MVVM features and interfaces, since they are all compatible and complementary, with and without using Source Generators - mix and match, if you will.</p>
<p>In my humble opinion, this is all relatively straightforward, as long as you have a working understanding of the MVVM pattern and its implementation in .NET. I hope that you learned something again from this article.</p>
<p>Last but not least, my friend and colleague Marco has his <a target="_blank" href="https://github.com/marcoseraphin/MVVMToolkitMAUISample">own sample</a> repository on GitHub with another example using the <em>CanExecute</em> predicate, check it out if you like.</p>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>GitHub</strong> <strong>repository</strong></a> for this post so you don't miss out on any future posts and developments.</p>
]]></content:encoded></item><item><title><![CDATA[A quick introduction to Compiled Bindings in .NET applications and why you should use them]]></title><description><![CDATA[Introduction
If you have already used modern .NET-based UI frameworks like Xamarin.Forms and .NET MAUI before, you are likely familiar with a common concept called data binding. This concept is used to access data from a data source (such as properti...]]></description><link>https://blog.ewers-peters.de/a-quick-introduction-to-compiled-bindings</link><guid isPermaLink="true">https://blog.ewers-peters.de/a-quick-introduction-to-compiled-bindings</guid><category><![CDATA[MVVM]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[data binding]]></category><category><![CDATA[compiled bindings]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Wed, 25 Jan 2023 17:00:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1674665713034/0c2ea5b6-cddb-4255-aa59-171ec204a097.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>If you have already used modern .NET-based UI frameworks like <a target="_blank" href="https://learn.microsoft.com/xamarin/get-started/what-is-xamarin-forms">Xamarin.Forms</a> and <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/what-is-maui">.NET MAUI</a> before, you are likely familiar with a common concept called <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/"><strong>data binding</strong></a>. This concept is used to access data from a data source <em>(such as properties inside a ViewModel)</em> from within the user interface and react to changes that may occur during runtime. Classic data binding in .NET based applications comes with a couple of drawbacks, though.</p>
<p><em>Worst of all</em>, they are slow and inefficient, because classic bindings are resolved during <em>runtime</em> using <a target="_blank" href="https://learn.microsoft.com/dotnet/csharp/programming-guide/concepts/reflection"><em>reflection</em></a>, which means that the binding source and target are scanned for matching properties and commands. <em>Secondly</em>, because of this runtime resolution, binding expressions are not evaluated during <em>compile-time</em>. This means that any incorrect bindings will only be noticed during runtime and potentially cause a crash or at the least a somewhat malfunctioning application. Finding these types of bugs can turn out to be a time consuming and finicky task. <em>Lastly</em>, classic bindings require some additional work in order to get useful code-completion suggestions and error messages in the XAML editor window.</p>
<p>Enter <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/compiled-bindings">compiled bindings</a>. In order to speed up data binding and provide compile-time evaluation of binding expressions <em>(and also allow Intellisense and other tools to provide useful suggestions during design-time)</em>, compiled bindings were introduced. According to Microsoft, compiled bindings are up to <strong>20 times (!) faster</strong> than classic bindings**. You should definitely use them**, if you can**.**</p>
<blockquote>
<p><strong>Disclaimer:</strong> This focus of this article is on compiled bindings in Xamarin.Forms and .NET MAUI. However, compiled bindings also exist in Windows Presentation Foundation (WPF), but they work quite differently and won't be covered in this blog article.</p>
</blockquote>
<p>For this blog post, I will use a <em>.NET MAUI</em> project <em>(check out the</em> <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>sample repository</strong></a><em>)</em>, but these concepts largely apply also to Xamarin.Forms.</p>
<h2 id="heading-example-viewmodel">Example ViewModel</h2>
<p>For the bindings in this article, I will use the following, simplified ViewModel <em>(you can find the full code in the</em> <a target="_blank" href="https://github.com/ewerspej/maui-samples"><em>sample repository</em></a><em>)</em>, which has a property called <code>Items</code> of type <code>ObservableCollection&lt;BindingItem&gt;</code> , an <code>AddItemCommand</code> to add new items and a <code>RemoveItemCommand</code> to remove either the last item or a specific item, depending on the <code>CommandParameter</code> that is passed in of type <code>BindingItem</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BindingsViewModel</span> : <span class="hljs-title">ObservableObject</span>
{
    [<span class="hljs-meta">ObservableProperty</span>]
    <span class="hljs-keyword">private</span> ObservableCollection&lt;BindingItem&gt; _items = <span class="hljs-keyword">new</span>();

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _counter = <span class="hljs-number">0</span>;

    [<span class="hljs-meta">RelayCommand</span>]
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AddItem</span>(<span class="hljs-params"></span>)</span>
    {
        Items.Add(<span class="hljs-keyword">new</span> BindingItem
        {
            Name = <span class="hljs-string">$"Item <span class="hljs-subst">{_counter}</span>"</span>,
            Count = _counter
        });

        _counter++;
    }

    [<span class="hljs-meta">RelayCommand</span>]
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">RemoveItem</span>(<span class="hljs-params">BindingItem item = <span class="hljs-literal">null</span></span>)</span>
    {
        <span class="hljs-keyword">if</span> (Items.Count == <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">if</span> (item == <span class="hljs-literal">null</span>)
        {
            Items.Remove(Items.Last());
            <span class="hljs-keyword">return</span>;
        }

        Items.Remove(item);
    }
}
</code></pre>
<h1 id="heading-setting-up-compiled-bindings">Setting up Compiled Bindings</h1>
<p>The central component for compiled bindings is the <code>x:DataType</code> attribute, which exists in both Xamarin.Forms and .NET MAUI. Basically, all we need to do is setting the <code>BindingContext</code> property and the <code>x:DataType</code> attribute in the View and we're good to go <em>(almost at least - there are some common pitfalls that I'll explain further down)</em>.</p>
<blockquote>
<p><strong>Note:</strong> For compiled bindings to be usable, you need make sure that <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/xaml/xamlc">XAML compilation</a> is enabled in your project, which is the default for .NET MAUI. For <a target="_blank" href="https://learn.microsoft.com/xamarin/xamarin-forms/xaml/xamlc">Xamarin.Forms</a> it needs to be explicitly enabled, either globally or locally. More recent versions of Xamarin.Forms also have XAML compilation enabled by default when using the official XAML templates.</p>
</blockquote>
<h2 id="heading-datatype-and-bindingcontext">DataType and BindingContext</h2>
<p>First, we need to set the <code>x:DataType</code> attribute of our <code>ContentPage</code> or <code>ContentView</code> on the root node in the XAML file to the underlying data type of the <code>BindingContext</code> of our View, which usually is a ViewModel:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ContentPage</span>
  <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiSamples.Views.BindingsPage"</span>
  <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
  <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
  <span class="hljs-attr">xmlns:viewModels</span>=<span class="hljs-string">"clr-namespace:MauiSamples.ViewModels"</span>
  <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"viewModels:BindingsViewModel"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- ... --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage</span>&gt;</span>
</code></pre>
<p>Here, I pass the name of the ViewModel including its namespace, so that it's <em>fully qualified</em>, because only fully-qualified names can be used in XAML compilation.</p>
<p>Once this is done, we still need to set the <code>BindingContext</code> for the View in the code-behind (the View's <em>.xaml.cs</em> file):</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BindingsPage</span> : <span class="hljs-title">ContentPage</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BindingsPage</span>(<span class="hljs-params">BindingsViewModel viewModel</span>)</span>
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}
</code></pre>
<p>Done. This is all we need in order to set up compiled bindings. Pretty simple 😊.</p>
<p>Technically, this could also be done entirely in XAML, but I'll stick to the more common way of setting the <code>BindingContext</code> in the code-behind, which is also what I recommend to you, because in real world applications, you would typically use dependency injection to pass a ViewModel instance into the View instead of instantiating it yourself in the View's XAML or code-behind.</p>
<blockquote>
<p><strong>Note:</strong> The <code>x:DataType</code> can be defined on any level in the XAML hierarchy and then only applies downward from that level in the XAML tree. However, it is recommended to set the <code>x:DataType</code> on the root level and then redefine it on deeper nested elements, if necessary (more on this further down).</p>
</blockquote>
<h2 id="heading-using-bindings">Using Bindings</h2>
<p>Now, with the <code>x:DataType</code> and the <code>BindingContext</code> set, we can go ahead and bind to the properties and commands of our ViewModel as usual:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ContentPage</span>
  <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiSamples.Views.BindingsPage"</span>
  <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
  <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
  <span class="hljs-attr">xmlns:models</span>=<span class="hljs-string">"clr-namespace:MauiSamples.Models"</span>
  <span class="hljs-attr">xmlns:viewModels</span>=<span class="hljs-string">"clr-namespace:MauiSamples.ViewModels"</span>
  <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"viewModels:BindingsViewModel"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
    <span class="hljs-attr">RowDefinitions</span>=<span class="hljs-string">"*, auto"</span>
    <span class="hljs-attr">RowSpacing</span>=<span class="hljs-string">"8"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">ListView</span>
      <span class="hljs-attr">Grid.Row</span>=<span class="hljs-string">"0"</span>
      <span class="hljs-attr">ItemsSource</span>=<span class="hljs-string">"{Binding Items}"</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- ... --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ListView</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">HorizontalStackLayout</span>
      <span class="hljs-attr">Grid.Row</span>=<span class="hljs-string">"1"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
        <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding AddItemCommand}"</span>
        <span class="hljs-attr">Text</span>=<span class="hljs-string">"Add Item"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
        <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding RemoveItemCommand}"</span>
        <span class="hljs-attr">Text</span>=<span class="hljs-string">"Remove Last"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">HorizontalStackLayout</span>&gt;</span>

  <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage</span>&gt;</span>
</code></pre>
<h2 id="heading-design-time-hints">Design-time Hints</h2>
<p>Apart from the massive boost in binding performance, one of the main differences between compiled bindings and classic bindings is that Visual Studio <em>(as well as plugins and extensions or any other modern IDE)</em> now recognizes the properties and commands and can provide suggestions and errors for the bindings to us during <em>design-time</em>, which means that we can already see in the text editor when something isn't set correctly even <em>before</em> compiling and running the app:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674637244753/a41364f1-5b2f-48b6-bd58-3f97983d53c1.png" alt class="image--center mx-auto" /></p>
<p>Here, we can see that <code>AddItem</code> is inaccessible, because it's a private method and doesn't exist as a command. The correct name of the command to bind to is <code>AddItemCommand</code> <em>(since we are using</em> <a target="_blank" href="https://ewerspej.hashnode.dev/introduction-to-mvvm-source-generators-for-dotnet"><em>MVVM Source Generators</em></a> <em>here)</em>. If we would try to run the app like this, we would receive compiler errors, which wouldn't happen with classic bindings.</p>
<blockquote>
<p><strong>Note:</strong> I am using the <a target="_blank" href="https://www.jetbrains.com/resharper/">JetBrains ReSharper</a> extension for Visual Studio (no affiliation), which provides the <strong>Inlay Hints</strong> for the data context in XAML (notice the <code>(BindingsViewModel).Path=</code>). This is additionally useful, because I can directly see what my current <code>BindingContext</code> is.</p>
</blockquote>
<h1 id="heading-nested-data-contexts">Nested Data Contexts</h1>
<p>There are various common scenarios, where the data context in the View hierarchy changes. On the root level of our View, we have a ViewModel set as the data context (<em>meaning it's set as the</em> <code>BindingContext</code>), but we may have other types that we are using in our ViewModel that are represented in our View hierarchy based on a <code>DataTemplate</code>.</p>
<p>The most common case is when we have a binding to a <em>List</em> or <em>Collection</em> of items of a specific type in our ViewModel, such as the <code>ObservableCollection&lt;BindingItem&gt;</code> in the <code>BindingsViewModel</code>. Using classic bindings, our View only knows at runtime, through reflection, what type the items in the <em>List</em> or <em>Collection</em> have.</p>
<p>With compiled bindings, we can already tell the View at <em>compile-time</em>, <em>before</em> the actual <code>BindingContext</code> is set (which only occurs during <em>runtime</em>), what type the <code>BindingContext</code> will have. Now, this <code>BindingContext</code> usually applies to the entire <code>ContentPage</code> or <code>ContentView</code> that we're dealing with.</p>
<p>This is problematic, because the data context of a <code>DataTemplate</code> usually is inferred from the type of the items that are contained inside of a <em>List</em> or <em>Collection</em>.</p>
<p>In the following situation, the <code>DataTemplate</code> will assume that the data context has not changed and will fail to bind to the <code>Name</code> property of the <code>BindingItem</code> element, because the <code>x:DataType</code> currently applies to the entire View:</p>
<pre><code class="lang-xml"> <span class="hljs-tag">&lt;<span class="hljs-name">ContentPage</span>
  <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiSamples.Views.BindingsPage"</span>
  <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
  <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
  <span class="hljs-attr">xmlns:models</span>=<span class="hljs-string">"clr-namespace:MauiSamples.Models"</span>
  <span class="hljs-attr">xmlns:viewModels</span>=<span class="hljs-string">"clr-namespace:MauiSamples.ViewModels"</span>
  <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"viewModels:BindingsViewModel"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
    <span class="hljs-attr">RowDefinitions</span>=<span class="hljs-string">"*, auto"</span>
    <span class="hljs-attr">RowSpacing</span>=<span class="hljs-string">"8"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">ListView</span>
      <span class="hljs-attr">Grid.Row</span>=<span class="hljs-string">"0"</span>
      <span class="hljs-attr">ItemsSource</span>=<span class="hljs-string">"{Binding Items}"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ListView.ItemTemplate</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">DataTemplate</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">ViewCell</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
              <span class="hljs-attr">Padding</span>=<span class="hljs-string">"8"</span>
              <span class="hljs-attr">ColumnDefinitions</span>=<span class="hljs-string">"*,*"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
                <span class="hljs-attr">Grid.Column</span>=<span class="hljs-string">"0"</span>
                <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Start"</span>
                <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Name}"</span>
                <span class="hljs-attr">VerticalTextAlignment</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">ViewCell</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">DataTemplate</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ListView.ItemTemplate</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ListView</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- ... --&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage</span>&gt;</span>
</code></pre>
<p>When we try to build and run this, we'll receive the following compiler error:</p>
<blockquote>
<p>Error XFC0045 Binding: Property "Name" not found on "MauiSamples.ViewModels.BindingsViewModel".</p>
</blockquote>
<p>This is because the <code>Name</code> property doesn't exist in the <code>BindingsViewModel</code>, but belongs to the <code>BindingItem</code> model class instead.</p>
<p>In order to resolve this, we can <em>redefine</em> the <code>x:DataType</code> anywhere in the View hierarchy. For this, all we need to do is specify the <code>BindingItem</code> class as the <code>x:DataType</code> on the <code>DataTemplate</code> and then the bindings will work as expected:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage</span>
  <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiSamples.Views.BindingsPage"</span>
  <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
  <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
  <span class="hljs-attr">xmlns:models</span>=<span class="hljs-string">"clr-namespace:MauiSamples.Models"</span>
  <span class="hljs-attr">xmlns:viewModels</span>=<span class="hljs-string">"clr-namespace:MauiSamples.ViewModels"</span>
  <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"viewModels:BindingsViewModel"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
    <span class="hljs-attr">RowDefinitions</span>=<span class="hljs-string">"*, auto"</span>
    <span class="hljs-attr">RowSpacing</span>=<span class="hljs-string">"8"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">ListView</span>
      <span class="hljs-attr">Grid.Row</span>=<span class="hljs-string">"0"</span>
      <span class="hljs-attr">ItemsSource</span>=<span class="hljs-string">"{Binding Items}"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ListView.ItemTemplate</span>&gt;</span>
        <span class="hljs-comment">&lt;!-- redefining the DataType here --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">DataTemplate</span> <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"models:BindingItem"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">ViewCell</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>
              <span class="hljs-attr">Padding</span>=<span class="hljs-string">"8"</span>
              <span class="hljs-attr">ColumnDefinitions</span>=<span class="hljs-string">"*,*"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
                <span class="hljs-attr">Grid.Column</span>=<span class="hljs-string">"0"</span>
                <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Start"</span>
                <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Name}"</span>
                <span class="hljs-attr">VerticalTextAlignment</span>=<span class="hljs-string">"Center"</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">ViewCell</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">DataTemplate</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ListView.ItemTemplate</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ListView</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- ... --&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage</span>&gt;</span>
</code></pre>
<p>When we run the app now, we'll see that our bindings work correctly and we can add and remove items using the provided buttons:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674639339901/a0a68da4-ad07-408d-94dd-d69ca1ff40ab.png" alt class="image--center mx-auto" /></p>
<p>Neat, we can now use compiled bindings and redefine the <code>x:DataType</code> on any level of the View hierarchy based on the structure of our ViewModel 😃. Which leads us to parent bindings, which are also affected by the <code>x:DataType</code>.</p>
<h1 id="heading-relative-bindings">Relative Bindings</h1>
<p>Instead of just removing the last item, it would be great if we could also remove any individual item in the <code>ObservableCollection</code>. For this, we need to add a button next to the label in the <code>DataTemplate</code>, bind to the <code>RemoveItemCommand</code> and pass the item itself as the <code>CommandParameter</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674639646686/8a3d6934-d77d-4562-9b91-a9033e13d9f2.png" alt class="image--center mx-auto" /></p>
<p><strong><em>But wait</em></strong>, the <code>RemoveItemCommand</code> cannot be found 😱 and the <em>design-time</em> inlay hint already shows us why: The data context is currently set to <code>BindingItem</code>, because we specified that as the data type for our <code>DataTemplate</code> earlier:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">DataTemplate</span> <span class="hljs-attr">x:DataType</span>=<span class="hljs-string">"models:BindingItem"</span>&gt;</span>
</code></pre>
<blockquote>
<p><strong>Note:</strong> This issue applies to classic bindings and compiled bindings alike. So does the solution for the issue. The difference is that we can now already identify the problem during design-time or compile-time instead of noticing the problem during runtime only.</p>
</blockquote>
<p>This is where <a target="_blank" href="https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/relative-bindings">relative bindings</a> come in. When trying to bind to a property or command of a parent data context, such as our <code>BindingsViewModel</code>, we need to ensure that the binding is set to the correct <em>Binding Source</em>, specified via the <code>Source</code> attribute of a binding expression. In our case, we need to specify a <code>RelativeSource</code> with an <code>AncestorType</code> which is set our <code>BindingsViewModel</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
  <span class="hljs-attr">Grid.Column</span>=<span class="hljs-string">"1"</span>
  <span class="hljs-attr">Text</span>=<span class="hljs-string">"Remove"</span>
  <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"Red"</span>
  <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding RemoveItemCommand, Source={RelativeSource AncestorType={x:Type viewModels:BindingsViewModel}}}"</span>
  <span class="hljs-attr">CommandParameter</span>=<span class="hljs-string">"{Binding .}"</span> /&gt;</span>
</code></pre>
<p>By doing this, we're telling the binding mechanism to look higher up in the View hierarchy for a data context that matches the type of the specified <code>RelativeSource</code>.</p>
<p>Now, the error is gone and we can build and run the app successfully and add and remove items as much as we like, even individual ones:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674640691212/64eab7f2-05eb-4b5d-b7c3-486e6662cb9e.png" alt class="image--center mx-auto" /></p>
<p>Awesome, our bindings work and we can now benefit from the performance boost as well as the <em>compile-time</em> (and also <em>design-time</em>) evaluation of binding expressions that compiled bindings provide 🏆.</p>
<h1 id="heading-conclusions-and-next-steps">Conclusions and next steps</h1>
<p>Compiled bindings are awesome and highly convenient. Especially the performance boost and the compile-time (as well as design-time) evaluation of binding expressions are gold.</p>
<p>You cannot only speed up your application using compiled bindings, but you can also reduce your debugging efforts and focus on developing quality features instead of hunting for bugs that turn out to be binding errors, which often are difficult to identify without IDE support.</p>
<p>Have you used compiled bindings before? Did you run into any issues with them? Let me know and I'll be happy to write another blog post to explore bindings even further.</p>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>GitHub</strong></a> repository for this post so you don't miss out on any future posts.</p>
]]></content:encoded></item><item><title><![CDATA[MVVM Source Generators: Advanced Scenarios]]></title><description><![CDATA[Introduction
Welcome to the second article of my mini-series about MVVM Source Generators for C# .NET. In the previous post, I discussed the basics and most important features that the MVVM Community Toolkit provides, specifically attributes that can...]]></description><link>https://blog.ewers-peters.de/mvvm-source-generators-advanced-scenarios</link><guid isPermaLink="true">https://blog.ewers-peters.de/mvvm-source-generators-advanced-scenarios</guid><category><![CDATA[MVVM]]></category><category><![CDATA[sourcegenerators]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[C#]]></category><category><![CDATA[ViewModel]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Mon, 09 Jan 2023 09:54:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672653064290/08fdd99b-6ab3-48ef-8219-170c354106fd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Welcome to the second article of my mini-series about <a target="_blank" href="https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/generators/overview">MVVM Source Generators</a> for C# .NET. In the <a target="_blank" href="https://ewerspej.hashnode.dev/introduction-to-mvvm-source-generators-for-dotnet">previous post</a>, I discussed the basics and most important features that the <a target="_blank" href="https://learn.microsoft.com/windows/communitytoolkit/mvvm/introduction">MVVM Community Toolkit</a> provides, specifically attributes that can be used to automatically generate properties and commands.</p>
<p>In this second part, I will show you how to intercept property setters in order to provide custom behavior, which you might be used to from developing <em>ViewModels</em> entirely by hand without using Source Generators. By intercepting the setters, we can define custom functionality that can be executed right before and right after changing the backing field of a property.</p>
<p>Last but not least, I will also demonstrate the beauty of auto-generated <em>RelayCommands</em>. In the past, developers added a lot of busy flags (properties) to their ViewModels in order to show some kind of busyness indicator (such as an <em>ActivityIndicator</em>, also known as a <em>spinner</em>) that informs the user that a longer operation is currently taking place.</p>
<p>Like in the previous post, I am using a <em>.NET MAUI</em> project (check out the <a target="_blank" href="https://github.com/ewerspej/maui-samples">sample repository</a>) to demonstrate the functionality, but since the MVVM Community Toolkit is independent from UI frameworks, the same things apply to technologies like <em>Windows Presentation Foundation</em> (WPF) and <em>Xamarin.Forms</em>.</p>
<h2 id="heading-updated-scenario">Updated scenario</h2>
<p>In order to demonstrate customized property setters as well as the awesome new way to indicate activity (or <em>busyness</em>) without flags, I have added a <em>Stepper</em> control to the UI from the <a target="_blank" href="https://ewerspej.hashnode.dev/introduction-to-mvvm-source-generators-for-dotnet">previous post</a> to simulate the selection of how many copies of the address label should be printed. When the number of copies is set to <code>0</code> the popup won't open. I have also added an <em>ActivityIndicator</em> to be displayed while the popup is being opened:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672908305292/f3468664-23b7-44db-9cc9-3c6b68d0182d.png" alt class="image--center mx-auto" /></p>
<p>The <code>AddressViewModel</code> receives two new properties which are called <code>Copies</code> and <code>IsBusy</code> and the <code>PrintAddress()</code> method will be changed to return an <code>async Task</code> in order to simulate a longer running operation:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _copies;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Copies
{
    <span class="hljs-keyword">get</span> =&gt; _copies;
    <span class="hljs-keyword">set</span> =&gt; SetField(<span class="hljs-keyword">ref</span> _copies, <span class="hljs-keyword">value</span>);
}

<span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> _isBusy;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> IsBusy
{
    <span class="hljs-keyword">get</span> =&gt; _isBusy;
    <span class="hljs-keyword">set</span> =&gt; SetField(<span class="hljs-keyword">ref</span> _isBusy, <span class="hljs-keyword">value</span>);
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"></span>)</span>
{
    IsBusy = <span class="hljs-literal">true</span>;

    <span class="hljs-keyword">await</span> Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>));

    OnPrintAddress?.Invoke(FullAddress);

    IsBusy = <span class="hljs-literal">false</span>;
}
</code></pre>
<p>The <em>ActivityIndicator</em> is bound to the <code>IsBusy</code> flag in the XAML:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ActivityIndicator</span>
  <span class="hljs-attr">IsVisible</span>=<span class="hljs-string">"{Binding IsBusy}"</span>
  <span class="hljs-attr">IsRunning</span>=<span class="hljs-string">"{Binding IsBusy}"</span>/&gt;</span>
</code></pre>
<p>In the next steps, we will add some custom functionality to the setter of the <code>Copies</code> property before addressing the busy flags. We'll first look at how this is usually done without Source Generators followed by how you can take advantage of the MVVM goodness that the Source Generators provide without having to live without customized property setters.</p>
<h2 id="heading-custom-property-setters">Custom Property Setters</h2>
<p>Using the previously introduced Source Generator attributes may have left some readers who have working MVVM experience with some open questions. For example, there are situations where you would not only add an <em>observable</em> property that raises the <code>PropertyChanging</code> or <code>PropertyChanged</code> events, but you might actually need to <em>customize</em> the property setter to execute additional functionality <em>if and when</em> the property is either <em>about</em> to change or has <em>just</em> changed.</p>
<blockquote>
<p><strong>Opinion:</strong> In my humble opinion, it's a bad practice to add a lot of functionality to a property setter. By adding a lot of complex statements or even loops to a setter, you're effectively mixing concerns, which makes them difficult to maintain. In the end, a setter is a like a method that updates a value and should have no unexpected side-effects. Adding a lot of extra functionality that belongs into a separate method can lead to the violation of the <a target="_blank" href="https://en.wikipedia.org/wiki/Single-responsibility_principle">Single Responsibility Principle</a> (SRP) and should be avoided.</p>
</blockquote>
<h3 id="heading-executing-logic-in-classic-setters">Executing logic in classic setters</h3>
<p>Let's say we want to execute some additional logic in the setter of our <code>Copies</code> property, e.g. to log the current and new values to the console, and also notify subscribers about the changing state. Usually, without Source Generators we would write something like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _copies;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Copies
{
    <span class="hljs-keyword">get</span> =&gt; _copies;
    <span class="hljs-keyword">set</span>
    {
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">value</span> == _copies)
        {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-comment">//do something before property is changing</span>
        Console.WriteLine(<span class="hljs-string">$"Property <span class="hljs-subst">{<span class="hljs-keyword">nameof</span>(Copies)}</span> is about to change. Current value: <span class="hljs-subst">{Copies}</span>, new value: <span class="hljs-subst">{<span class="hljs-keyword">value</span>}</span>"</span>);
        OnPropertyChanging();        

        _copies = <span class="hljs-keyword">value</span>;

        <span class="hljs-comment">//do something after property changed</span>
        Console.WriteLine(<span class="hljs-string">$"Property <span class="hljs-subst">{<span class="hljs-keyword">nameof</span>(Copies)}</span> is has changed. Current value: <span class="hljs-subst">{Copies}</span>, new value: <span class="hljs-subst">{<span class="hljs-keyword">value</span>}</span>"</span>);
        OnPropertyChanged();
    }
}
</code></pre>
<blockquote>
<p><strong>Note:</strong> For simplicity's sake I went with logging something to the console here instead of some advanced logic. Of course, it's also possible to execute some complex logic inside a property setter, although I recommend separating concerns whenever possible.</p>
</blockquote>
<p>Now, how can we achieve something similar using the Source Generators, though?</p>
<h3 id="heading-executing-logic-in-auto-generated-setters">Executing logic in auto-generated setters</h3>
<p>As it turns out, it's actually quite easy to add custom behavior to auto-generated property setters, because the MVVM Source Generators graciously provide partial methods <em>(signature only)</em> for us which we can "hook" into, meaning we can provide an implementation body for these methods.</p>
<p>Let's add the <code>Copies</code> property in the <em>MVVM Source Generator</em> way and then look at what is actually being generated for us. This is the property with the <code>[ObservableProperty]</code> attribute:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ObservableProperty</span>]
<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _copies;
</code></pre>
<p>If you remember from the previous post, in the <a target="_blank" href="https://ewerspej.hashnode.dev/introduction-to-mvvm-source-generators-for-dotnet#heading-under-the-hood">Under the hood</a> section, there are two methods, specifically, which are generated for us based on the property's name when using the <code>ObservableObject</code> base class together with the <code>[ObservableProperty]</code> attribute:</p>
<ul>
<li><p><code>partial void On[PropertyName]Changing(&lt;type&gt; value)</code></p>
</li>
<li><p><code>partial void On[PropertyName]Changed(&lt;type&gt; value)</code></p>
</li>
</ul>
<p>For the <code>Copies</code> property these auto-generated methods look like this:</p>
<pre><code class="lang-csharp"><span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>Executes the logic for when <span class="hljs-doctag">&lt;see cref="Copies"/&gt;</span> is changing.<span class="hljs-doctag">&lt;/summary&gt;</span></span>
[<span class="hljs-meta">global::System.CodeDom.Compiler.GeneratedCode(<span class="hljs-meta-string">"CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator"</span>, <span class="hljs-meta-string">"8.1.0.0"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">partial</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnCopiesChanging</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> <span class="hljs-keyword">value</span></span>)</span>;

<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>Executes the logic for when <span class="hljs-doctag">&lt;see cref="Copies"/&gt;</span> just changed.<span class="hljs-doctag">&lt;/summary&gt;</span></span>
[<span class="hljs-meta">global::System.CodeDom.Compiler.GeneratedCode(<span class="hljs-meta-string">"CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator"</span>, <span class="hljs-meta-string">"8.1.0.0"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">partial</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnCopiesChanged</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> <span class="hljs-keyword">value</span></span>)</span>;
</code></pre>
<p>As we can see, these methods are actually <code>partial</code> methods that don't define a body. We will take advantage of that a bit further down, but first let's have a look at <em>when and where</em> those methods are invoked. This is the auto-generated property:</p>
<pre><code class="lang-csharp"><span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;inheritdoc cref="_copies"/&gt;</span></span>
[<span class="hljs-meta">global::System.CodeDom.Compiler.GeneratedCode(<span class="hljs-meta-string">"CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator"</span>, <span class="hljs-meta-string">"8.1.0.0"</span>)</span>]
[<span class="hljs-meta">global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Copies
{
    <span class="hljs-keyword">get</span> =&gt; _copies;
    <span class="hljs-keyword">set</span>
    {
        <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">global</span>::System.Collections.Generic.EqualityComparer&lt;<span class="hljs-keyword">int</span>&gt;.Default.Equals(_copies, <span class="hljs-keyword">value</span>))
        {
            OnCopiesChanging(<span class="hljs-keyword">value</span>);
            OnPropertyChanging(<span class="hljs-keyword">global</span>::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Copies);
            _copies = <span class="hljs-keyword">value</span>;
            OnCopiesChanged(<span class="hljs-keyword">value</span>);
            OnPropertyChanged(<span class="hljs-keyword">global</span>::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Copies);
        }
    }
}
</code></pre>
<p>Right in beginning of the setter, the equality of the <code>_copies</code> backing field and the new <code>value</code> is checked. Only if this comparison evaluates to <code>false</code>, meaning that the values are different, the setter logic is executed.</p>
<p>First, the <code>OnCopiesChanging()</code> method is invoked with the new <code>value</code> as an argument. Since the method doesn't actually have a body, nothing happens <em>- yet</em>. That call is followed by raising the <code>PropertyChanging</code> event of the <code>INotifyPropertyChanging</code> interface.</p>
<p>Second, the value of the <code>_copies</code> backing field gets updated <em>before</em> <code>OnCopiesChanged()</code> is invoked using the new <code>value</code> as an argument. This is followed by raising the <code>PropertyChanged</code> event of the <code>INotifyPropertyChanged</code> interface.</p>
<p>This means that we can "hook" into the setter by providing implementation bodies for the <code>OnCopiesChanging()</code> and <code>OnCopiesChanged()</code> methods in order to achieve the same functionality as we did without the Source Generators:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ObservableProperty</span>]
<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _copies;

<span class="hljs-function"><span class="hljs-keyword">partial</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnCopiesChanging</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> <span class="hljs-keyword">value</span></span>)</span>
{
    Console.WriteLine(<span class="hljs-string">$"Property <span class="hljs-subst">{<span class="hljs-keyword">nameof</span>(Copies)}</span> is about to change. Current value: <span class="hljs-subst">{Copies}</span>, new value: <span class="hljs-subst">{<span class="hljs-keyword">value</span>}</span>"</span>);
}

<span class="hljs-function"><span class="hljs-keyword">partial</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnCopiesChanged</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> <span class="hljs-keyword">value</span></span>)</span>
{
    Console.WriteLine(<span class="hljs-string">$"Property <span class="hljs-subst">{<span class="hljs-keyword">nameof</span>(Copies)}</span> is has changed. Current value: <span class="hljs-subst">{Copies}</span>, new value: <span class="hljs-subst">{<span class="hljs-keyword">value</span>}</span>"</span>);
}
</code></pre>
<p><strong>That's it<em>.</em></strong> That's <em>all</em> we have to do in order to add custom functionality for our property setters. If we wanted to, we could even access other properties or run some fancy logic. Yeah! 🎉</p>
<h2 id="heading-mixing-approaches">Mixing approaches</h2>
<p>For common and simple scenarios, the MVVM Source Generators are the easiest way to minimize your coding efforts and increase productivity by reducing this repetitive task to a minimum, but what about non-standard properties that require some special logic which cannot be achieved using the MVVM Source Generators?</p>
<p>In the rare case that you really cannot achieve some highly specialized behavior using the auto-generated properties, such as executing logic inside of getters, or inside setters independent of the result of the equality comparison, you can still implement your properties like you would have without using Source Generators. It is completely valid to mix the two approaches and use the best of both worlds.</p>
<p>Nothing stops you from implementing one property using a Source Generator attribute and another one by implementing it completely manually:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ObservableProperty</span>]
[<span class="hljs-meta">NotifyPropertyChangedFor(nameof(IsOfLegalAge))</span>]
<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _age;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> IsOfLegalAge =&gt; Nationality == Nation.USA ? Age &gt;= <span class="hljs-number">21</span> : Age &gt;= <span class="hljs-number">18</span>;

<span class="hljs-keyword">private</span> Nation _nationality;
<span class="hljs-keyword">public</span> Nation Nationality
{
    <span class="hljs-keyword">get</span> =&gt; _nationality;
    <span class="hljs-keyword">set</span>
    {
        <span class="hljs-keyword">if</span>(!SetField(<span class="hljs-keyword">ref</span> _nationality)) <span class="hljs-keyword">return</span>;
        OnPropertyChanged(<span class="hljs-keyword">nameof</span>(IsOfLegalAge));
    }
}
</code></pre>
<h2 id="heading-goodbye-busy-flags">Goodbye, busy flags</h2>
<p>In the beginning of this article, I have introduced an <code>IsBusy</code> property that is used to show or hide an <em>ActivityIndicator</em> and it is set manually at the beginning and at the end of the <code>PrintAddressAsync()</code> method:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"></span>)</span>
{
    IsBusy = <span class="hljs-literal">true</span>;

    <span class="hljs-keyword">await</span> Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>));

    OnPrintAddress?.Invoke(FullAddress);

    IsBusy = <span class="hljs-literal">false</span>;
}
</code></pre>
<p>This is a common scenario in many ViewModels, but often you need to have various flags to indicate busyness for different operations and sometimes you just cannot reuse the same flag for that.</p>
<h3 id="heading-problems-of-busy-flags">Problems of busy flags</h3>
<p>Doing this is problematic, because it scatters the code with flags and you may even run into issues when you have some complex logic that is running in the method that your command invokes.</p>
<p>I have often encountered bugs where busy flags have not been reset correctly, because a developer decided to <em>(rightfully!)</em> jump out of a method early if a certain condition isn't met and the developer <em>(it might have been myself on occasion)</em> forgot to set the flag to <code>false</code> again when the method returns early from execution.</p>
<p>Take the following scenario as an example:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"></span>)</span>
{
    IsBusy = <span class="hljs-literal">true</span>;

    <span class="hljs-keyword">if</span> (Copies &lt; <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span>;

    <span class="hljs-keyword">await</span> Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>));

    OnPrintAddress?.Invoke(FullAddress);

    IsBusy = <span class="hljs-literal">false</span>;
}
</code></pre>
<p>Here, <code>IsBusy</code> would remain <code>true</code> although the method has already returned, if the value of <code>Copies</code> is smaller than <code>1</code>. The <em>ActivityIndicator</em> would keep spinning forever <em>(figuratively)</em>. This can be remedied in a couple of different ways, but none of them are pretty.</p>
<p>For example, we could set the <code>IsBusy</code> flag to <code>false</code> in multiple locations in our code, or we could decide to wrap that code, either by using <a target="_blank" href="https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/try-finally"><code>try-finally</code></a> blocks or with an extra method which is executed between the two statements that update the <code>IsBusy</code> property.</p>
<p><strong>Option 1: Set flag in multiple locations</strong></p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"></span>)</span>
{
    IsBusy = <span class="hljs-literal">true</span>;

    <span class="hljs-keyword">if</span> (Copies &lt; <span class="hljs-number">1</span>)
    {
        IsBusy = <span class="hljs-literal">false</span>;
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">await</span> Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>));

    OnPrintAddress?.Invoke(FullAddress);

    IsBusy = <span class="hljs-literal">false</span>;
}
</code></pre>
<p>This is ugly and error-prone, because we may easily forget to add the required statements to <em>all</em> possible code branches that return early from execution.</p>
<p><strong>Option 2: Use a</strong> <code>try-finally</code> <strong>block</strong></p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">try</span>
    {
        IsBusy = <span class="hljs-literal">true</span>;

        <span class="hljs-keyword">if</span> (Copies &lt; <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span>;

        <span class="hljs-keyword">await</span> Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>));

        OnPrintAddress?.Invoke(FullAddress);
    }
    <span class="hljs-keyword">finally</span>
    {
        IsBusy = <span class="hljs-literal">false</span>;
    }
}
</code></pre>
<p>This solution is abusing <code>try-finally</code> blocks for non-intended purposes, but it will work.</p>
<blockquote>
<p><strong>Note:</strong> Usually, you would use a <code>finally</code> block to clean up resources</p>
</blockquote>
<p><strong>Option 3: Introduce a separate method</strong></p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"></span>)</span>
{
    IsBusy = <span class="hljs-literal">true</span>;

    <span class="hljs-keyword">await</span> PrintAsync();

    IsBusy = <span class="hljs-literal">false</span>;
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (Copies &lt; <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span>;

    <span class="hljs-keyword">await</span> Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>));

    OnPrintAddress?.Invoke(FullAddress);
}
</code></pre>
<p>This is by far the best option when using flags, but it's still not pretty having to deal with several different busy flags in more elaborate ViewModels.</p>
<h3 id="heading-introducing-asyncrelaycommand">Introducing AsyncRelayCommand</h3>
<p>There is a solution to this and it's called <a target="_blank" href="https://learn.microsoft.com/dotnet/api/communitytoolkit.mvvm.input.asyncrelaycommand?view=win-comm-toolkit-dotnet-7.0"><code>AsyncRelayCommand</code></a>.</p>
<p>The <code>[RelayCommand]</code> attribute of the MVVM Source Generators will generate different types of commands for us depending on the method signature.</p>
<p>When using a <code>void</code> method it will create a regular <code>RelayCommand</code>, but when the return type is an <code>async Task</code>, for example, it will actually generate an <code>AsyncRelayCommand</code> for us:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">//this will create a RelayCommand called "PrintAddressCommand"</span>
[<span class="hljs-meta">RelayCommand</span>]
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PrintAddress</span>(<span class="hljs-params"></span>)</span> {} 

<span class="hljs-comment">//this will create an AsyncRelayCommand called "PrintAddressCommand"</span>
[<span class="hljs-meta">RelayCommand</span>]
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"></span>)</span> {}
</code></pre>
<blockquote>
<p><strong>Important:</strong> When using <code>async</code> methods, it's common practice to use the <strong>"Async"</strong> suffix for the method name, e.g. <code>PrintAddressAsync()</code>. However, the Source Generators will recognize the suffix and remove it from the command name, so the command will still be called <code>PrintAddressCommand</code> (instead of <code>PrintAddressAsyncCommand</code>).</p>
</blockquote>
<p>This is splendid, because the <code>AsyncRelayCommand</code> comes with its own type of busy flag in the form of a property called <code>IsRunning</code>. We can use this property instead of implementing our own <code>IsBusy</code> property. All we need to do is change the bindings of the <em>ActivityIndicator</em> from <code>IsBusy</code> to <code>PrintAddressCommand.IsRunning</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ActivityIndicator</span>
  <span class="hljs-attr">IsVisible</span>=<span class="hljs-string">"{Binding PrintAddressCommand.IsRunning}"</span>
  <span class="hljs-attr">IsRunning</span>=<span class="hljs-string">"{Binding PrintAddressCommand.IsRunning}"</span>/&gt;</span>
</code></pre>
<blockquote>
<p><strong>Note:</strong> Buttons in .NET MAUI automatically use the <code>IsRunning</code> flag when their <code>Command</code> property is bound to an asynchronous command. This is useful, because the button will be disabled until the command finishes execution.</p>
</blockquote>
<p>We don't need to set <em>any</em> busy flags in the ViewModel anymore:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">RelayCommand</span>]
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">PrintAddressAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (Copies &lt; <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span>;

    <span class="hljs-keyword">await</span> Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>));

    OnPrintAddress?.Invoke(FullAddress);
}
</code></pre>
<p>👋 Goodbye, busy flags - <code>AsyncRelayCommand</code> FTW! I love it! 🤩</p>
<h2 id="heading-conclusions-and-next-steps">Conclusions and next steps</h2>
<p>Writing clean and maintainable ViewModels has never been easier thanks to the Source Generators of the MVVM Community Toolkit. We can still write exactly the same kind of logic like we did before while saving loads of valuable time and tremendously reducing the risk of bugs.</p>
<p>Source Generators can help you with decreasing the amount of boilerplate code and make ViewModels much more legible and comprehensible, provided you have a basic understanding of the MVVM pattern.</p>
<p>It's easier than ever to quickly write up a ViewModel with many different properties and set up commands and bindings with busy indicators without sacrificing any of the flexibility of manually implementing the <code>INotifyPropertyChanging</code> and <code>INotifyPropertyChanged</code> interfaces.</p>
<p>I hope you're just as excited as I am about this major development in the .NET realm. James Montemagno has recently summarized these amazing features of the MVVM Community Toolkit in another great <a target="_blank" href="https://www.youtube.com/watch?v=9vvm_-YveTs">YouTube video</a> which is definitely worth checking out, as well.</p>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters"><strong>LinkedIn</strong></a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej"><strong>blog</strong></a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples"><strong>GitHub</strong></a> repository for this post so you don't miss out on any future posts.</p>
]]></content:encoded></item><item><title><![CDATA[Introduction to MVVM Source Generators for C# .NET]]></title><description><![CDATA[Introduction
I am a huge fan of the Model-View-ViewModel pattern (MVVM) and it's no surprise to me that it keeps gaining popularity (even outside of .NET development). When applied correctly, it helps with writing Clean Code so that the resulting cod...]]></description><link>https://blog.ewers-peters.de/introduction-to-mvvm-source-generators-for-dotnet</link><guid isPermaLink="true">https://blog.ewers-peters.de/introduction-to-mvvm-source-generators-for-dotnet</guid><category><![CDATA[sourcegenerators]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[C#]]></category><category><![CDATA[MVVM]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Mon, 19 Dec 2022 07:42:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671296610438/HO0JEX_KB.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>I am a huge fan of the <strong>Model-View-ViewModel</strong> pattern (MVVM) and it's no surprise to me that it keeps gaining popularity <em>(even outside of .NET development)</em>. When applied correctly, it helps with writing <strong><em>Clean Code</em></strong> so that the resulting code is easy to read, understand, test and maintain.</p>
<p>In this blog article, which is part of a mini series on <a target="_blank" href="https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/generators/overview">MVVM Source Generators</a>, I will show how you can take advantage of the <a target="_blank" href="https://learn.microsoft.com/windows/communitytoolkit/mvvm/introduction">MVVM Community Toolkit</a> and why you should use Source Generators in your development projects.</p>
<blockquote>
<p><strong>Important:</strong> This article assumes prior knowledge about the <a target="_blank" href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel">MVVM pattern</a> and how it is used in .NET applications.</p>
<p>If you are completely new to MVVM, please consider familiarizing yourself with it first and learn about the <code>INotifyPropertyChanged</code> interface as well as data bindings and commands. There is a popular <a target="_blank" href="https://www.youtube.com/watch?v=Pso1MeX_HvI&amp;ab_channel=JamesMontemagno">introductory video</a> for MVVM on YouTube by James Montemagno.</p>
<p>I strongly believe that newcomers should first learn how to implement MVVM in .NET in the conventional way before taking shortcuts using Source Generators in order to avoid frustration.</p>
</blockquote>
<p>For the reason of convenience <em>(and because I cannot get enough of it)</em>, I will use a <em>.NET MAUI</em> project <em>(check out the</em> <a target="_blank" href="https://github.com/ewerspej/maui-samples">sample repository</a><em>)</em>, but the ViewModel could be from any other .NET based application, since the MVVM Community Toolkit is agnostic to application and UI development frameworks such as <em>WPF</em>, <em>Xamarin.Forms</em> or <em>.NET MAUI</em>. <strong><em>Therefore, even if you're not familiar yet with .NET MAUI, you will still want to keep reading on.</em></strong></p>
<h2 id="heading-why-use-source-generators">Why use Source Generators?</h2>
<p>While the MVVM pattern is a fantastic way to write maintainable and unit-testable applications, it requires a lot of boilerplate code. For every property and every command, we need to implement a backing field, setters and getters and raise an event to notify the UI that something has changed, like so:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _firstName;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FirstName
{
    <span class="hljs-keyword">get</span> =&gt; _firstName;
    <span class="hljs-keyword">set</span>
    {
        <span class="hljs-keyword">if</span> (_firstName.Equals(<span class="hljs-keyword">value</span>))
        {
            <span class="hljs-keyword">return</span>;
        }

        _firstName = <span class="hljs-keyword">value</span>;
        OnPropertyChanged();
    }
}
</code></pre>
<p>This can quickly become expensive, because on top of the actual business logic there is a lot of additional code that needs to be maintained. This costs valuable development time and increases the risk of bugs, e.g. because developers like to use copy/paste a lot and may occasionally forget to update the backing fields.</p>
<blockquote>
<p><strong>Anecdote:</strong> A former colleague of mine used to call this behavior "copy pasta" as a loose reference to the messiness of spaghetti code</p>
</blockquote>
<p>In order to avoid those common mistakes and help with the <em>Don't Repeat Yourself</em> (DRY) principle, the MVVM Community Toolkit helps us to reduce the amount of this boilerplate code. It does so in several ways:</p>
<ul>
<li><p>It provides default implementations of the <code>INotifyPropertyChanged</code> and <code>INotifyPropertyChanging</code> interfaces (e.g. <code>ObservableObject</code>).</p>
</li>
<li><p>It comes with <code>SetProperty()</code> methods which set the backing field for a property and raise the <code>PropertyChanged</code> and <code>PropertyChanging</code> events for us, but only if the new <code>value</code> differs from the current value of the backing field.</p>
</li>
<li><p>Instead of fully implementing each property and each command with all their backing fields, setters and getters, we can just let the Source Generators do it for us.</p>
</li>
</ul>
<p>This drastically reduces development time and our code files look much cleaner. So, by using Source Generators, our code from above can actually be reduced to writing a one-liner like this one:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ObservableProperty</span>] <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _firstName;
</code></pre>
<p>Before getting deeper into this and learning about how all this works, let's have a look at a common ViewModel that implements the <code>INotifyPropertyChanged</code> interface and after that, we'll see how it changes when we use MVVM Source Generators.</p>
<h2 id="heading-setting">Setting</h2>
<p>Let's assume that we want to implement an application that let's us enter a name and address to generate an address label. The application should have a live preview for the label which updates automatically when any of the input data changes as well as a printing function <em>(we won't actually print anything; instead, for simplicity, I just open a popup displaying the entered data)</em>.</p>
<p>Our ViewModel therefore needs a couple of properties and a command:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FirstName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> LastName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> StreetAddress { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> PostCode { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> City { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FullAddress { <span class="hljs-keyword">get</span>; }
<span class="hljs-keyword">public</span> ICommand PrintAddressCommand { <span class="hljs-keyword">get</span>; }
</code></pre>
<p>We can bind to those properties and the command in our XAML and this will not change throughout this blog post, even with the Source Generators:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Label</span> <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding FullAddress}"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">Text</span>=<span class="hljs-string">"Print Address"</span> <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding PrintAddressCommand}"</span> /&gt;</span>
</code></pre>
<p>Whenever the <code>FirstName</code>, <code>LastName</code>, <code>StreetAddress</code>, <code>PostCode</code> or <code>City</code> property changes, the <code>FullAddress</code> property should be live-updated in our View, which looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671009507895/GW8KzafAY.PNG" alt class="image--center mx-auto" /></p>
<p>When the <strong>Print Address</strong> button is clicked, the <code>PrintAddressCommand</code> will open a popup that displays the address (which we will see further down).</p>
<blockquote>
<p><strong>Note:</strong> I am skipping the rest of the UI code and only focus on the ViewModel in this blog post. If you want to see the whole code in action including the actual Views, check out the <a target="_blank" href="https://github.com/ewerspej/maui-samples">sample repository</a>.</p>
</blockquote>
<p>Let's have a look at what the fully implemented ViewModel looks like.</p>
<h2 id="heading-full-viewmodel-without-source-generators">Full ViewModel without Source Generators</h2>
<p>I've called it <code>AddressViewModel</code> and implemented the <code>INotifyPropertyChanged</code> interface without using any of the existing helper classes, just to demonstrate what a manually implemented ViewModel commonly looks (or better <em>used to look</em>) like:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System.ComponentModel;
<span class="hljs-keyword">using</span> System.Runtime.CompilerServices;
<span class="hljs-keyword">using</span> System.Text;
<span class="hljs-keyword">using</span> System.Windows.Input;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.ViewModels</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AddressViewModel</span> : <span class="hljs-title">INotifyPropertyChanged</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">delegate</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PrintAddressDelegate</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> address</span>)</span>;
    <span class="hljs-keyword">public</span> PrintAddressDelegate OnPrintAddress = <span class="hljs-literal">null</span>;

    <span class="hljs-keyword">private</span> ICommand _showAddressCommand;
    <span class="hljs-keyword">public</span> ICommand ShowAddressCommand =&gt; _showAddressCommand ??= <span class="hljs-keyword">new</span> Command(PrintAddress);

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _firstName;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FirstName
    {
        <span class="hljs-keyword">get</span> =&gt; _firstName;
        <span class="hljs-keyword">set</span>
        {
            <span class="hljs-keyword">if</span> (SetField(<span class="hljs-keyword">ref</span> _firstName, <span class="hljs-keyword">value</span>))
            {
                OnPropertyChanged(<span class="hljs-keyword">nameof</span>(FullAddress));
            }
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _lastName;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> LastName
    {
        <span class="hljs-keyword">get</span> =&gt; _lastName;
        <span class="hljs-keyword">set</span>
        {
            <span class="hljs-keyword">if</span> (SetField(<span class="hljs-keyword">ref</span> _lastName, <span class="hljs-keyword">value</span>))
            {
                OnPropertyChanged(<span class="hljs-keyword">nameof</span>(FullAddress));
            }
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _streetAddress;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> StreetAddress
    {
        <span class="hljs-keyword">get</span> =&gt; _streetAddress;
        <span class="hljs-keyword">set</span>
        {
            <span class="hljs-keyword">if</span> (SetField(<span class="hljs-keyword">ref</span> _streetAddress, <span class="hljs-keyword">value</span>))
            {
                OnPropertyChanged(<span class="hljs-keyword">nameof</span>(FullAddress));
            }
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _postCode;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> PostCode
    {
        <span class="hljs-keyword">get</span> =&gt; _postCode;
        <span class="hljs-keyword">set</span>
        {
            <span class="hljs-keyword">if</span> (SetField(<span class="hljs-keyword">ref</span> _postCode, <span class="hljs-keyword">value</span>))
            {
                OnPropertyChanged(<span class="hljs-keyword">nameof</span>(FullAddress));
            }
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _city;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> City
    {
        <span class="hljs-keyword">get</span> =&gt; _city;
        <span class="hljs-keyword">set</span>
        {
            <span class="hljs-keyword">if</span> (SetField(<span class="hljs-keyword">ref</span> _city, <span class="hljs-keyword">value</span>))
            {
                OnPropertyChanged(<span class="hljs-keyword">nameof</span>(FullAddress));
            }
        }
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FullAddress
    {
        <span class="hljs-keyword">get</span>
        {
            <span class="hljs-keyword">var</span> stringBuilder = <span class="hljs-keyword">new</span> StringBuilder();

            stringBuilder
                .AppendLine(<span class="hljs-string">$"<span class="hljs-subst">{FirstName}</span> <span class="hljs-subst">{LastName}</span>"</span>)
                .AppendLine(StreetAddress)
                .AppendLine(<span class="hljs-string">$"<span class="hljs-subst">{PostCode}</span> <span class="hljs-subst">{City}</span>"</span>);

            <span class="hljs-keyword">return</span> stringBuilder.ToString();
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PrintAddress</span>(<span class="hljs-params"></span>)</span>
    {
        OnPrintAddress?.Invoke(FullAddress);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">event</span> PropertyChangedEventHandler PropertyChanged;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnPropertyChanged</span>(<span class="hljs-params">[CallerMemberName] <span class="hljs-keyword">string</span> propertyName = <span class="hljs-literal">null</span></span>)</span>
    {
        PropertyChanged?.Invoke(<span class="hljs-keyword">this</span>, <span class="hljs-keyword">new</span> PropertyChangedEventArgs(propertyName));
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">SetField</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">ref</span> T field, T <span class="hljs-keyword">value</span>, [CallerMemberName] <span class="hljs-keyword">string</span> propertyName = <span class="hljs-literal">null</span></span>)</span>
    {
        <span class="hljs-keyword">if</span> (EqualityComparer&lt;T&gt;.Default.Equals(field, <span class="hljs-keyword">value</span>))
        {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }

        field = <span class="hljs-keyword">value</span>;
        OnPropertyChanged(propertyName);
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }
}
</code></pre>
<p><strong>Uff</strong>, that's a lot of code for just a couple of properties and one measly command! So much repetition... It could be even worse, if the equality comparison had been implemented separately in each property, as well.</p>
<p>The code above technically is fine and sound, but doing the same thing over and over again for all the different ViewModels an application might have, eventually becomes quite tedious and is error-prone.</p>
<blockquote>
<p><strong>Note:</strong> For the example scenario, we don't need the <code>INotifyPropertyChanging</code> interface, so I didn't implement that here.</p>
</blockquote>
<p>Before we can reduce the amount of code and use the Source Generators, we have to set up a few things and understand how to use the MVVM Community Toolkit. Let's do that next.</p>
<h2 id="heading-how-to-use-the-mvvm-community-toolkit">How to use the MVVM Community Toolkit</h2>
<h3 id="heading-setup">Setup</h3>
<p>Before we can start, we need to download and install the <strong>CommunityToolkit.MVVM</strong> package from <a target="_blank" href="https://nuget.org">nuget.org</a> in our C# project. At the time of writing, the latest stable release is <code>8.0.0</code>, but we will already dig into the <code>8.1.0-preview2</code>, so let's install that one (make sure to tick the <em>Include prerelease</em> checkbox next to the search bar), because it comes with some additional features <em>(which I will explore in a separate blog post)</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671012008435/SZdK0P_Fa.PNG" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>Disclaimer:</strong> Preview packages may be unstable or come with breaking changes</p>
</blockquote>
<p>Once installed, we can pull in the required classes and attributes by adding the following <code>using</code> statement to a ViewModel:</p>
<p><code>using CommunityToolkit.Mvvm.ComponentModel;</code></p>
<blockquote>
<p><strong>Note:</strong> The MVVM Community Toolkit also provides regular MVVM functionality and base classes that can be used to simplify ViewModels and I highly recommend using it with or without the Source Generators.</p>
</blockquote>
<p>Afterwards, we can use the <code>ObservableObject</code> class as a base class <em>or</em> use the <code>[INotifyPropertyChanged]</code> attribute on a <a target="_blank" href="https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods">partial class</a> to automagically generate the <code>INotifyPropertyChanged</code> implementation for us.</p>
<p>Use the <code>ObservableObject</code> base class if your class does not need to inherit from another base class:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> CommunityToolkit.Mvvm.ComponentModel;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.ViewModels</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AddressViewModel</span> : <span class="hljs-title">ObservableObject</span> { <span class="hljs-comment">/*...*/</span> }
</code></pre>
<p>Otherwise, if your class already inherits from another base class (C# only supports single inheritance), then you can use the <code>[INotifyPropertyChanged]</code> attribute <em>(and optionally also the</em> <code>[INotifyPropertyChanging]</code> <em>attribute)</em> instead:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> CommunityToolkit.Mvvm.ComponentModel;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.ViewModels</span>;

[<span class="hljs-meta">INotifyPropertyChanged</span>]
[<span class="hljs-meta">INotifyPropertyChanging</span>] <span class="hljs-comment">//optional</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AddressViewModel</span> : <span class="hljs-title">SomeOtherBaseClass</span> { <span class="hljs-comment">/*...*/</span> }
</code></pre>
<blockquote>
<p><strong>Note:</strong> When using the <code>[INotifyPropertyChanged]</code> attribute or <code>ObservableObject</code> base class, your class <strong>must</strong> be declared as <code>partial</code>, because Source Generators create code that complements the class. The <code>[INotifyPropertyChanged]</code> attribute should only be used when your ViewModel already inherits from another base class.</p>
</blockquote>
<h3 id="heading-attributes">Attributes</h3>
<p>The MVVM Community Toolkit uses C# attributes to let the MVVM Source Generators know that something should be auto-generated. <a target="_blank" href="https://learn.microsoft.com/dotnet/csharp/programming-guide/concepts/attributes/">Attributes</a> can be placed above or in front of fields and method signatures.</p>
<p>When using the Source Generators, you'll probably be using the following attributes more frequently than any of the others:</p>
<h4 id="heading-observableproperty">[ObservableProperty]</h4>
<p>This attribute is used to auto-generate a property for a backing field. The resulting property, by convention, always begins with an uppercase letter. Backing fields must be declared private and start with a lowercase letter or with an underscore (_):</p>
<pre><code class="lang-csharp"><span class="hljs-comment">//generates a property called "Age"</span>
[<span class="hljs-meta">ObservableProperty</span>]
<span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> age; 

<span class="hljs-comment">//generates a property called "Year"</span>
[<span class="hljs-meta">ObservableProperty</span>] <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _year;
</code></pre>
<p>We only specify the backing fields for the properties, but to access them, we still need to use the appropriate property identifiers. They can be bound to via accessing the <code>Age</code> and <code>Year</code> from within the XAML code:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Label</span> <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Age}"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Label</span> <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Year}"</span> /&gt;</span>
</code></pre>
<h4 id="heading-relaycommand">[RelayCommand]</h4>
<p>In order to auto-generate a command, you can simply place this attribute above or in front of a method. The resulting command will have the same name as the method, but with the <em>Command</em> suffix:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">//generates a RelayCommand called "SayHelloCommand"</span>
[<span class="hljs-meta">RelayCommand</span>]
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SayHello</span>(<span class="hljs-params"></span>)</span>
{
    Console.WriteLine(<span class="hljs-string">"Hello);
}</span>
</code></pre>
<p>The <code>[RelayCommand]</code> attribute can also be used on asynchronous methods, which will actually create an instance of <code>AsyncRelayCommand</code> under the hood, which is very convenient when using MVVM with .NET MAUI:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">//generates an AsyncRelayCommand called "SayHelloAsyncCommand"</span>
[<span class="hljs-meta">RelayCommand</span>] <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SayHelloAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">await</span> MessageService.ShowMessageAsync(<span class="hljs-string">"Hello"</span>);
}
</code></pre>
<p>The resulting commands can be bound to via accessing <code>SayHelloCommand</code> and <code>SayHelloAsyncCommand</code> from the XAML code:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding SayHelloCommand}"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding SayHelloAsyncCommand}"</span> /&gt;</span>
</code></pre>
<h4 id="heading-notifypropertychangedfor">[NotifyPropertyChangedFor]</h4>
<p>Sometimes, you also want to inform the consumer of property that another property has changed as well and that any bindings should be updated. That's what <code>[NotifyPropertyChangedFor]</code> attribute is for and it only works in combination with the <code>[ObservableProperty]</code> attribute. While the other attributes do not require an argument, this one requires the name of the property for which a <code>PropertyChanged</code> event should be raised:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ObservableProperty</span>]
[<span class="hljs-meta">NotifyPropertyChangedFor(nameof(FullName))</span>]
<span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _firstName;

[<span class="hljs-meta">ObservableProperty</span>]
[<span class="hljs-meta">NotifyPropertyChangedFor(nameof(FullName))</span>]
<span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _lastName;

<span class="hljs-comment">//a PropertyChanged event will be raised for this property</span>
<span class="hljs-comment">//whenever either the FirstName or LastName property changes</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FullName =&gt; <span class="hljs-string">$"<span class="hljs-subst">{FirstName}</span> <span class="hljs-subst">{LastName}</span>"</span>;
</code></pre>
<h2 id="heading-updating-the-viewmodel-with-source-generators">Updating the ViewModel with Source Generators</h2>
<p>Now that we've learned about how the Source Generators can be used, let's apply them to reduce the size of our <code>AddressViewModel</code> step-by-step.</p>
<blockquote>
<p><strong>Hint:</strong> In the <a target="_blank" href="https://github.com/ewerspej/maui-samples">sample repository</a>, I've actually added both versions of the ViewModel, so that you can compare them.</p>
</blockquote>
<h3 id="heading-properties">Properties</h3>
<p>For each of the properties, we will change the following:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _firstName;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FirstName
{
    <span class="hljs-keyword">get</span> =&gt; _firstName;
    <span class="hljs-keyword">set</span>
    {
        <span class="hljs-keyword">if</span> (SetField(<span class="hljs-keyword">ref</span> _firstName, <span class="hljs-keyword">value</span>))
        {
            OnPropertyChanged(<span class="hljs-keyword">nameof</span>(FullAddress));
        }
    }
}
</code></pre>
<p>into:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ObservableProperty</span>]
[<span class="hljs-meta">NotifyPropertyChangedFor(nameof(FullAddress))</span>]
<span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _firstName;
</code></pre>
<p>and so forth.</p>
<h3 id="heading-command">Command</h3>
<p>We can safely remove the following:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> ICommand _showAddressCommand;
<span class="hljs-keyword">public</span> ICommand ShowAddressCommand =&gt; _showAddressCommand ??= <span class="hljs-keyword">new</span> Command(PrintAddress);
</code></pre>
<p>and instead simply add the <code>[RelayCommand]</code> attribute above the <code>PrintAddress()</code> method:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">RelayCommand</span>]
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PrintAddress</span>(<span class="hljs-params"></span>)</span>
{
    OnPrintAddress?.Invoke(FullAddress);
}
</code></pre>
<h2 id="heading-resulting-viewmodel-with-source-generators">Resulting ViewModel with Source Generators</h2>
<p>The final result for the <code>AddressViewModel</code> with all the changes looks like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> CommunityToolkit.Mvvm.ComponentModel;
<span class="hljs-keyword">using</span> CommunityToolkit.Mvvm.Input;
<span class="hljs-keyword">using</span> System.Text;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.ViewModels</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AddressViewModel</span> : <span class="hljs-title">ObservableObject</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">delegate</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PrintAddressDelegate</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> address</span>)</span>;
    <span class="hljs-keyword">public</span> PrintAddressDelegate OnPrintAddress = <span class="hljs-literal">null</span>;

    [<span class="hljs-meta">ObservableProperty</span>]
    [<span class="hljs-meta">NotifyPropertyChangedFor(nameof(FullAddress))</span>]
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _firstName;

    [<span class="hljs-meta">ObservableProperty</span>]
    [<span class="hljs-meta">NotifyPropertyChangedFor(nameof(FullAddress))</span>]
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _lastName;

    [<span class="hljs-meta">ObservableProperty</span>]
    [<span class="hljs-meta">NotifyPropertyChangedFor(nameof(FullAddress))</span>]
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _streetAddress;

    [<span class="hljs-meta">ObservableProperty</span>]
    [<span class="hljs-meta">NotifyPropertyChangedFor(nameof(FullAddress))</span>]
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _postCode;

    [<span class="hljs-meta">ObservableProperty</span>]
    [<span class="hljs-meta">NotifyPropertyChangedFor(nameof(FullAddress))</span>]
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _city;

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FullAddress
    {
        <span class="hljs-keyword">get</span>
        {
            <span class="hljs-keyword">var</span> stringBuilder = <span class="hljs-keyword">new</span> StringBuilder();

            stringBuilder
                .AppendLine(<span class="hljs-string">$"<span class="hljs-subst">{FirstName}</span> <span class="hljs-subst">{LastName}</span>"</span>)
                .AppendLine(StreetAddress)
                .AppendLine(<span class="hljs-string">$"<span class="hljs-subst">{PostCode}</span> <span class="hljs-subst">{City}</span>"</span>);

            <span class="hljs-keyword">return</span> stringBuilder.ToString();
        }
    }

    [<span class="hljs-meta">RelayCommand</span>]
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PrintAddress</span>(<span class="hljs-params"></span>)</span>
    {
        OnPrintAddress?.Invoke(FullAddress);
    }
}
</code></pre>
<p>🎉 <strong>WOW</strong>, we've just saved about <strong>50%</strong> of the lines of code compared to the initial version of the ViewModel. Pretty impressive, isn't it?</p>
<p>When we run the code again, our application still behaves exactly like before. Pressing the button will open the popup with the address:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671013370353/4hbQFteVu.PNG" alt class="image--center mx-auto" /></p>
<p><strong>Awesome, MVVM Source Generators FTW!</strong> 🏆</p>
<h2 id="heading-under-the-hood">Under the hood</h2>
<p>Now, what actually happens when the properties and commands are generated? Since we've marked our ViewModel as being a <code>partial class</code>, the Source Generators can create more parts for it in separate files, like the properties and commands based on the attributes we've provided.</p>
<p>Since Source Generators in C# are built on top of the <a target="_blank" href="https://learn.microsoft.com/visualstudio/code-quality/roslyn-analyzers-overview?view=vs-2022">Code Analyzers</a> of the .NET Compiler Platform (Roslyn), they run after making an edit to an open C# file. That way, the generated sources are always available to the C# compiler. We can find the auto-generated files (which always end in <em>.g.cs</em>) for our project in the Solution Explorer under <em>Dependencies -&gt; net7.0 -&gt; Analyzers -&gt; CommunityToolkit.Mvvm.SourceGenerators</em>.</p>
<p>If we select the <strong><em>ObservablePropertyGenerator</em></strong> we can find a file ending in <strong>AddressViewModel.g.cs</strong>, which contains our auto-generated properties with their setters and getters, e.g. for our <code>FirstName</code> property:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AddressViewModel</span>
{
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;inheritdoc cref="_firstName"/&gt;</span></span>
    [<span class="hljs-meta">global::System.CodeDom.Compiler.GeneratedCode(<span class="hljs-meta-string">"CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator"</span>, <span class="hljs-meta-string">"8.1.0.0"</span>)</span>]
    [<span class="hljs-meta">global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FirstName
    {
        <span class="hljs-keyword">get</span> =&gt; _firstName;
        <span class="hljs-keyword">set</span>
        {
            <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">global</span>::System.Collections.Generic.EqualityComparer&lt;<span class="hljs-keyword">string</span>&gt;.Default.Equals(_firstName, <span class="hljs-keyword">value</span>))
            {
                OnFirstNameChanging(<span class="hljs-keyword">value</span>);
                OnPropertyChanging(<span class="hljs-keyword">global</span>::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.FirstName);
                _firstName = <span class="hljs-keyword">value</span>;
                OnFirstNameChanged(<span class="hljs-keyword">value</span>);
                OnPropertyChanged(<span class="hljs-keyword">global</span>::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FirstName);
                OnPropertyChanged(<span class="hljs-keyword">global</span>::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullAddress);
            }
        }
    }

    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>As we can see, a default <code>EqualityComparer</code> is used for the <code>_firstName</code> backing field which is of type <code>string</code>. Only if the strings of the backing field and the <code>value</code> object differ, the <code>PropertyChanging</code> event is raised before updating the backing field to the provided <code>value</code>. Once the backing field was updated, the <code>PropertyChanged</code> event is raised, but not just for our <code>FirstName</code> property, it is also raised for the <code>FullAddress</code> property, because we added the <code>[NotifyPropertyChangedFor(nameof(FullAddress))]</code> attribute above our <code>_firstName</code> backing field.</p>
<p>If you look closely, you'll notice that there are not just <code>OnPropertyChanging()</code> and <code>OnPropertyChanged()</code> method calls, but also calls to an <code>OnFirstNameChanging()</code> and an <code>OnFirstNameChanged()</code> method. The first two methods raise the <code>PropertyChanging</code> and <code>PropertyChanged</code> events respectively, while the other two are actually <code>partial</code> methods without a body, which are declared further down in the auto-generated source file:</p>
<pre><code class="lang-csharp"><span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>Executes the logic for when <span class="hljs-doctag">&lt;see cref="FirstName"/&gt;</span> is changing.<span class="hljs-doctag">&lt;/summary&gt;</span></span>
[<span class="hljs-meta">global::System.CodeDom.Compiler.GeneratedCode(<span class="hljs-meta-string">"CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator"</span>, <span class="hljs-meta-string">"8.1.0.0"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">partial</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnFirstNameChanging</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">value</span></span>)</span>;
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>Executes the logic for when <span class="hljs-doctag">&lt;see cref="FirstName"/&gt;</span> just changed.<span class="hljs-doctag">&lt;/summary&gt;</span></span>
[<span class="hljs-meta">global::System.CodeDom.Compiler.GeneratedCode(<span class="hljs-meta-string">"CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator"</span>, <span class="hljs-meta-string">"8.1.0.0"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">partial</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnFirstNameChanged</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">value</span></span>)</span>;
</code></pre>
<p>💡 These can be used to add custom functionality to our auto-generated property setters. How this can be done will be part of an upcoming blog post, so stay tuned!</p>
<blockquote>
<p><strong>Note:</strong> The <code>OnPropertyChanging()</code> and <code>OnFirstNameChanging()</code> methods were created in this example, because the ViewModel inherits from <code>ObservableObject</code>, which implements both the <code>INotifyPropertyChanging</code> and the <code>INotifyPropertyChanged</code> interfaces.</p>
</blockquote>
<h2 id="heading-conclusion-and-next-steps">Conclusion and next steps</h2>
<p>Reducing boilerplate code in your MVVM application has become even simpler now with the MVVM Source Generators of the MVVM Community Toolkit. As I have demonstrated, they are very convenient and super easy to use - provided that you are already somewhat familiar with the MVVM pattern in .NET applications and know what you are doing.</p>
<p>So far, I have shown the most straightforward and simple use cases with the common <code>[ObservableProperty]</code>, <code>[RelayCommand]</code> and <code>[NotifyPropertyChangedFor]</code> attributes. If you would like to learn more about this topic, then stay tuned, because I will soon write another blog post about advanced scenarios, such as adding custom functionality to auto-generated property setters and how to get rid of annoying busy flags that technically don't belong into the business logic 🤯.</p>
<p>Microsoft's brilliant James Montemagno also made a <a target="_blank" href="https://www.youtube.com/watch?v=aCxl0z04BN8&amp;list=PLZs676Qtj_TE6rM75X6OxOVKDK7lKY1gu&amp;ab_channel=JamesMontemagno">cool YouTube video</a> about the MVVM Source Generators of the MVVM Community Toolkit. Check it out, if you like. It helped me get started, as well.</p>
<p>A big <strong>thank you</strong> to <a target="_blank" href="https://jfversluis.dev/">Gerald Versluis</a> for reviewing this blog post for me. Your input was truly valuable. 💝</p>
<blockquote>
<p>This blog post was written and published as part of the <a target="_blank" href="https://dotnet.christmas/2022">2022 .NET Advent Calendar</a> by <a target="_blank" href="https://dusted.codes">Dustin Moris</a>.</p>
</blockquote>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters">LinkedIn</a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej">blog</a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples">GitHub</a> repository for this post so you don't miss out on any future posts.</p>
]]></content:encoded></item><item><title><![CDATA[How to write a Custom Control for .NET MAUI using SkiaSharp]]></title><description><![CDATA[Intro
Sometimes, when you're developing an app, you find yourself in need of a specific control but you just can't manage to find the right one, because the available ones do not offer some of the features that you're looking for. This happens to me ...]]></description><link>https://blog.ewers-peters.de/how-to-write-a-custom-control-for-net-maui-using-skiasharp</link><guid isPermaLink="true">https://blog.ewers-peters.de/how-to-write-a-custom-control-for-net-maui-using-skiasharp</guid><category><![CDATA[skiasharp]]></category><category><![CDATA[custom-controls]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Cross Platform App Development. ]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Tue, 29 Nov 2022 12:59:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1669725616570/kU012dTR-.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-intro">Intro</h3>
<p>Sometimes, when you're developing an app, you find yourself in need of a specific control but you just can't manage to find the right one, because the available ones do not offer some of the features that you're looking for. This happens to me quite often and in that case, I tend to just write up my own little custom control.</p>
<p>Building controls using just XAML in <strong>.NET MAUI</strong> <em>(or Xamarin.Forms)</em> is a common thing that most mobile developers frequently do. However, sometimes you need more control over the look and feel and for that you can use <em>drawn</em> controls instead of reusing existing XAML elements or platform-specific native controls.</p>
<p>In this blog post, I will demonstrate how you can easily draw up your own custom controls for .NET MAUI using <strong>SkiaSharp</strong> and what you need to do in order to make it reusable - all without needing to write custom handlers, mappers or renderers. Last but not least, I will also show you how Visual Studio 2022 17.4 helps you with creating a nuget package (.nupgk) that you can upload to nuget.org to share it with the world.</p>
<blockquote>
<p>While you can use .NET MAUI's <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/graphics/draw?view=net-maui-7.0"><em>GraphicsView</em></a> which provides a drawing canvas, I personally prefer drawing controls with SkiaSharp.</p>
</blockquote>
<p>In this blog post, I am going to refer to a different GitHub repository than the usual one. Step-by-step, we will be building this Progress Bar control which I have already developed and published: https://github.com/ewerspej/epj.ProgressBar.Maui.</p>
<h3 id="heading-goal">Goal</h3>
<p>Let's develop a simple but customizable Progress Bar control that will look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669629868011/hpLO1n3El.png" alt="bar0.png" /></p>
<p>The colors of the base and the progress should be customizable. It will also be possible to set the <em>width</em> and <em>height</em> for the Progress Bar.</p>
<h3 id="heading-setup">Setup</h3>
<p>In order to begin, we need a new solution with two projects inside. The first one will be a new <em>.NET MAUI App</em> project which will serve as our test bed to see our custom control in action. The second one will be a <em>.NET MAUI Class Library</em> project. This is where we will actually implement our custom control.</p>
<blockquote>
<p><strong>Note:</strong> A common approach is to place the solution and the sample project in a separate folder, e.g. <code>sample</code>, while the library project resides in its own <code>src</code> <em>(or <code>source</code> or whatever you prefer to call it)</em> folder. The reason to do this is simple: We want to separate our sample code from our library, which makes it easier to navigate and maintain, but that's not a must, it's merely a matter of choice and convenience.</p>
</blockquote>
<h4 id="heading-sample-solution">Sample Solution</h4>
<p>First, we create a new folder called <code>ProgressBar</code> and inside that, we create another folder called <code>sample</code> before we start up Visual Studio 2022 17.4 and create a new project by selecting <strong>.NET MAUI App</strong> from the templates:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669630991374/cEy0S9lGH.PNG" alt="MAUI_App.PNG" /></p>
<p>In the next dialog, we choose the <code>sample</code> folder from before as the directory for our new project. We can place the solution and the project in the same folder here to keep the folder structure simple. We click our way through the project creation wizard and should end up with a folder structure similar to this:</p>
<ul>
<li>\ProgressBar\sample\ProgressBarSample</li>
</ul>
<blockquote>
<p><strong>Note:</strong> As mentioned above, this is not a must, you can also leave out the <code>sample</code> directory to keep the path short.</p>
</blockquote>
<h4 id="heading-progressbar-project">ProgressBar Project</h4>
<p>Now, we can add the project for the Progress Bar control in the separate <code>src</code> folder. With the solution still open in Visual Studio, we right click on it and select <em>Add -&gt; New Project</em>. From the templates, we select <strong>.NET MAUI Class Library</strong> this time:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669631624975/5tIQjCoxP.PNG" alt="MAUI_Class_Library.PNG" /></p>
<p>On the following page of the wizard we need to provide a name for the class library as well as a location. For the name, I will use <strong>ProgressBar.Maui</strong>, but this is just an example. Normally, you would choose some kind of appropriate package name. For the location, I will choose the <code>src</code> folder and select <em>Create</em> to finish the setup:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669631991811/73g0rpGrz.PNG" alt="ProgressBar.Maui.PNG" /></p>
<p>Once the class library project is created, we end up with a folder structure similar to this:</p>
<ul>
<li>\ProgressBar\sample\ProgressBarSample</li>
<li>\ProgressBar\src\ProgressBar.Maui</li>
</ul>
<blockquote>
<p><strong>Note:</strong> In our class library project, we will find a folder structure similar to that of a .NET MAUI App, with a Platform folder for platform-specific code, which can be used to call APIs on Android, iOS, etc. and to create platform-specific handlers, if necessary. In our case, we won't need any of that. Therefore, it's safe to remove the Platform folder and its contents, if you wish to do so.</p>
</blockquote>
<p>In the <strong>ProgressBar.Maui</strong> project, let's rename the <code>Class1.cs</code> file to <code>ProgressBar.cs</code> and also change the class name to <code>ProgressBar</code>.</p>
<p>We also need to add a <em>Project Reference</em> to the <strong>ProgressBar.Maui</strong> project to our <strong>ProgressBarSample</strong> project so that we can use any classes from that in our App. To do this, right-click on <em>ProgressBarSample</em> and select <em>Add -&gt; Project Reference</em>, then select the class library project.</p>
<h3 id="heading-adding-skiasharp">Adding SkiaSharp</h3>
<p>Next, we need to add <strong>SkiaSharp</strong> to our class library project. For this, we add the following packages in the NuGet package manager (right-click on the <em>ProgressBar.Maui</em> project and select <em>Manage NuGet Packages</em>):</p>
<ul>
<li>SkiaSharp.Views.Maui.Controls <em>(version 2.88.3 at time of writing)</em></li>
<li>SkiaSharp.Views.Maui.Core <em>(version 2.88.3 at time of writing)</em></li>
</ul>
<p>Once installed, we can use the <strong>SKCanvasView</strong> as a base class for our control. After that, our class should look like this:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> SkiaSharp.Views.Maui.Controls;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">ProgressBar.Maui</span>;

<span class="hljs-comment">// All the code in this file is included in all platforms.</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProgressBar</span> : <span class="hljs-title">SKCanvasView</span>
{
}
</code></pre>
<h3 id="heading-handler-registration">Handler Registration</h3>
<p>Before we advance to the actual implementation, we need to <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/handlers/?view=net-maui-7.0">register a handler</a> for our control. This is required, because otherwise MAUI doesn't know how to render the control for each platform.</p>
<p>We don't actually need our own platform-specific handlers since we inherit directly from <code>SKCanvasView</code> without additional requirements. Therefore, we can conveniently use the existing <code>SKCanvasViewHandler</code> from <em>SkiaSharp</em>, because it takes care of everything for us already.</p>
<p>In order to be able register the handler for our control, we need to create a static class inside our <strong>ProgressBar.Maui</strong> project that I usually call <code>Registration</code>. Inside that class, we create an extension method called <code>UseProgressHandler()</code> where we add the handler to the <code>MauiAppBuilder</code>:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> SkiaSharp.Views.Maui.Handlers;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">ProgressBar.Maui</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Registration</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MauiAppBuilder <span class="hljs-title">UseProgressBar</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> MauiAppBuilder builder</span>)</span>
    {
        builder.ConfigureMauiHandlers(h =&gt;
        {
            h.AddHandler&lt;ProgressBar, SKCanvasViewHandler&gt;();
        });

        <span class="hljs-keyword">return</span> builder;
    }
}
</code></pre>
<p>This can now be used in our <strong>ProgressBarSample</strong> project's <code>MauiProgram</code> class as follows:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MauiProgram</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MauiApp <span class="hljs-title">CreateMauiApp</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp&lt;App&gt;()
            .UseProgressBar() <span class="hljs-comment">//add this line</span>
            .ConfigureFonts(fonts =&gt;
            {
                fonts.AddFont(<span class="hljs-string">"OpenSans-Regular.ttf"</span>, <span class="hljs-string">"OpenSansRegular"</span>);
                fonts.AddFont(<span class="hljs-string">"OpenSans-Semibold.ttf"</span>, <span class="hljs-string">"OpenSansSemibold"</span>);
            });

        <span class="hljs-keyword">return</span> builder.Build();
    }
}
</code></pre>
<p>That's it, we're all set up to actually implement our custom Progress Bar control.</p>
<h3 id="heading-adding-the-control-to-xaml">Adding the control to XAML</h3>
<p>Before implementing the details, let's already add our <code>ProgressBar</code> to a XAML <em>Page</em> or <em>View</em>, so that we can use that to see what we are actually developing. To do this, we simply import the namespace from our class library and add the control to the layout:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ContentPage</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
             <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
             <span class="hljs-attr">xmlns:maui</span>=<span class="hljs-string">"clr-namespace:ProgressBar.Maui;assembly=ProgressBar.Maui"</span>
             <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"ProgressBarSample.MainPage"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">VerticalStackLayout</span>
            <span class="hljs-attr">Spacing</span>=<span class="hljs-string">"25"</span>
            <span class="hljs-attr">Padding</span>=<span class="hljs-string">"30,0"</span>
            <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">maui:ProgressBar</span>
      <span class="hljs-attr">WidthRequest</span>=<span class="hljs-string">"250"</span>
      <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"5"</span> /&gt;</span>

  <span class="hljs-tag">&lt;/<span class="hljs-name">VerticalStackLayout</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage</span>&gt;</span>
</code></pre>
<p>We cannot set any custom properties, yet, because we haven't actually implemented anything so far, but we will already provide values for the <code>WidthRequest</code> and <code>HeightRequest</code> that each control inherits from <code>VisualElement</code> so that our Progress Bar has a defined size.</p>
<blockquote>
<p><strong>Note:</strong> We're not going to implement additional sizing properties, our Progress Bar will simply size itself automatically based on the allocated size of the control.</p>
</blockquote>
<h3 id="heading-implementing-the-progress-bar">Implementing the Progress Bar</h3>
<p>Now, we can use the empty class that we created earlier to implement the drawing logic for our Progress Bar control.</p>
<h4 id="heading-setting-up-the-canvas-for-drawing">Setting up the Canvas for drawing</h4>
<p>Before we can draw anything, we need to set up our canvas. First, we add a few private fields to store the canvas and some additional information about our drawing surface:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProgressBar</span> : <span class="hljs-title">SKCanvasView</span>
{
    <span class="hljs-comment">// actual canvas instance to draw on</span>
    <span class="hljs-keyword">private</span> SKCanvas _canvas; 

    <span class="hljs-comment">// rectangle which will be used to draw the Progress Bar</span>
    <span class="hljs-keyword">private</span> SKRect _drawRect; 

    <span class="hljs-comment">// holds information about the dimensions, etc.</span>
    <span class="hljs-keyword">private</span> SKImageInfo _info;
}
</code></pre>
<p>All the drawing will be done in the <code>OnPaintSurface()</code> method, which we need to override it from our base class. That's where we actually set up our canvas and the drawing rectangle that will contain the progress bar:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnPaintSurface</span>(<span class="hljs-params">SKPaintSurfaceEventArgs e</span>)</span>
{
    <span class="hljs-keyword">base</span>.OnPaintSurface(e);

    _canvas = e.Surface.Canvas;
    _canvas.Clear(); <span class="hljs-comment">// clears the canvas for every frame</span>
    _info = e.Info;
    _drawRect = <span class="hljs-keyword">new</span> SKRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, _info.Width, _info.Height);

    <span class="hljs-comment">//...</span>
}
</code></pre>
<p>Every time <code>OnPaintSurface()</code> gets called our control will be drawn. Therefore, we request the canvas to draw on and call <code>Clear()</code> on it. Otherwise, we would draw on top of what has been drawn already before.</p>
<h4 id="heading-adding-the-progress-and-color-properties">Adding the Progress and Color properties</h4>
<p>Next, let's add the properties for the progress value and the colors. Our <code>Progress</code> property will be of type <code>float</code>, because <em>SkiaSharp</em> uses <code>float</code> and thus we don't need to use any typecasts. We also add <code>BaseColor</code> and <code>ProgressColor</code> properties of type <code>Color</code> <em>(Microsoft.Maui.Graphics.Color)</em>.  Our properties will be bindable and should look like this:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">float</span> Progress
{
    <span class="hljs-keyword">get</span> =&gt; (<span class="hljs-keyword">float</span>)GetValue(ProgressProperty);
    <span class="hljs-keyword">set</span> =&gt; SetValue(ProgressProperty, <span class="hljs-keyword">value</span>);
}

<span class="hljs-keyword">public</span> Color ProgressColor
{
    <span class="hljs-keyword">get</span> =&gt; (Color)GetValue(ProgressColorProperty);
    <span class="hljs-keyword">set</span> =&gt; SetValue(ProgressColorProperty, <span class="hljs-keyword">value</span>);
}

<span class="hljs-keyword">public</span> Color BaseColor
{
    <span class="hljs-keyword">get</span> =&gt; (Color)GetValue(BaseColorProperty);
    <span class="hljs-keyword">set</span> =&gt; SetValue(BaseColorProperty, <span class="hljs-keyword">value</span>);
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> BindableProperty ProgressProperty = BindableProperty.Create(
    <span class="hljs-keyword">nameof</span>(Progress), <span class="hljs-keyword">typeof</span>(<span class="hljs-keyword">float</span>), <span class="hljs-keyword">typeof</span>(ProgressBar), <span class="hljs-number">0.0f</span>, propertyChanged: OnBindablePropertyChanged);

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> BindableProperty ProgressColorProperty = BindableProperty.Create(
    <span class="hljs-keyword">nameof</span>(ProgressColor), <span class="hljs-keyword">typeof</span>(Color), <span class="hljs-keyword">typeof</span>(ProgressBar), Colors.Orange, propertyChanged: OnBindablePropertyChanged);

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> BindableProperty BaseColorProperty = BindableProperty.Create(
    <span class="hljs-keyword">nameof</span>(BaseColor), <span class="hljs-keyword">typeof</span>(Color), <span class="hljs-keyword">typeof</span>(ProgressBar), Colors.LightGray, propertyChanged: OnBindablePropertyChanged);
</code></pre>
<p>In order to be able to update the drawn control whenever any of the properties change, we need to call <code>InvalidateSurface()</code> ourselves which we do in a <code>PropertyChanged</code> event handler:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnBindablePropertyChanged</span>(<span class="hljs-params">BindableObject bindable, <span class="hljs-keyword">object</span> oldValue, <span class="hljs-keyword">object</span> newValue</span>)</span>
{
    ((ProgressBar)bindable).InvalidateSurface();
}
</code></pre>
<blockquote>
<p><strong>Note:</strong> SkiaSharp renders the entire control each time <code>OnPaintSurface()</code> is called. This usually happens, when the surface gets invalidated. In order to avoid unnecessary (and expensive) rendering cycles, it's important to limit how often <code>InvalidateSurface()</code> gets called. </p>
</blockquote>
<p>Now, we can start drawing the actual control.</p>
<h4 id="heading-drawing-the-base">Drawing the base</h4>
<p>First, we draw the base. For this, we create a new method <code>DrawBase()</code> so that our <code>OnPaintSurface()</code> method doesn't get too crowded. In that new method, we create an instance of <code>SKPath</code> and we add our <code>_drawRect</code> to it, because that's the shape we want to draw as a path. Then, we draw the path by calling <code>DrawPath()</code> on the <code>_canvas</code> and pass the path as well as a <code>SKPaint</code> object which holds information about how to draw the base. We want to fill the entire rectangle with the <code>BaseColor</code>:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">DrawBase</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> basePath = <span class="hljs-keyword">new</span> SKPath();

    basePath.AddRect(_drawRect);

    _canvas.DrawPath(basePath, <span class="hljs-keyword">new</span> SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = BaseColor.ToSKColor(),
        IsAntialias = <span class="hljs-literal">true</span>
    });
}
</code></pre>
<p>Now, we can call <code>DrawBase()</code> at the end of our <code>OnPaintSurface()</code> override:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnPaintSurface</span>(<span class="hljs-params">SKPaintSurfaceEventArgs e</span>)</span>
{
    <span class="hljs-keyword">base</span>.OnPaintSurface(e);

    _canvas = e.Surface.Canvas; 
    _canvas.Clear(); <span class="hljs-comment">// clears the canvas for every frame</span>
    _info = e.Info; 
    _drawRect = <span class="hljs-keyword">new</span> SKRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, _info.Width, _info.Height);

    DrawBase();
}
</code></pre>
<p>When we run our app, we will already see a gray bar:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669715626384/j20GP9wUu.png" alt="base.png" /></p>
<p>Great. Let's add some progress!</p>
<h4 id="heading-drawing-the-progress">Drawing the progress</h4>
<p>Similar to drawing the base, we will create a new method called <code>DrawProgress()</code>. We will add a rectangle and a <code>SKPaint</code> again, but this time we use the <code>Progress</code> property to determine the width the of the bar which will indicate the actual progress:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">DrawProgress</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> progressPath = <span class="hljs-keyword">new</span> SKPath();

    <span class="hljs-keyword">var</span> progressRect = <span class="hljs-keyword">new</span> SKRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, _info.Width * Progress, _info.Height);

    progressPath.AddRect(progressRect);

    _canvas.DrawPath(progressPath, <span class="hljs-keyword">new</span> SKPaint
    {
        Style = SKPaintStyle.Fill,
        IsAntialias = <span class="hljs-literal">true</span>,
        Color = ProgressColor.ToSKColor()
    });
}
</code></pre>
<p>In order to draw the actual progress, we must not forget to call our <code>DrawProgress()</code> method in <code>OnPaintSurface()</code>:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnPaintSurface</span>(<span class="hljs-params">SKPaintSurfaceEventArgs e</span>)</span>
{
    <span class="hljs-keyword">base</span>.OnPaintSurface(e);

     _canvas = e.Surface.Canvas;
    _canvas.Clear(); <span class="hljs-comment">// clears the canvas for every frame</span>
    _info = e.Info;
    _drawRect = <span class="hljs-keyword">new</span> SKRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, _info.Width, _info.Height);

    DrawBase();
    DrawProgress();
}
</code></pre>
<blockquote>
<p><strong>Important:</strong> <code>DrawProgress()</code> must be called <strong>after</strong> <code>DrawBase()</code>, because on a canvas, everything is drawn on top of each other. If we would make the calls the other way around, we wouldn't be able to see the progress being drawn, because the base would cover it entirely.</p>
</blockquote>
<p>Before running the app again, let's update our XAML and set the <code>Progress</code> property to a value between 0.0 and 1.0, e.g. <code>0.4</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">maui:ProgressBar</span>
  <span class="hljs-attr">WidthRequest</span>=<span class="hljs-string">"250"</span>
  <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"5"</span>
  <span class="hljs-attr">Progress</span>=<span class="hljs-string">"0.4"</span>/&gt;</span>
</code></pre>
<p>Now, when we run the app again, we will see the <code>ProgressBar</code> filling 40% of its entire width:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669716837208/E-zUsGLQe.png" alt="progress0.png" /></p>
<p>The progress color is <em>Orange</em>, because that's the default value we provided to our <em>BindableProperty</em>.
We can change this by setting the <code>ProgressColor</code> property in our XAML to some other color:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">maui:ProgressBar</span>
  <span class="hljs-attr">WidthRequest</span>=<span class="hljs-string">"250"</span>
  <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"5"</span>
  <span class="hljs-attr">Progress</span>=<span class="hljs-string">"0.4"</span>
  <span class="hljs-attr">ProgressColor</span>=<span class="hljs-string">"DeepSkyBlue"</span>/&gt;</span>
</code></pre>
<p>Then it will look like this <em>(thanks to Hot Reload, we don't even need to restart the app to do this)</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669717052748/6vi9xqpcC.png" alt="progress1.png" /></p>
<p>🤩 Awesome, our control is ready for use. The <code>Progress</code> value and any of the other properties can be used to bind to a <em>ViewModel</em> or to be set to dynamic or static resources. </p>
<blockquote>
<p><strong>Note:</strong> You can find the full code including <strong>color gradients</strong> and <strong>animations</strong> on <a target="_blank" href="https://github.com/ewerspej/epj.ProgressBar.Maui">GitHub</a>. The complete control is also <a target="_blank" href="https://www.nuget.org/packages/epj.ProgressBar.Maui/">available on nuget.org</a>.</p>
</blockquote>
<h3 id="heading-nuget">NuGet</h3>
<p><strong>Sharing is caring!</strong>  In the Open Source realm it's great to sometimes give back to the Community. Personally, I find it rewarding to contribute something useful. It also helps with development projects and to show some of your skills.</p>
<p>If you happen to feel the urge to share your own custom control but struggle with setting up a <code>.nuspec</code> file and create a <code>.nupkg</code> file using the NuGet CLI, fear no more, Visual Studio 2022 17.4 comes to the rescue 🦸🏽‍♂️.</p>
<p>When you right-click on the MAUI Library project <em>(<code>ProgressBar.Maui</code> in our case)</em>, select <strong>Properties</strong> and navigate to the <strong>Package</strong> section. Turn on package creation by enabling the checkbox where it says <strong>Generate NuGet package on build</strong>. Set a couple of more properties like the <strong>version</strong>, <strong>title</strong>, <strong>description</strong> and <strong>authors</strong> and you're good to go:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669718832540/V2-K4AyvF.PNG" alt="nuget.PNG" /></p>
<p>These settings will actually just update the <em>.csproj</em> file and add the following properties to the main <code>PropertyGroup</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">GeneratePackageOnBuild</span>&gt;</span>True<span class="hljs-tag">&lt;/<span class="hljs-name">GeneratePackageOnBuild</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Title</span>&gt;</span>My Amazing Progress Bar<span class="hljs-tag">&lt;/<span class="hljs-name">Title</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Version</span>&gt;</span>1.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">Version</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Authors</span>&gt;</span>YourName<span class="hljs-tag">&lt;/<span class="hljs-name">Authors</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Description</span>&gt;</span>A really cool ProgressBar for .NET MAUI<span class="hljs-tag">&lt;/<span class="hljs-name">Description</span>&gt;</span>
</code></pre>
<p>When you create a build, Visual Studio will automatically generate a NuGet package for you in the <code>bin</code> folder, which can be used to upload and share your custom control on nuget.org.</p>
<blockquote>
<p><strong>Important: </strong> Always use <code>Release</code> mode for NuGet packages, <strong>never</strong> upload <code>Debug</code> versions to nuget.org!</p>
</blockquote>
<h3 id="heading-conclusions-and-next-steps">Conclusions and next steps</h3>
<p>As I have shown, it is very easy to quickly develop customizable controls by drawing on a canvas without the need of any platform-specific handlers or mappers for .NET MAUI (or renderers when you're familiar with Xamarin.Forms) using <em>SkiaSharp</em>. Visual Studio even makes it easy to share your custom controls with the world without a lot of hazzle.</p>
<p>In future blog posts, I will write about using <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/graphics/draw?view=net-maui-7.0">MAUI Graphics</a> and ways to customize and extend existing controls. In the meanwhile, if you would like to learn more about drawn controls in .NET MAUI, you can also check out <a target="_blank" href="https://learn.microsoft.com/en-us/events/dotnetconf-2021/drawn-controls-in-net-maui">Javier Suárez Ruiz's session</a> from .NET Conf 2021. </p>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters">LinkedIn</a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej">blog</a> and star the <a target="_blank" href="https://github.com/ewerspej/epj.ProgressBar.Maui">GitHub repository</a> for this post so you don't miss out on any future posts. Don't forget to share this with your friends and colleagues who are interested in learning about this topic.
Thank you for reading and sharing 💝</p>
]]></content:encoded></item><item><title><![CDATA[Multi-Targeting in .NET MAUI - Part 2]]></title><description><![CDATA[Intro
Welcome to Part 2 of my mini-series about Multi-Targeting in .NET MAUI!
In Part 1, I have explored and demonstrated the built-in functionality of the default .NET MAUI single-project approach providing platform-specific implementations inside t...]]></description><link>https://blog.ewers-peters.de/multi-targeting-in-net-maui-part-2</link><guid isPermaLink="true">https://blog.ewers-peters.de/multi-targeting-in-net-maui-part-2</guid><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[multitargeting]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><category><![CDATA[unit testing]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Wed, 16 Nov 2022 18:09:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/yh0UtueiZ-I/upload/v1668622084632/3Jgixs7od.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-intro">Intro</h3>
<p>Welcome to<strong> Part 2</strong> of my mini-series about <strong>Multi-Targeting in .NET MAUI</strong>!</p>
<p>In <strong><a target="_blank" href="https://ewerspej.hashnode.dev/multi-targeting-in-net-maui-part-1">Part 1</a></strong>, I have explored and demonstrated the built-in functionality of the default .NET MAUI single-project approach providing platform-specific implementations inside the different platform folders. For simple uses cases this approach is completely appropriate and for many scenarios it is absolutely sufficient. However, as soon as unit tests and more complex scenarios come into play, such as database access or user authentication, we might need to take a different approach.</p>
<blockquote>
<p><strong>Attention:</strong> I have upgraded the <a target="_blank" href="https://github.com/ewerspej/maui-samples">sample project</a> from .NET 6.0 to .NET 7.0 which has been released a few days ago. Gerald Versluis has a short <a target="_blank" href="https://blog.verslu.is/maui/upgrade-dotnet-maui-dotnet-7/">blog article</a> about how to do exactly that.</p>
</blockquote>
<p>In <strong>Part 2</strong>, I am now going to show what happens to our previous implementation when we add a Unit Test project to our solution and how to solve the arising issues. We will use <strong>filename-based Multi-Targeting</strong> for this. I am not going to take a deep dive into Unit Tests here, because that is an entire topic on its own.</p>
<blockquote>
<p><strong>Note:</strong> I will write a blog post about Unit Tests in the future. Until then, you may want to check out the <a target="_blank" href="https://www.youtube.com/watch?v=C9vIDLQwc7M&amp;ab_channel=GeraldVersluis">video</a> by Gerald about how to add a Unit Test project for your MAUI app.</p>
</blockquote>
<h3 id="heading-unit-tests-require-a-different-target-platform">Unit Tests require a different Target Platform</h3>
<p>Looking back at <strong><a target="_blank" href="https://ewerspej.hashnode.dev/multi-targeting-in-net-maui-part-1">Part 1</a></strong>, the following platforms were targeted by our single-project:</p>
<ul>
<li>Android</li>
<li>iOS</li>
<li>MacCatalyst</li>
<li>Windows</li>
</ul>
<p>However, as soon as a Unit Test project is added and a project reference is set to our single project, we will see the following issue <em>(I personally prefer NUnit, but the approach is identical for xUNit, I have tried this with both testing frameworks)</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668418800834/dTozoizqt.PNG" alt="00_Unit_Test_Project_Dependency_Fails.PNG" /></p>
<p>😱 The project reference is invalid!</p>
<p>We can also find the following error message in the Error List:</p>
<pre><code class="lang-txt">Project MauiSamples is not compatible with net7.0 (.NETCoreApp,Version=v7.0). Project MauiSamples supports:
  - net7.0-android33.0 (.NETCoreApp,Version=v7.0)
  - net7.0-ios16.0 (.NETCoreApp,Version=v7.0)
  - net7.0-maccatalyst15.4 (.NETCoreApp,Version=v7.0)
  - net7.0-windows10.0.19041 (.NETCoreApp,Version=v7.0)    MauiSamples.Tests
</code></pre>
<p>This message tells us the following: None of the targets is supported by the <code>MauiSamples.Tests</code> project that was added.</p>
<p>The reason for this is that Unit Test projects should be platform agnostic and <em>NUnit</em> (as well as <em>xUnit</em>) projects target plain .NET, but none of the platform-specific flavors:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- the test project can only reference plain .NET (net7.0 in this case) --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">TargetFramework</span>&gt;</span>net7.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFramework</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ImplicitUsings</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">ImplicitUsings</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Nullable</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">Nullable</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">IsPackable</span>&gt;</span>false<span class="hljs-tag">&lt;/<span class="hljs-name">IsPackable</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.Maui.Dependencies"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"6.0.547"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.NET.Test.Sdk"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"17.3.2"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Moq"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"4.18.2"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"NUnit"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"3.13.3"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"NUnit3TestAdapter"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"4.2.1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"NUnit.Analyzers"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"3.3.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"coverlet.collector"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"3.1.2"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ProjectReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"..\MauiSamples\MauiSamples.csproj"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>Essentially, we are missing the <code>net7.0</code> platform target in the configuration of our MAUI project. This can easily be resolved by adding <code>net7.0</code> as another target platform to our MAUI single-project (<em>MauiSamples.csproj</em> in the example). We also need to add an extra condition to the <code>OutputType</code>, because our Unit Test project expects a <em>Dynamic Link Library</em> (.dll) and not an <em>Executable</em> (.exe):</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- add net7.0 target framework here --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">TargetFrameworks</span>&gt;</span>net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFrameworks</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">TargetFrameworks</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$([MSBuild]::IsOSPlatform('windows'))"</span>&gt;</span>$(TargetFrameworks);net7.0-windows10.0.19041.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFrameworks</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- add this condition as well --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">OutputType</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"'$(TargetFramework)' != 'net7.0'"</span>&gt;</span>Exe<span class="hljs-tag">&lt;/<span class="hljs-name">OutputType</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- skipping other settings --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>After making these changes our project reference finally is valid 🥳:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668420051856/_v5IiEwcT.PNG" alt="01_Unit_Test_Project_Dependency_OK.PNG" /></p>
<h3 id="heading-limitations-of-the-built-in-approach">Limitations of the built-in approach</h3>
<p><strong>But wait,</strong> something else seems to be wrong: Our build still fails with a new error 😱:</p>
<pre><code class="lang-txt">Error    CS1061    'MainPage' does not contain a definition for 'SayHello' and no accessible extension method 'SayHello' accepting a first argument of type 'MainPage' could be found (are you missing a using directive or an assembly reference?)    MauiSamples (net7.0)
</code></pre>
<p>That's because we did not provide an implementation of the <code>SayHello()</code> extension method for our new <code>net7.0</code> target platform. We've hit a limitation of the single-project's built-in approach for Multi-Targeting, because there is no directory for non-platform-specific implementations. </p>
<p>In order to remedy this, we can go back to using a <strong>preprocessor directive</strong> after all:</p>
<pre><code class="lang-c#">    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Button_OnPressed</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> sender, EventArgs e</span>)</span>
    {
<span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> ANDROID || IOS || MACCATALYST || WINDOWS</span>
        <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>.SayHello();
<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span>
    }
</code></pre>
<p>😓 Bummer! That's what we tried to get rid of in the first place (despite it now being shorter than before). At least, we can write unit tests for our business logic now and still end up with a cleaner setup than what we initially started with in <strong><a target="_blank" href="https://ewerspej.hashnode.dev/multi-targeting-in-net-maui-part-1">Part 1</a></strong>. <strong>A trade-off? Not in the least. Just keep reading.</strong></p>
<h3 id="heading-solution">Solution</h3>
<p>Back to square one <em>(regarding the avoidance of preprocessor directives)</em>? Not at all. It's merely a limitation of the .NET MAUI single-project setup. The solution for this is <strong>filename</strong> <em>(and/or folder)</em> <strong>based Multi-Targeting</strong>.</p>
<p>The <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/configure-multi-targeting?view=net-maui-7.0#configure-filename-based-multi-targeting">official documentation</a> for this is very useful and I recommend studying it if you want to get a better understanding of what the conditions in the following paragraphs mean. However, I am going to make some alterations to the solution proposed in the official documentation, because we don't want to be limited to a specific major version of .NET.</p>
<p>Instead of continuing with the <code>SayHello()</code> example, I will get into actual platform-specific APIs now and how they can be called from within a <strong>ViewModel</strong>. <em>That way, we can compare the different approaches within the same solution in the <a target="_blank" href="https://github.com/ewerspej/maui-samples">sample repository</a>.</em></p>
<h3 id="heading-filename-based-multi-targeting">Filename-based Multi-Targeting</h3>
<p>When dealing with different platforms, especially when writing services or using platform-specific APIs that share a common interface, it makes sense to group the different implementations together. The default Multi-Targeting approach does not give us this option, while the filename-based approach does. Let's have a look at how this works.</p>
<p>First, we need to update our project file and add the following <code>&lt;ItemGroup&gt;</code> elements:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Android --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$(TargetFramework.StartsWith('net')) == true AND $(TargetFramework.Contains('-android')) != true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Compile</span> <span class="hljs-attr">Remove</span>=<span class="hljs-string">"**\**\*.Android.cs"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">None</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"**\**\*.Android.cs"</span> <span class="hljs-attr">Exclude</span>=<span class="hljs-string">"$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

<span class="hljs-comment">&lt;!-- iOS --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$(TargetFramework.StartsWith('net')) == true AND $(TargetFramework.Contains('-ios')) != true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Compile</span> <span class="hljs-attr">Remove</span>=<span class="hljs-string">"**\**\*.iOS.cs"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">None</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"**\**\*.iOS.cs"</span> <span class="hljs-attr">Exclude</span>=<span class="hljs-string">"$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Mac Catalyst --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$(TargetFramework.StartsWith('net')) == true AND $(TargetFramework.Contains('-maccatalyst')) != true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Compile</span> <span class="hljs-attr">Remove</span>=<span class="hljs-string">"**\**\*.MacCatalyst.cs"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">None</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"**\**\*.MacCatalyst.cs"</span> <span class="hljs-attr">Exclude</span>=<span class="hljs-string">"$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Windows --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$(TargetFramework.StartsWith('net')) == true AND $(TargetFramework.Contains('-windows')) != true"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Compile</span> <span class="hljs-attr">Remove</span>=<span class="hljs-string">"**\*.Windows.cs"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">None</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"**\*.Windows.cs"</span> <span class="hljs-attr">Exclude</span>=<span class="hljs-string">"$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

<span class="hljs-comment">&lt;!-- .NET --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"!($(TargetFramework.StartsWith('net')) == true AND $(TargetFramework.EndsWith('.0')) == true AND $(TargetFramework.Contains('-')) != true)"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- e.g net6.0 or net7.0 --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Compile</span> <span class="hljs-attr">Remove</span>=<span class="hljs-string">"**\*.net.cs"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">None</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"**\*.net.cs"</span> <span class="hljs-attr">Exclude</span>=<span class="hljs-string">"$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
</code></pre>
<p>With these <code>&lt;ItemGroup&gt;</code> elements we are telling the build system to only compile certain files when a specific platform is selected and otherwise simply ignore them based on their filename. For example, we only want the Android-specific C# files with the <code>Android.cs</code> suffix to be compiled when the target platform is <code>net6.0-android</code> or <code>net7.0-android</code> (or higher). In all other cases, the files are removed from compilation. </p>
<blockquote>
<p><strong>Note:</strong> Often, you will come across similar setups where the .NET version is hard-coded in the conditions of the <code>&lt;ItemGroup&gt;</code> elements, like in the official documentation. That's fine if you don't plan to upgrade to newer .NET versions, but I just switched from <code>net6.0</code> to <code>net7.0</code>and this way, I had less to adjust in my project file.</p>
</blockquote>
<p>Essentially, instead of using conditional compilation with preprocessor directives, we are now using <strong>filename-based conditional compilation</strong>.</p>
<h3 id="heading-a-common-interface">A common interface</h3>
<p>Having the filename-based conditional compilation in place, we can now create a new folder in our project and add platform-specific implementations to call some platform-specific APIs. Let's call the folder <code>Services\Device</code>. In this folder, we can then add an interface that I call <code>IDeviceService</code> and which declares a single method to set the screen brightness: </p>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.Services.Device</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IDeviceService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">SetScreenBrightness</span>(<span class="hljs-params"><span class="hljs-keyword">float</span> brightness</span>)</span>;
}
</code></pre>
<p>This can be used for <strong>Dependency Injection</strong> and <strong>Unit Tests</strong>, which we will have a look at a little further down, but let's first create the platform-specific implementations.</p>
<p>Since we are dealing with a <strong><em>platform-agnostic</em></strong> scenario, meaning that our business logic does not know anything about the platform it is being executed on, we need to provide implementations for all the possible platforms that are part of our single project, even the ones that are unused. We will do this in a sub-folder called <code>Platform</code> and with the following names:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668432126540/ZwC9XP13t.PNG" alt="02_Platforms.PNG" /></p>
<p>Note that we have C# files for each of our <strong>five</strong> target platforms (including plain .NET) and also a <code>DeviceService.shared.cs</code> file.</p>
<h3 id="heading-a-shared-implementation">A shared implementation</h3>
<p>First, we implement our shared class. We are going to use <code>partial</code> classes for this setup and <code>DeviceService.shared.cs</code> will serve as the shared part of our implementation, because we may have common methods and properties that may be shared among the different platform implementations. </p>
<blockquote>
<p><strong>Note:</strong> Technically, it would also be possible to do this without the shared part and without <code>partial</code> classes.</p>
</blockquote>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.Services.Device.Platform</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DeviceService</span> : <span class="hljs-title">IDeviceService</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> DeviceService _instance;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> DeviceService Instance =&gt; _instance ??= <span class="hljs-keyword">new</span> DeviceService();

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">DeviceService</span>(<span class="hljs-params"></span>)</span> {}

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetScreenBrightness</span>(<span class="hljs-params"><span class="hljs-keyword">float</span> brightness</span>)</span>;
}
</code></pre>
<p>Note how this shared part does not provide a body for the <code>SetScreenBrightness()</code> method. The reason for this is that each platform should provide its own implementation. Therefore, the body will be defined in the platform-specific code files.</p>
<p>For simplicity and ease of access to other APIs that are not covered in this article, I have also created a <em>singleton</em> for our <code>DeviceServices</code> class.</p>
<blockquote>
<p><strong>Note</strong>: Singletons are commonly considered to be an anti-pattern, because they may violate the Single Responsibility Principle and are often misused for so-called God-objects (global classes that contain a lot of shared logic and information). However, you will come across singletons in cross-platform and API development quite often. When used appropriately, they deserve some love, too.</p>
</blockquote>
<h3 id="heading-the-platform-specific-implementations">The platform-specific implementations</h3>
<p>Now that we have the interface and the shared code, we can finally create the platform-specific implementations. To keep things short, I will only focus on Android and iOS here, using the screen brightness example.</p>
<blockquote>
<p><strong>Important:</strong> Just like in the built-in Multi-Targeting approach from <strong><a target="_blank" href="https://ewerspej.hashnode.dev/multi-targeting-in-net-maui-part-1">Part 1</a></strong>, it is important that the namespace and class name are <strong><em>exactly the same</em></strong> for each implementation.</p>
</blockquote>
<h4 id="heading-net-windows-and-maccatalyst">.NET, Windows and MacCatalyst</h4>
<p>Our plain .NET, Windows and MacCatalyst implementations will only be empty stubs:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.Services.Device.Platform</span>;

<span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DeviceService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetScreenBrightness</span>(<span class="hljs-params"><span class="hljs-keyword">float</span> brightness</span>)</span>
    {
        <span class="hljs-comment">//ignore</span>
    }
}
</code></pre>
<p>Only mobile platforms like Android and iOS support setting the screen brightness directly (as far as I know).</p>
<h4 id="heading-android">Android</h4>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> AndroidPlatform = Microsoft.Maui.ApplicationModel.Platform;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.Services.Device.Platform</span>;

<span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DeviceService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetScreenBrightness</span>(<span class="hljs-params"><span class="hljs-keyword">float</span> brightness</span>)</span>
    {
        <span class="hljs-keyword">if</span> (AndroidPlatform.CurrentActivity?.Window?.Attributes == <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">var</span> attributes = AndroidPlatform.CurrentActivity.Window.Attributes;
        attributes.ScreenBrightness = brightness;
        AndroidPlatform.CurrentActivity.Window.Attributes = attributes;
    }
}
</code></pre>
<h4 id="heading-ios">iOS</h4>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> UIKit;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.Services.Device.Platform</span>;

<span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DeviceService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetScreenBrightness</span>(<span class="hljs-params"><span class="hljs-keyword">float</span> brightness</span>)</span>
    {
        UIScreen.MainScreen.Brightness = brightness;
    }
}
</code></pre>
<p>And just like that, we have our platform-specific API calls hidden behind a common interface, accessible either through our singleton or via the interface through Depedency Injection. We can now use the singleton instance of our <code>DeviceService</code> class and call the <code>SetScreenBrightness()</code> method from anywhere in our business logic without knowing the current target platform. We can even apply DIP and inject the <code>DeviceService</code> as a dependency using the <code>IDeviceService</code> interface.</p>
<p>The really cool thing is that we can use platform-specific namespaces inside these implementations without any build errors on the other target platforms, because we use filename-based conditional compilation under the hood.</p>
<h3 id="heading-putting-it-all-to-use">Putting it all to use</h3>
<p>To keep things short, I am using <em>MVVM Code Generation</em> for the ViewModel and I will only show a single Unit Test for demonstration purposes. </p>
<blockquote>
<p>James Montemagno has a great <a target="_blank" href="https://www.youtube.com/watch?v=aCxl0z04BN8&amp;ab_channel=JamesMontemagno">video</a> about the MVVM Source Generators. I will also dig deeper into Code Generation for MVVM in a separate blog post.</p>
</blockquote>
<h4 id="heading-the-view-and-its-viewmodel">The View and its ViewModel</h4>
<p>Let's start with the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/architecture/maui/mvvm">ViewModel</a>. I will call this class <code>MainViewModel</code>, because it will be the ViewModel for my <code>MainPage</code> and it only contains a constructor, two methods to set the screen brightness and also a private field for our <code>IDeviceService</code> interface from above. The dependency on the specific <code>DeviceService</code> implementation can be injected via the constructor that way.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> CommunityToolkit.Mvvm.ComponentModel;
<span class="hljs-keyword">using</span> CommunityToolkit.Mvvm.Input;
<span class="hljs-keyword">using</span> MauiSamples.Services.Device;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.ViewModels</span>;

[<span class="hljs-meta">ObservableObject</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MainViewModel</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IDeviceService _deviceService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainViewModel</span>(<span class="hljs-params">IDeviceService deviceService</span>)</span>
    {
        _deviceService = deviceService;
    }

    [<span class="hljs-meta">RelayCommand</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetHighBrightness</span>(<span class="hljs-params"></span>)</span>
    {
        _deviceService.SetScreenBrightness(<span class="hljs-number">1.0f</span>);
    }

    [<span class="hljs-meta">RelayCommand</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetLowBrightness</span>(<span class="hljs-params"></span>)</span>
    {
        _deviceService.SetScreenBrightness(<span class="hljs-number">0.1f</span>);
    }
}
</code></pre>
<p>That's it already for the ViewModel. It can now be used as the <code>BindingContext</code> for the <code>MainPage.xaml.cs</code>:</p>
<pre><code class="lang-c#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainPage</span>(<span class="hljs-params"></span>)</span>
{
    InitializeComponent();
    BindingContext = <span class="hljs-keyword">new</span> MainViewModel(DeviceService.Instance);
}
</code></pre>
<p>Note how the singleton is used to inject the dependency with the platform-specific implementation <em>(decided at compile-time, remember?)</em> into the ViewModel, which does not know <strong><em>anything</em></strong> at all about the different platforms.</p>
<p>In the <code>MainPage.xaml</code> we can now add two buttons and bind to the commands <em>(which are auto-generated for us)</em>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">HorizontalStackLayout</span>
  <span class="hljs-attr">Spacing</span>=<span class="hljs-string">"30"</span>
  <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
    <span class="hljs-attr">Text</span>=<span class="hljs-string">"Dim Display"</span>
    <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Start"</span>
    <span class="hljs-attr">WidthRequest</span>=<span class="hljs-string">"100"</span>
    <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding SetLowBrightnessCommand}"</span>/&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
    <span class="hljs-attr">Text</span>=<span class="hljs-string">"Undim Display"</span>
    <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"End"</span>
    <span class="hljs-attr">WidthRequest</span>=<span class="hljs-string">"100"</span>
    <span class="hljs-attr">Command</span>=<span class="hljs-string">"{Binding SetHighBrightnessCommand}"</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">HorizontalStackLayout</span>&gt;</span>
</code></pre>
<p><strong>Awesome</strong>, now we can dim and undim the display of our mobile device. 🍾</p>
<blockquote>
<p><strong>Note:</strong> Like with many other platform-specific APIs, this only works on real devices, emulators/simulators do not support dimming. If you would like to see the code in action, check out the <a target="_blank" href="https://github.com/ewerspej/maui-samples">GitHub repository</a> for this post and run the app on a real Android or iOS device.</p>
</blockquote>
<h4 id="heading-writing-unit-tests">Writing Unit Tests</h4>
<p>Perfect, we can not only dim our display now, we can also write Unit Tests for our ViewModel in our test project, because we have an interface that we can use to mock the dependency <em>(I'm using the Moq library here, but any other mocking framework will work just as well)</em>:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> MauiSamples.Services.Device;
<span class="hljs-keyword">using</span> MauiSamples.ViewModels;
<span class="hljs-keyword">using</span> Moq;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples.Tests.ViewModels</span>;

[<span class="hljs-meta">TestFixture</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MainViewModelTests</span>
{
    [<span class="hljs-meta">Test</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetHighBrightness_SetScreenBrightnessCalled</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-comment">//arrange</span>
        <span class="hljs-keyword">var</span> deviceServiceMock = <span class="hljs-keyword">new</span> Mock&lt;IDeviceService&gt;();
        <span class="hljs-keyword">var</span> vm = <span class="hljs-keyword">new</span> MainViewModel(deviceServiceMock.Object);

        <span class="hljs-comment">//act</span>
        vm.SetHighBrightness();

        <span class="hljs-comment">//assert</span>
        deviceServiceMock.Verify(service =&gt; service.SetScreenBrightness(It.IsAny&lt;<span class="hljs-keyword">float</span>&gt;()), Times.Once);
    }
}
</code></pre>
<p>Wonderful. My developer heart is happy and full of joy. 💖</p>
<h3 id="heading-conclusions-and-next-steps">Conclusions and next steps</h3>
<p>As I have demonstrated, <strong>Multi-Targeting</strong> is a marvelous way to support platform-specific APIs from within a single MAUI app project. The need for platform-specific projects we know from <em>Xamarin.Forms</em> is gone, drastically reducing development and maintenance effort while still allowing to implement platform-specific functionality.</p>
<p>With <strong>filename-based Multi-Targeting</strong>, we can group platform implementations together, use a common interface and share some parts of the code to reduce code duplication. A .NET MAUI project doesn't require a variety of different platform and test projects to be maintained separately.</p>
<p>Even custom components and MAUI class libraries can make use of the Multi-Targeting approaches that I have presented in this mini-series, as you will see in future blog posts.</p>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters">LinkedIn</a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej">blog</a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples">GitHub repository</a> for this post so you don't miss out on any future posts.</p>
]]></content:encoded></item><item><title><![CDATA[Multi-Targeting in .NET MAUI - Part 1]]></title><description><![CDATA[Intro
I am a huge fan of the Dependency Inversion Principle (DIP). I apply it via Dependency Injection (DI) and Inversion of Control (IoC) a lot in my projects, because I like developing loosely coupled, well structured software components that are e...]]></description><link>https://blog.ewers-peters.de/multi-targeting-in-net-maui-part-1</link><guid isPermaLink="true">https://blog.ewers-peters.de/multi-targeting-in-net-maui-part-1</guid><category><![CDATA[conditional-compilation]]></category><category><![CDATA[multitargeting]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[app development]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Tue, 08 Nov 2022 09:09:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/kpmwxeDoNR4/upload/v1667817625048/H-0mXw_7G.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-intro">Intro</h3>
<p>I am a huge fan of the <em>Dependency Inversion Principle (DIP)</em>. I apply it via <em>Dependency Injection (DI)</em> and <em>Inversion of Control (IoC)</em> a lot in my projects, because I like developing loosely coupled, well structured software components that are easy to maintain and extend. </p>
<p>However, when it comes to mobile application development and especially to UI related topics that do not involve any business logic, applying the DIP for platform-specific APIs can be quite cumbersome and seems like overkill, because it often involves <em>Interfaces</em> and <em>IoC containers</em> (or the <em>Dependency Service</em> in <em>Xamarin.Forms</em>). </p>
<p>In this blog post, I am going to explore an alternative approach to using platform-specific code that comes already built-in with .NET MAUI: <strong>Multi-Targeting</strong>.  It is a clean way to implement and call platform-specific APIs, e.g. to dim the display, set the volume for audio playback or display a notification to the user without requiring interfaces and IoC containers.</p>
<p>In the .NET realm, the term has been around for a while already - .NET MAUI just makes heavy use of it with the single project approach. In Xamarin.Forms, you would need to create an interface, then create the different implementations in the platform projects and either register the implementation with the Dependency Service or with any other IoC container. That's not necessary anymore in .NET MAUI. Interfaces and Dependency Injection may still be required for complex, business-logic related scenarios, but this requirement has become optional for simple platform-specific APIs.</p>
<h3 id="heading-hello-from-platform">Hello from [Platform]!</h3>
<p>In this first part, we will simply display a message to the user from within an event handler for a simple Button press event:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">Text</span>=<span class="hljs-string">"Say Hello"</span> <span class="hljs-attr">Pressed</span>=<span class="hljs-string">"Button_OnPressed"</span> /&gt;</span>
</code></pre>
<p>When the Button is pressed, a "Hello from <em>[Platform]</em>!" message is shown and depending on the platform, it will either be "Hello from Android!", "Hello from iOS!", "Hello from Windows!" or "Hello from MacCatalyst!". </p>
<p>Let's start with the simplest (and older) form of condition-based access to platform-specific code.</p>
<h3 id="heading-conditional-compilation">Conditional Compilation</h3>
<p>One approach to build and ship platform-specific implementations is to use <strong>preprocessor directives</strong> (like <code>#define</code>, <code>#if</code>, <code>#elif</code>, etc.) and only compile certain bits of code when a specific target platform is selected. This is called <strong>conditional compilation</strong> and like the name says, specific parts of the code only get compiled when certain conditions are met.</p>
<p>For example, if we wanted to display an alert message with a different text on each platform, we could simply write the following code somewhere in our UI code, e.g. in an event handler that gets triggered when a Button was pressed:</p>
<pre><code class="lang-c#">    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Button_OnPressed</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> sender, EventArgs e</span>)</span>
    {
<span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> ANDROID</span>
        <span class="hljs-keyword">await</span> DisplayAlert(<span class="hljs-string">"Hello"</span>, <span class="hljs-string">"Hello from Android!"</span>, <span class="hljs-string">"OK"</span>);
<span class="hljs-meta">#<span class="hljs-meta-keyword">elif</span> IOS</span>
        <span class="hljs-keyword">await</span> DisplayAlert(<span class="hljs-string">"Hello"</span>, <span class="hljs-string">"Hello from iOS!"</span>, <span class="hljs-string">"OK"</span>);
<span class="hljs-meta">#<span class="hljs-meta-keyword">elif</span> WINDOWS</span>
        <span class="hljs-keyword">await</span> DisplayAlert(<span class="hljs-string">"Hello"</span>, <span class="hljs-string">"Hello from Windows!"</span>, <span class="hljs-string">"OK"</span>);
<span class="hljs-meta">#<span class="hljs-meta-keyword">else</span></span>
        <span class="hljs-keyword">await</span> DisplayAlert(<span class="hljs-string">"Hello"</span>, <span class="hljs-string">"Hello from another platform (MacCatalyst, Tizen, ...)"</span>, <span class="hljs-string">"OK"</span>);
<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span>
    }
</code></pre>
<p>Tapping the Button now triggers the display of an alert that looks like this on Android and very similar on the other platforms, just with different text:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667818073237/zbOTLiW36.PNG" alt="01_Hello_Android.PNG" /></p>
<p>This works and certainly is a valid approach for simple scenarios, but the code doesn't look great. As a general rule, preprocessor directives should be handled with care and should be avoided whenever possible as they lead to code that is illegible and difficult to maintain. They also bear the danger that some conditions are set incorrectly and then debugging becomes difficult quickly. </p>
<p>Imagine having to write a platform helper class that provides access to various different platform-specific APIs. Muddling them all together quickly becomes messy. Wouldn't it be better to separate the different implementations per platform instead? I think so, too.</p>
<p>Let's look at one of the alternatives.</p>
<h3 id="heading-a-simple-but-better-approach">A simple, but better approach</h3>
<p>Essentially, the built-in Multi-Targeting in .NET MAUI allows developers to implement and call platform-specific code from a shared context while only building and shipping the relevant parts of the code for the target platform.</p>
<p>The single project created via the default template comes with a <code>Platforms</code> folder and sub-folders for each separate platform, e.g. <code>Android</code>, <code>iOS</code> and so on. This structure provides a basic setup; the contents of the individual platform folders are only built and shipped for the targeted platform.</p>
<p>This allows us to create platform-specific assets and even redefine the same class inside the same namespace per platform without any build conflicts, all while keeping the irrelevant parts from the different platforms out of the resulting platform-specific app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667817966399/CLaWZin77.PNG" alt="00_Project_Structure.PNG" /></p>
<p>Now, instead of using conditional compilation, we can create a separate file in each of the platform-specific folders that the single project comes with by default and provide an implementation there. This will certainly work and probably is the best for simple scenarios like the one shown above with the <code>DisplayAlert()</code> call. </p>
<p>Let's create a <code>Messages.cs</code> for each platform with the following content:</p>
<p><strong>Android</strong></p>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Messages</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SayHello</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> Page page</span>)</span>
    {
        <span class="hljs-keyword">await</span> page.DisplayAlert(<span class="hljs-string">"Hello"</span>, <span class="hljs-string">"Hello from Android!"</span>, <span class="hljs-string">"OK"</span>);
    }
}
</code></pre>
<p><strong>iOS</strong></p>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Messages</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SayHello</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> Page page</span>)</span>
    {
        <span class="hljs-keyword">await</span> page.DisplayAlert(<span class="hljs-string">"Hello"</span>, <span class="hljs-string">"Hello from iOS!"</span>, <span class="hljs-string">"OK"</span>);
    }
}
</code></pre>
<p><strong>MacCatalyst</strong></p>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Messages</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SayHello</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> Page page</span>)</span>
    {
        <span class="hljs-keyword">await</span> page.DisplayAlert(<span class="hljs-string">"Hello"</span>, <span class="hljs-string">"Hello from MacCatalyst!"</span>, <span class="hljs-string">"OK"</span>);
    }
}
</code></pre>
<p><strong>Windows</strong></p>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiSamples</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Messages</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SayHello</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> Page page</span>)</span>
    {
        <span class="hljs-keyword">await</span> page.DisplayAlert(<span class="hljs-string">"Hello"</span>, <span class="hljs-string">"Hello from Windows!"</span>, <span class="hljs-string">"OK"</span>);
    }
}
</code></pre>
<p>After adding all the <code>Message.cs</code> files, our project structure looks as follows (note that each platform has its own <code>Messages</code> implementation now):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667818249493/N_FlPGJ5M.PNG" alt="02_Project_Structure_Messages.PNG" /></p>
<p><strong>Important:</strong> Please note that the namespace and the class name must be identical in each of the <code>Messages.cs</code> files in order for this approach to work. This is a general rule for multi-targeted APIs.</p>
<p>We can now update the event handler of the Button as follows:</p>
<pre><code class="lang-c#">    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Button_OnPressed</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> sender, EventArgs e</span>)</span>
    {
        <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>.SayHello();
    }
</code></pre>
<p>As we can see, the preprocessor directives are gone and we can call the <code>SayHello()</code> method as an extension method on our <code>MainPage</code> in a single line without having to worry about invoking platform-specific code anymore, because the build system takes care of that for us. It is much cleaner, more legible and easily maintained and extended.</p>
<p>It still works (showing <code>Windows</code> this time):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667818358199/sgQeDnBrz.PNG" alt="03_Hello_Windows.PNG" /></p>
<h3 id="heading-conclusions-and-next-steps">Conclusions and next steps</h3>
<p>The built-in <strong>Multi-Targeting</strong> approach of .NET MAUI is quite powerful, it works beautifully and usually suffices for simple uses cases like the one presented in this article. I have only shown how to handle the platform differences, no actual platform-specific APIs have been invoked so far. <em>I will get into actual platform-specific APIs in some of my upcoming blog posts (e.g. for setting the screen brightness on each platform).</em></p>
<p>In <strong><a target="_blank" href="https://ewerspej.hashnode.dev/multi-targeting-in-net-maui-part-2">Part 2</a></strong>, I will explore more advanced setups that involve unit tests and dependency injection, which require a different approach to Multi-Targeting. I will show how to setup your project using filename-based Multi-Targeting and partial classes for platform-specific implementations. So, stay tuned!</p>
<p>If you enjoyed this blog post, then follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters">LinkedIn</a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej">blog</a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples">GitHub repository</a> for this post so you don't miss out on any future posts.</p>
]]></content:encoded></item><item><title><![CDATA[Let's customize the Splash Screen of a MAUI app]]></title><description><![CDATA[Intro
A custom company or app logo that shows up while an app is starting is a nice eye catcher and helps with strengthening the recognition of a brand. In this blog post I want to show you how you can easily modify the splash screen which comes alre...]]></description><link>https://blog.ewers-peters.de/lets-customize-the-splash-screen-of-a-maui-app</link><guid isPermaLink="true">https://blog.ewers-peters.de/lets-customize-the-splash-screen-of-a-maui-app</guid><category><![CDATA[splash screen]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><category><![CDATA[mobile app development]]></category><category><![CDATA[#dotnet-maui]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Fri, 04 Nov 2022 14:04:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/UfseYCHvIH0/upload/v1667570511155/qqOoJuD_G.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-intro">Intro</h3>
<p>A custom company or app logo that shows up while an app is starting is a nice eye catcher and helps with strengthening the recognition of a brand. In this blog post I want to show you how you can easily modify the splash screen which comes already prepared with new MAUI app projects with your own logo and background.</p>
<h4 id="heading-considerations">Considerations</h4>
<p>In the past, apps would show complex graphics or fancy animations on the splash screen. With the advent of newer mobile operating system versions, especially since Android 12.0, mobile app developers have less options for the customization of the splash screen of an app, at least for simple scenarios.</p>
<p>For the purpose of a unified look and feel, the splash screen should be simple and feature only a custom logo and a simple background. Modern apps have relatively low start up latencies, so that the splash screen will not be visible for a long time anyway. Therefore, I will only focus on adding a custom logo and background color, advanced scenarios including animations and splash screen activities won't be covered.</p>
<p>However, as an extra goody, I will show you how to change the theme of the splash screen based on the system theme on Android.</p>
<p><em>Note: Starting with Android 12.0, the splash screen icon will be displayed inside a circular frame and everything outside that frame will be hidden by a mask. For more on this, please refer to the <a target="_blank" href="https://developer.android.com/develop/ui/views/launch/splash-screen">official documentation</a>.</em></p>
<h3 id="heading-the-default-splash-screen">The default splash screen</h3>
<p>This is what the default splash screen of the MAUI app template project looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667568687213/lJscG_4r6.PNG" alt="00_Splash_Default.PNG" class="image--center mx-auto" /></p>
<p>The logo and background color are defined in the <em>.csproj</em> file of your MAUI app project. You can edit the file by right-clicking on the project and selecting <em>"Edit Project File"</em>. Then scroll down to the <code>&lt;PropertyGroup&gt;</code> that contains the following:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Splash Screen --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">MauiSplashScreen</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Splash\splash.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#512BD4"</span> <span class="hljs-attr">BaseSize</span>=<span class="hljs-string">"128,128"</span> /&gt;</span>
</code></pre>
<h3 id="heading-add-your-own-logo">Add your own logo</h3>
<p>In order to add your own logo, the only thing you need to do is replace the <code>splash.svg</code> with your own logo. As an example, we will just use the <code>dotnet-bot.svg</code> which comes with the template, but we give it a different name, e.g. <code>mysplashscreen.svg</code>:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Splash Screen --&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">MauiSplashScreen</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Splash\mysplashscreen.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#512BD4"</span> <span class="hljs-attr">BaseSize</span>=<span class="hljs-string">"128,128"</span> /&gt;</span>
</code></pre>
<p><strong>Important:</strong> The logo should be square and with a transparent background for best results. </p>
<p><em>Note: At the time of writing, there is an issue with the one of the build actions of MAUI, which requires giving the splash screen image a completely new name each time it is changed. This will be fixed in a future release.</em></p>
<h3 id="heading-change-the-background-color">Change the background color</h3>
<p>Now, we just need to change the background color of the splash screen to make it look great:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Splash Screen --&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">MauiSplashScreen</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\Splash\mysplash.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#000000"</span> <span class="hljs-attr">BaseSize</span>=<span class="hljs-string">"128,128"</span> /&gt;</span>
</code></pre>
<h3 id="heading-result">Result</h3>
<p>Pretty cool, we now have a custom splash screen that works on Android and iOS:</p>
<h4 id="heading-ios">iOS</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667568699598/iPkLNVyDT.PNG" alt="01_Splash_iOS.PNG" class="image--center mx-auto" /></p>
<h4 id="heading-android">Android</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667568706627/yM88SQVvk.png" alt="02_Splash_Android.png" class="image--center mx-auto" /></p>
<h3 id="heading-change-appearance-based-on-android-system-theme">Change appearance based on Android system theme</h3>
<p>On Android, there is a little trick on how to go even a step further and change the background color (and status bar theme) for the splash screen depending on the selected system theme. For this, we can take advantage of the night mode feature to show the black background only for dark mode and a white background for white mode, which is a neat little extra.</p>
<h4 id="heading-light-mode">Light Mode</h4>
<p>In your MAUI project, navigate to the <code>Platforms/Android/Resources/</code> folder and, if not present yet, add a <code>values</code> subfolder. In this folder, create two files (or edit the existing ones):</p>
<ul>
<li>colors.xml</li>
<li>styles.xml</li>
</ul>
<p><strong>Attention:</strong> The <code>styles.xml</code> actually probably already exists and is simply hidden from the project. If so, right-click on the <code>values</code> folder and select <em>Add -&gt; Existing Item</em> and then select the <code>styles.xml</code> file in that location.</p>
<p>Then, in your <code>colors.xml</code> define some colors, e.g.:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">resources</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">color</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"colorPrimary"</span>&gt;</span>#c2c2c2<span class="hljs-tag">&lt;/<span class="hljs-name">color</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">color</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"colorPrimaryDark"</span>&gt;</span>#ffffff<span class="hljs-tag">&lt;/<span class="hljs-name">color</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">color</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"colorAccent"</span>&gt;</span>#3c3c3c<span class="hljs-tag">&lt;/<span class="hljs-name">color</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">resources</span>&gt;</span>
</code></pre>
<p>Next, create your own Theme in the <code>styles.xml</code>, inherit from <code>Maui.SplashTheme</code> and set your color for the <code>android:windowSplashScreenBackground</code> item:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">resources</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"MyTheme"</span> <span class="hljs-attr">parent</span>=<span class="hljs-string">"Maui.SplashTheme"</span>&gt;</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">item</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"android:windowSplashScreenBackground"</span>&gt;</span>@color/colorPrimaryDark<span class="hljs-tag">&lt;/<span class="hljs-name">item</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">item</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"android:windowLightStatusBar"</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">item</span>&gt;</span>
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">resources</span>&gt;</span>
</code></pre>
<p>This will set up your splash screen color when the user is using the Light theme of Android.</p>
<h4 id="heading-dark-mode">Dark Mode</h4>
<p>For Dark theme, you need to do essentially the same as for the Light theme, with a small difference.</p>
<p>In your MAUI project, add the following subfolder: <code>Platforms/Android/Resources/values-night</code> (note the <em>-night</em> suffix). In this folder, again create the two files:</p>
<ul>
<li>colors.xml</li>
<li>styles.xml</li>
</ul>
<p>In your <code>colors.xml</code> define some colors, e.g.:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">resources</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">color</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"colorPrimary"</span>&gt;</span>#c2c2c2<span class="hljs-tag">&lt;/<span class="hljs-name">color</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">color</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"colorPrimaryDark"</span>&gt;</span>#000000<span class="hljs-tag">&lt;/<span class="hljs-name">color</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">color</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"colorAccent"</span>&gt;</span>#c2c2c2<span class="hljs-tag">&lt;/<span class="hljs-name">color</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">resources</span>&gt;</span>
</code></pre>
<p>Again, create your own Theme in the <code>styles.xml</code>, inherit from <code>Maui.SplashTheme</code> and set your color for the <code>android:windowSplashScreenBackground</code> item:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">resources</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"MyTheme"</span> <span class="hljs-attr">parent</span>=<span class="hljs-string">"Maui.SplashTheme"</span>&gt;</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">item</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"android:windowSplashScreenBackground"</span>&gt;</span>@color/colorPrimaryDark<span class="hljs-tag">&lt;/<span class="hljs-name">item</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">item</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"android:windowLightStatusBar"</span>&gt;</span>false<span class="hljs-tag">&lt;/<span class="hljs-name">item</span>&gt;</span>
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">resources</span>&gt;</span>
</code></pre>
<p>This will set up your splash screen color when the user is using the Dark theme of Android.</p>
<p>When you're done, your folder structure should look something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667568418619/F1QFObrSX.PNG" alt="04_Android_Styles.PNG" /></p>
<h4 id="heading-final-step">Final Step</h4>
<p>Finally, we still need to apply our custom theme to our <code>MainActivity</code> like this:</p>
<pre><code class="lang-c#">[<span class="hljs-meta">Activity(Theme = <span class="hljs-meta-string">"@style/MyTheme"</span>, MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-title">MauiAppCompatActivity</span> { }
</code></pre>
<h4 id="heading-android-light-mode">Android: Light Mode</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667568720161/Ow38hRCjR.png" alt="04_Android_Light_Splash.png" class="image--center mx-auto" /></p>
<p><em>Note: The background color of the image can only be set once in the <code>&lt;MauiSplashScreen&gt;</code> build action and therefore is not adaptive, unfortunately. Notice the round image mask on Android as described in the beginning, as well.</em></p>
<h4 id="heading-android-dark-mode">Android: Dark Mode</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667568706627/yM88SQVvk.png" alt="02_Splash_Android.png" class="image--center mx-auto" /></p>
<h3 id="heading-conclusions">Conclusions</h3>
<p>Setting up a simple splash screen is very easy with .NET MAUI, because the <code>&lt;MauiSplashScreen&gt;</code> build action pretty much does all the work for you as it automatically generates everything for you. Unlike <em>Xamarin.Forms</em>, there is no need to create your own storyboard for iOS, for example, anymore.</p>
<p>If you enjoyed this blog post, you may want to follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters">LinkedIn</a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej">blog</a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples">GitHub repository</a> for this post.</p>
]]></content:encoded></item><item><title><![CDATA[A simple way to implement Dark Mode in .NET MAUI]]></title><description><![CDATA[Intro
I absolutely love dark app themes. As a person working a lot in front of computer screens and using mobile devices a lot, I have come to appreciate the ease on the eyes that Dark (or Night) Modes of software and mobile apps provide. In this blo...]]></description><link>https://blog.ewers-peters.de/implement-dark-mode-in-net-maui</link><guid isPermaLink="true">https://blog.ewers-peters.de/implement-dark-mode-in-net-maui</guid><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[dark mode]]></category><category><![CDATA[app development]]></category><category><![CDATA[theme]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Fri, 21 Oct 2022 09:41:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/qtfPFGVfNAw/upload/v1666947029007/zohsDf5Pk.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-intro">Intro</h3>
<p>I absolutely <strong>love</strong> dark app themes. As a person working a lot in front of computer screens and using mobile devices a lot, I have come to appreciate the ease on the eyes that <strong>Dark (or <em>Night</em>) Modes</strong> of software and mobile apps provide. In this blog post, I would like to show a simple way to implement this wonderful feature in order to provide a flawless user experience to your app's users without having to manipulate a <code>ResourceDictionary</code> at run-time.</p>
<p>As the convention goes these days, users should have the option to choose whether to use 
a Dark or Light Theme or to simply follow the system setting. <strong>.NET MAUI</strong> conveniently supports "Light" and "Dark" App Themes right out of the box. The sample Shell app even comes pre-configured with some styles that use the built-in markup extension <code>AppThemeBinding</code>. Let's explore how to take advantage of this. <em>The code for this sample implementation can be found in my <a target="_blank" href="https://github.com/ewerspej/maui-samples">GitHub repository</a>.</em></p>
<h3 id="heading-the-userapptheme-property-and-styles">The UserAppTheme property and Styles</h3>
<p>The <code>Application</code> class (<em>App.xaml.cs</em>) comes with a property of the <code>enum</code> type <code>AppTheme</code> called <code>UserAppTheme</code> which can be set to change the current theme:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// dark theme</span>
UserAppTheme = AppTheme.Dark;

<span class="hljs-comment">// light theme</span>
UserAppTheme = AppTheme.Light;

<span class="hljs-comment">// follow system default</span>
UserAppTheme = AppTheme.Unspecified;
</code></pre>
<p>Coupled with the <code>AppThemeBinding</code> markup extension inside some <code>Style</code> elements in a <code>ResourceDictionary</code> (e.g. defined in the <em>Styles.xaml</em> file) this would already to suffice to set the theme once and be done:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"ContentPage"</span> <span class="hljs-attr">ApplyToDerivedTypes</span>=<span class="hljs-string">"True"</span>&gt;</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BackgroundColor"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}"</span> /&gt;</span>
</span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>
</code></pre>
<p>Our app would now just be either shown with a black or white page background depending on the hard-coded value for the <code>UserAppTheme</code> property. This is already pretty neat, but we want to let the user decide. So let's do that.</p>
<h3 id="heading-our-own-theme-class">Our own Theme class</h3>
<p>In order to provide a meaningful user experience, we are going to implement our own <code>Theme</code> class which holds the <code>AppTheme</code> as well as a <code>DisplayName</code>. </p>
<p>The advantages of this class will become apparent further down. <em>Hint: The DisplayName could be used in advanced scenarios to show a translated string depending on the selected language, but we won't cover localization of string resources in this article.</em></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Theme</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Theme Dark = <span class="hljs-keyword">new</span>(AppTheme.Dark, <span class="hljs-string">"Night Mode"</span>);
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Theme Light = <span class="hljs-keyword">new</span>(AppTheme.Light, <span class="hljs-string">"Day Mode"</span>);
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Theme System = <span class="hljs-keyword">new</span>(AppTheme.Unspecified, <span class="hljs-string">"Follow System"</span>);

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> List&lt;Theme&gt; AvailableThemes { <span class="hljs-keyword">get</span>; } = <span class="hljs-keyword">new</span>()
    {
        Dark,
        Light,
        System
    };

    <span class="hljs-keyword">public</span> AppTheme AppTheme { <span class="hljs-keyword">get</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> DisplayName { <span class="hljs-keyword">get</span>; }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">Theme</span>(<span class="hljs-params">AppTheme theme, <span class="hljs-keyword">string</span> displayName</span>)</span>
    {
        AppTheme = theme;
        DisplayName = displayName;
    }
}
</code></pre>
<p>Since the theme can usually be selected in a <strong>Settings</strong> section of an app, we will also implement a small <code>SettingsService</code> class.</p>
<h3 id="heading-the-settingsservice">The SettingsService</h3>
<p>In this example, we simply create a small class that implements the <em>INotifyPropertyChanged</em> interface and use it as a singleton for convenience. I am skipping advanced topics like Dependency Injection and MVVM Code Generation.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SettingsService</span> : <span class="hljs-title">INotifyPropertyChanged</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> SettingsService _instance;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> SettingsService Instance =&gt; _instance ??= <span class="hljs-keyword">new</span> SettingsService();

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">SettingsService</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-comment">// set the default (in advanced scenarios, this could be read from the preferences)</span>
        Theme = Theme.System;
    }

    <span class="hljs-keyword">private</span> Theme _theme;
    <span class="hljs-keyword">public</span> Theme Theme
    {
        <span class="hljs-keyword">get</span> =&gt; _theme;
        <span class="hljs-keyword">set</span>
        {
           <span class="hljs-keyword">if</span>(_theme == <span class="hljs-keyword">value</span>) <span class="hljs-keyword">return</span>;
            _theme = <span class="hljs-keyword">value</span>;
           OnPropertyChanged();
        }
    }

    <span class="hljs-comment">//...</span>
}
</code></pre>
<p>We are going to use the <code>SettingsService</code> to hold the information about the current app theme, but it can also be extended to actually store the theme to the app's preferences. <em>To see how this can be done, check out the repository with the sample code further down.</em></p>
<h3 id="heading-a-sample-ui">A sample UI</h3>
<p>Now that we have the Theme and SettingsService classes, we can create a simple UI that will update its styling based on the selected <code>AppTheme</code>. We can add the following XAML to our <code>MainPage</code> (or any other <em>Page</em> for that matter) and bind to the <code>SettingsService.Instance</code> singleton and the <code>DisplayName</code> of our <code>Theme</code> class:</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.Resources</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Label"</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"LabelStyle"</span>&gt;</span><span class="xml">
        <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Text"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"{AppThemeBinding Light=Light, Dark=Dark}"</span> /&gt;</span>
      </span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ResourceDictionary</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.Resources</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">VerticalStackLayout</span>
      <span class="hljs-attr">Spacing</span>=<span class="hljs-string">"25"</span>
      <span class="hljs-attr">Padding</span>=<span class="hljs-string">"30,0"</span>
      <span class="hljs-attr">Margin</span>=<span class="hljs-string">"0,50,0,0"</span>
      <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Start"</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">Label</span> 
        <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
        <span class="hljs-attr">Style</span>=<span class="hljs-string">"{DynamicResource LabelStyle}"</span>
        <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Title"</span>/&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">HorizontalStackLayout</span>
        <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
        <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Label</span>
          <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
          <span class="hljs-attr">Text</span>=<span class="hljs-string">"Select Theme: "</span> /&gt;</span>

        <span class="hljs-comment">&lt;!-- <span class="hljs-doctag">Note:</span> We are binding to the SettingsService singleton and the DisplayName of the Theme --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Picker</span>
          <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
          <span class="hljs-attr">ItemsSource</span>=<span class="hljs-string">"{Binding Source={x:Static models:Theme.AvailableThemes}}"</span>
          <span class="hljs-attr">ItemDisplayBinding</span>=<span class="hljs-string">"{Binding DisplayName}"</span>
          <span class="hljs-attr">SelectedItem</span>=<span class="hljs-string">"{Binding Path=Theme, Source={x:Static services:SettingsService.Instance}}"</span>/&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">HorizontalStackLayout</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">VerticalStackLayout</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
</code></pre>
<p>We also need to define a few more styles in our main <code>ResourceDictionary</code> which lives in the <strong>Styles.xaml</strong> file of our project, but they can also be defined anywhere else:</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"ContentPage"</span> <span class="hljs-attr">ApplyToDerivedTypes</span>=<span class="hljs-string">"True"</span>&gt;</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BackgroundColor"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}"</span> /&gt;</span>
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Label"</span>&gt;</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"TextColor"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"{AppThemeBinding Light={StaticResource Gray500}, Dark={StaticResource White}}"</span> /&gt;</span>
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Picker"</span>&gt;</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"TextColor"</span> <span class="hljs-attr">Value</span>=<span class="hljs-string">"{AppThemeBinding Light={StaticResource Gray500}, Dark={StaticResource White}}"</span> /&gt;</span>
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>
</code></pre>
<p>This should roughly look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666341678295/8RhG2-s_g.PNG" alt="00_SampleUI.PNG" /></p>
<p>At the moment, nothing will actually happen when we tap on the Picker and select any of the Themes, because we still need to wire up the <code>SettingService</code> with the App's <code>UserAppTheme</code> property. Let's do that next.</p>
<h3 id="heading-bringing-it-all-together">Bringing it all together</h3>
<p>The final step is to subscribe to the <code>PropertyChanged</code> event of our <code>SettingsService</code> in order to be able to react to changes in the settings. We will do this from within the <strong>App.xaml.cs</strong>, because that's where the <code>UserAppTheme</code> property lives:</p>
<pre><code>public App()
{
    InitializeComponent();
    MainPage = <span class="hljs-keyword">new</span> AppShell();

    <span class="hljs-comment">// let's set the initial theme already during the app start</span>
    SetTheme();

    <span class="hljs-comment">// subscribe to changes in the settings</span>
    SettingsService.Instance.PropertyChanged += OnSettingsPropertyChanged;
}

private <span class="hljs-keyword">void</span> OnSettingsPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    <span class="hljs-keyword">if</span> (e.PropertyName == nameof(SettingsService.Theme))
    {
        SetTheme();
    }
}

private <span class="hljs-keyword">void</span> SetTheme()
{
    UserAppTheme = SettingsService.Instance?.Theme != <span class="hljs-literal">null</span>
                 ? SettingsService.Instance.Theme.AppTheme
                 : AppTheme.Unspecified;
}
</code></pre><p>Essentially, what we are doing here is to respond to changes in the settings directly. This is possible, because our <code>SettingsService</code> implements the <code>INotifyPropertyChanged</code> interface.</p>
<h3 id="heading-running-the-app">Running the app</h3>
<p>Finally, we can run the app and select the App Theme during run-time, which is pretty awesome:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666342866947/cvPqFJfJ0.PNG" alt="01_SelectTheme.PNG" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666342875779/7uj-TdUlN.PNG" alt="02_DarkMode.PNG" /></p>
<h3 id="heading-conclusion-and-further-writing">Conclusion and further writing</h3>
<p><strong>.NET MAUI</strong> comes with a lot of functionality out of the box and ready to use. I hope this blog post gives you an idea of how to provide an easy way to add a Dark Mode to your app and let your users select their preferred App Theme.</p>
<p>In future posts, I will show more advanced topics like wiring up the Picker control with localized strings, creating custom components and sharing them via nuget.org, as well as using MVVM Code Generation to your advantage to reduce. Stay tuned!</p>
<p>If you enjoyed this blog post, you may want to follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters">LinkedIn</a>, subscribe to this <a target="_blank" href="https://hashnode.com/@ewerspej">blog</a> and star the <a target="_blank" href="https://github.com/ewerspej/maui-samples">GitHub repository</a> for this post.</p>
<p><strong>Note:</strong> <em>At the time of writing, I have used Visual Studio 2022 17.4 Preview 2.1 and .NET 7 RC1.</em></p>
]]></content:encoded></item><item><title><![CDATA[Console.WriteLine("Hello, Developers");]]></title><description><![CDATA[Init();
My name is Julian and I am a passionate (mobile) app developer and I would like to share  the things I'm learning during the life long journey as a developer. 
My third attempt at starting a blog
Yes, I've tried before. Without a plan. Young ...]]></description><link>https://blog.ewers-peters.de/hello-developers</link><guid isPermaLink="true">https://blog.ewers-peters.de/hello-developers</guid><category><![CDATA[C#]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[app development]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[Julian Ewers-Peters]]></dc:creator><pubDate>Fri, 07 Oct 2022 08:42:02 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-init">Init();</h2>
<p>My name is Julian and I am a passionate (mobile) app developer and I would like to share  the things I'm learning during the life long journey as a developer. </p>
<h2 id="heading-my-third-attempt-at-starting-a-blog">My third attempt at starting a blog</h2>
<p>Yes, I've tried before. Without a plan. Young and naive. Now, I'm 35 and a freelancer. Still clueless, but with a goal: Enjoying the journey of casually sharing my experiences in the development world.</p>
<p>My articles will be relatively short (like this sentence). I like writing, but I cannot stand unnecessary prose in tech blogs. <em>Any philosophical writings will be tagged as such, to give you a fair chance at circumventing them.</em></p>
<p>As this is just the beginning of a (hopefully) longer journey in blogging about tech, I will keep it short for now:</p>
<pre><code>Console.WriteLine(<span class="hljs-string">"Hello, Developers"</span>);
</code></pre><h2 id="heading-what-will-i-be-writing-about">What will I be writing about?</h2>
<ul>
<li>App Development</li>
<li>Xamarin.Forms</li>
<li>.NET MAUI</li>
<li>C#/.NET</li>
<li>Flutter</li>
<li>Unit Tests</li>
<li>Clean Code</li>
<li>Excursions in Software Architecture and Design</li>
</ul>
<p>Follow me on <a target="_blank" href="https://www.linkedin.com/in/jewerspeters">LinkedIn</a> to stay in the loop about what I'm currently up to.</p>
]]></content:encoded></item></channel></rss>