Jekyll2021-12-15T10:12:36+00:00http://smstuebe.de/feed.xmlSvens BlogBlog about xamarin and c# developmentUsing Xamarin Live Reload with VMWare Fusion in offline land2018-07-06T12:00:00+00:002018-07-06T12:00:00+00:00http://smstuebe.de/2018/07/06/xamarin-live-reload-vmware<p>One of the latest big improvement to the Xamarin.Forms ecosystem is Xamarin LiveReload. It’s the next step when you reach the limits of LivePlayer. It helps you quickly iterating during your UI developent.</p>
<p>For more information and installation intructions, see: <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/live-reload " target="_blank" onclick="return tol(this);">Xamarin Live Reload</a>.</p>
<p><a href="https://twitter.com/ianvink " target="_blank" onclick="return tol(this);">Ian Vink</a> wrote a <a href="https://ianvink.wordpress.com/2018/07/06/live-reload-from-visual-studio-2017-for-windows-in-wmware-fusion-on-a-mac-xamarin/ " target="_blank" onclick="return tol(this);">blog post</a> about how to use Xamarin Live Reload in a MacOS + VMWare Fusion + Visual Studio 2017 on a Windows 10 VM environment.</p>
<p>The major disadvantage of the described setup is: <b>It doesn’t work without internet or setting up a local MQTT server</b>.</p>
<p>That’s why I want to describe the setup I’ve chosen on my machine. On my development machine I use port forwarding from MacOS to my Windows 10 VM. Here is what you have to do, if you live in a country like me where fast internet isn’t available everywhere (Germany, not kidding!) or you are on a plane ✈️ or train 🚆.</p>
<h3>Setup</h3>
<ol>
<li>ensure, your VM uses NAT as networking adapter type (<code class="language-plaintext highlighter-rouge">Virtual Machine -> Network Adapter -> NAT</code>)</li>
<li>get your VMs IP address (<code class="language-plaintext highlighter-rouge">Window -> Virtual Machine Library</code> or <kbd>⌘</kbd> <kbd>⇧</kbd> <kbd>L</kbd>) when your machine is running
<img src="/img/live-reload-vmware-library.png" style="margin:0 auto; cursor: pointer;" /></li>
<li>open the terminal on your Mac</li>
<li>open the nat configuration file
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>vim /Library/Preferences/VMware<span class="se">\ </span>Fusion/vmnet8/nat.conf
</code></pre></div> </div>
</li>
<li>find <code class="language-plaintext highlighter-rouge">[incomingtcp]</code></li>
<li>add <code class="language-plaintext highlighter-rouge">1883 = 192.168.2.178:1883</code> (<b>replace the IP with the IP from step 2!</b>)
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>incomingtcp]
<span class="c"># Use these with care — anyone can enter into your VM through these…</span>
<span class="c"># The format and example are as follows:</span>
<span class="c">#<external port number> = <VM’s IP address>:<VM’s port number></span>
1883 <span class="o">=</span> 192.168.2.178:1883
</code></pre></div> </div>
</li>
<li>exit vim 😜</li>
<li>restart VM networking
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> /Applications/VMware<span class="se">\ </span>Fusion.app/Contents/Library/vmnet-cli <span class="nt">--stop</span>
<span class="nb">sudo</span> /Applications/VMware<span class="se">\ </span>Fusion.app/Contents/Library/vmnet-cli <span class="nt">--start</span>
</code></pre></div> </div>
</li>
</ol>
<p>You might have to restart your VM if Live Reload doesn’t work the first time.</p>
<p><strong>Bonus tip:</strong> I’m using port forwarding for several other applications (like Windows Device Portal, local web servers, etc., too. So feel freee to add as many ports as you want :)</p>
<p><br />
<small>Found a typo? Send me a pull request!</small></p>Sven-Michael StübeOne of the latest big improvement to the Xamarin.Forms ecosystem is Xamarin LiveReload. It’s the next step when you reach the limits of LivePlayer. It helps you quickly iterating during your UI developent.Xamarin.iOS WiFi debugging2018-03-07T12:00:00+00:002018-03-07T12:00:00+00:00http://smstuebe.de/2018/03/07/ios-wifi-debug<p>The latest Visual Studio update makes an Xcode iOS/tvOS debugging feature available for Xamarin developers. It’s called WiFi debugging. This means that you can now debug your Xamarin.iOS Apps without having to plug your phone in via USB.</p>
<h3>Requirements</h3>
<p>Make sure you have installed at least these versions of the following software:</p>
<ul>
<li>Xcode 9.0</li>
<li>macOS 10.12.4</li>
<li>VS Mac 7.4 or VS 2017.1.6</li>
<li>iOS 11.0 or tvOS 11.0</li>
</ul>
<p>Your Mac and your device have to be connected to the same network.</p>
<h3>Setup</h3>
<p>Setting up your device for WiFi debugging is pretty easy. Just</p>
<ul>
<li>connect your iOS Device via USB</li>
<li>open Xcode</li>
<li>navigate to the device manager (Window > Devices and Simulators)</li>
<li>select your device and check <code class="language-plaintext highlighter-rouge">Connect via network</code></li>
<li>disconnect the USB connection</li>
</ul>
<p><img src="/img/wifi-debug-paired.png" style="margin:0 auto; cursor: pointer;" /></p>
<h3>Special to Xamarin</h3>
<p>In its <a href="https://help.apple.com/xcode/mac/9.0/index.html?localePath=en.lproj#/devac3261a70" target="_blank" onclick="return tol(this);">trouble shooting guide</a>, Apple states, that you have to ensure that port <code class="language-plaintext highlighter-rouge">62078</code> is open. This port is used to connect the device to Xcode (make it visible in the device list). In addition to that you can configure the Debugger port in VS. The default port is <code class="language-plaintext highlighter-rouge">10000</code> and you can configure it in the project settings of your iOS project in the iOS Debug section. There, you can also force the debugger to attach via network even if your device is connected via USB.</p>
<p><img src="/img/wifi-debug-ports.png" style="margin:0 auto; cursor: pointer;" /></p>
<h3>Observations</h3>
<p>WiFi Deploy/Debug works well. Unfortunately, the connection is flaky as soon as the device goes into sleep mode / turns off the display.
Sometimes, I couldn’t bring it back to work and had to open the device window of Xcode to trigger a new connection. The Visual Studio teams have done a good job integrating this requested feature. Unfortunately, the flaky connection currently prevents me from using it in production. But if you really need to free up your USB ports, you should have a look at this feature.</p>
<!-- ((ip.src in {192.168.43.129 192.168.43.63}) || (ip.dst in {192.168.43.129 192.168.43.63})) && (tcp.port in {10000 53872 62078}) -->
<p><br />
<small>Found a typo? Send me a pull request!</small></p>Sven-Michael StübeThe latest Visual Studio update makes an Xcode iOS/tvOS debugging feature available for Xamarin developers. It’s called WiFi debugging. This means that you can now debug your Xamarin.iOS Apps without having to plug your phone in via USB.Reading NFC tags with iOS 11 and CoreNFC2017-09-05T12:00:00+00:002017-09-05T12:00:00+00:00http://smstuebe.de/2017/09/05/reading-nfc-ios<p>Besides introducing the famous ARKit, Apple made a former private API usable in every App with iOS 11: <a href="https://developer.apple.com/documentation/corenfc" target="_blank" onclick="return tol(this);">CoreNFC</a>. CoreNFC allows reading NFC tags with your iPhone 7/7+ and maybe soon with your iPhone 8 ;) And of course the Xamarin team has worked hard to make it usable with C#. In this blog post I’ll show you how to quickly read a message from a NFC tag formatted in NDEF.</p>
<h2 class="section-heading">Setup</h2>
<h3>Development environment</h3>
<p>Because iOS 11 is still beta, the setup up effort is a bit higher. You can skip this if Apple and Xamarin have released the stable verions.</p>
<ul>
<li>Download and install XCode 9 beta 6</li>
<li>Download and install the Xamarin release for XCode 9 support <a href="https://releases.xamarin.com/preview-xcode-9-beta-6-ios-11-macos-10-13-support-preview-6/" target="_blank" onclick="return tol(this);">here</a> and read <strong>carefully</strong>!</li>
</ul>
<h3>App Identifier</h3>
<p>Unfortunately, you can’t just use the API with any App. You have to enable the <code class="language-plaintext highlighter-rouge">NFC Tag Reading</code> feature in your developer account.</p>
<ul>
<li>Go to <a href="https://developer.apple.com" target="_blank" onclick="return tol(this);">developer.apple.com</a> and sign into your developer account</li>
<li>Open App IDs</li>
<li>Create a new <strong>Explicit</strong> App ID e.g. <code class="language-plaintext highlighter-rouge">com.smstuebe.test.nfc</code> (Wildcard App IDs don’t allow enabling this feature)</li>
<li>check <code class="language-plaintext highlighter-rouge">NFC Tag Reading</code> and continue</li>
<li>make sure the App ID is included in a provisioning profile</li>
</ul>
<p><img src="/img/nfc-ios-app-id.png" style="margin:0 auto; cursor: pointer;" /></p>
<h3>Project</h3>
<p>In your iOS project you have to add some more stuff.</p>
<p><b>Info.plist</b></p>
<p>In your Info.plist file you have to set the <code class="language-plaintext highlighter-rouge">CFBundleIdentifier</code> and add the <code class="language-plaintext highlighter-rouge">NFCReaderUsageDescription</code>.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><key></span>CFBundleIdentifier<span class="nt"></key></span>
<span class="nt"><string></span>com.smstuebe.test.nfc<span class="nt"></string></span>
<span class="nt"><key></span>NFCReaderUsageDescription<span class="nt"></key></span>
<span class="nt"><string></span>This is very important to my App.<span class="nt"></string></span></code></pre></figure>
<p><b>Entitlements.plist</b></p>
<p>Add the following lines to the Entitlements.plist file. Because we want to read Tags using the NDEF format, we add <code class="language-plaintext highlighter-rouge">NDEF</code> to the <code class="language-plaintext highlighter-rouge">com.apple.developer.nfc.readersession.formats</code>.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dict></span>
<span class="nt"><key></span>com.apple.developer.nfc.readersession.formats<span class="nt"></key></span>
<span class="nt"><array></span>
<span class="nt"><string></span>NDEF<span class="nt"></string></span>
<span class="nt"></array></span>
<span class="nt"></dict></span></code></pre></figure>
<h2 class="section-heading">Code</h2>
<p>Finally, the setup is done. Coding will be much easier :) You can easily asyncify the API by using a <code class="language-plaintext highlighter-rouge">TaskCompletionSource</code>.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">class</span> <span class="nc">AsyncNfcReader</span> <span class="p">:</span> <span class="n">NSObject</span><span class="p">,</span> <span class="n">INFCNdefReaderSessionDelegate</span>
<span class="p">{</span>
<span class="k">private</span> <span class="n">NFCNdefReaderSession</span> <span class="n">_session</span><span class="p">;</span>
<span class="k">private</span> <span class="n">TaskCompletionSource</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="n">_tcs</span><span class="p">;</span>
<span class="k">public</span> <span class="n">Task</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="nf">ScanAsync</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">NFCNdefReaderSession</span><span class="p">.</span><span class="n">ReadingAvailable</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidOperationException</span><span class="p">(</span><span class="s">"Reading NDEF is not available"</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">_tcs</span> <span class="p">=</span> <span class="k">new</span> <span class="n">TaskCompletionSource</span><span class="p"><</span><span class="kt">string</span><span class="p">>();</span>
<span class="n">_session</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">NFCNdefReaderSession</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">DispatchQueue</span><span class="p">.</span><span class="n">CurrentQueue</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="n">_session</span><span class="p">.</span><span class="nf">BeginSession</span><span class="p">();</span>
<span class="k">return</span> <span class="n">_tcs</span><span class="p">.</span><span class="n">Task</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">DidInvalidate</span><span class="p">(</span><span class="n">NFCNdefReaderSession</span> <span class="n">session</span><span class="p">,</span> <span class="n">NSError</span> <span class="n">error</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_tcs</span><span class="p">.</span><span class="nf">TrySetException</span><span class="p">(</span><span class="k">new</span> <span class="nf">Exception</span><span class="p">(</span><span class="n">error</span><span class="p">?.</span><span class="n">LocalizedFailureReason</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">DidDetect</span><span class="p">(</span><span class="n">NFCNdefReaderSession</span> <span class="n">session</span><span class="p">,</span> <span class="n">NFCNdefMessage</span><span class="p">[]</span> <span class="n">messages</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">bytes</span> <span class="p">=</span> <span class="n">messages</span><span class="p">[</span><span class="m">0</span><span class="p">].</span><span class="n">Records</span><span class="p">[</span><span class="m">0</span><span class="p">].</span><span class="n">Payload</span><span class="p">.</span><span class="nf">Skip</span><span class="p">(</span><span class="m">3</span><span class="p">).</span><span class="nf">ToArray</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">message</span> <span class="p">=</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">.</span><span class="nf">GetString</span><span class="p">(</span><span class="n">bytes</span><span class="p">);</span>
<span class="n">_tcs</span><span class="p">.</span><span class="nf">SetResult</span><span class="p">(</span><span class="n">message</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><code class="language-plaintext highlighter-rouge">NFCNdefReaderSession</code> is the class that handles the reading of NDEF tags. It creates a system dialog (see Test section) when calling <code class="language-plaintext highlighter-rouge">BeginSession()</code>. This dialog isn’t customizable, yet. Because of my experiences with the fingerprint API, I think Apple will add some possibilities for customization (mainly localizing the shown texts) in later releases.</p>
<p>You can read a single tag or multiple tags in one session. The 3rd constructor parameter <code class="language-plaintext highlighter-rouge">invalidateAfterFirstRead</code> is set to true, because we want to read just one tag.</p>
<p>The <code class="language-plaintext highlighter-rouge">AsyncNfcReader</code> implements the <code class="language-plaintext highlighter-rouge">INFCNdefReaderSessionDelegate</code> and thats why it inherits from <code class="language-plaintext highlighter-rouge">NSObject</code>. In the <code class="language-plaintext highlighter-rouge">DidDetect</code> we read the message from the first record. In my case the Record is of type <code class="language-plaintext highlighter-rouge">T</code>. This means the first 3 bytes contain the Encoding and the language. In the interests of simplification I just ignore the first 3 bytes with <code class="language-plaintext highlighter-rouge">Skip(3)</code> and decode the remaining bytes as UTF8 string. The format is specified in the <a href="https://learn.adafruit.com/adafruit-pn532-rfid-nfc/ndef" target="_blank" onclick="return tol(this);">NDEF specification</a>. There are libraries like <a href="https://github.com/andijakl/ndef-nfc" target="_blank" onclick="return tol(this);">NDEF NFC</a> that allow decoding the payload correctly.</p>
<p>You can use the <code class="language-plaintext highlighter-rouge">AsyncNfcReader</code> like this in your code:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="kt">var</span> <span class="n">reader</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AsyncNfcReader</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">message</span> <span class="p">=</span> <span class="k">await</span> <span class="n">reader</span><span class="p">.</span><span class="nf">ScanAsync</span><span class="p">();</span></code></pre></figure>
<h2 class="section-heading">Test</h2>
<p>For testing I bought some <a href="http://amzn.to/2wDpWsY" target="_blank" onclick="return tol(this);">NFC tags with 4kB storage</a> and wrote some data to it with an Android App.
Simply start the App and scan the tag. The antenna is located at the upper egde of your iPhone.</p>
<p><img src="/img/nfc-ios-app-test.png" style="margin:0 auto; cursor: pointer;" /></p>
<h2 class="section-heading">Discussion</h2>
<ul>
<li>Error handling and decoding is very sloppy. This has to be hardened in production apps.</li>
<li>The documentation from Apple isn’t complete. The API supports other formats than NDEF (e.g. ISO15693).</li>
<li>The API doesn’t seem to support writing tags (06.09.2017)</li>
<li>I started implementing a <a href="https://github.com/smstuebe/xamarin-nfc" target="_blank" onclick="return tol(this);">Plugin for reading NFC tags</a>. I’ll publish it in the next months after the features are available in the stable releases.</li>
</ul>
<p><br />
<small>Found a typo? Send me a pull request!</small></p>Sven-Michael StübeBesides introducing the famous ARKit, Apple made a former private API usable in every App with iOS 11: CoreNFC. CoreNFC allows reading NFC tags with your iPhone 7/7+ and maybe soon with your iPhone 8 ;) And of course the Xamarin team has worked hard to make it usable with C#. In this blog post I’ll show you how to quickly read a message from a NFC tag formatted in NDEF.Calligraphy with MvvmCross2017-02-12T12:00:00+00:002017-02-12T12:00:00+00:00http://smstuebe.de/2017/02/12/mvvmcross-calligraphy<p>Calligraphy is a nice library that provides custom font handling in Android. I once had issues with the MvvmCross layout inflation and knew that using Calligraphy with MvvmCross will cause some problems and I pushed it onto my Blog todo list - but I was lazy. But this week I saw <a href="http://stackoverflow.com/questions/42105583/callygraphyxamarin-not-working-in-mvxappcompatactivity" target="_blank" onclick="return tol(this);">a question on stackoverflow</a>.</p>
<h2 class="section-heading">TL;DR</h2>
<p>If you just want to get Calligraphy working.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Install-Package MvvmCross.Calligraphy
</code></pre></div></div>
<p>and modify your Setup.cs</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">class</span> <span class="nc">Setup</span> <span class="p">:</span> <span class="n">MvxAndroidSetup</span>
<span class="p">{</span>
<span class="k">protected</span> <span class="k">override</span> <span class="n">MvxAndroidBindingBuilder</span> <span class="nf">CreateBindingBuilder</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">CalligraphyMvxAndroidBindingBuilder</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span> </code></pre></figure>
<p>done. You <strong>don’t</strong> need to override <code class="language-plaintext highlighter-rouge">AttachBaseContext</code>. And you can setup calligraphy via <code class="language-plaintext highlighter-rouge">CalligraphyConfig.InitDefault</code> as documented.</p>
<h2 class="section-heading">Problem</h2>
<p>MvvmCross and Calligraphy are using the same approach for two different things. MvvmCross uses a custom LayoutInflater to provide xml based data binding and Calligraphy uses a custom LayoutInflater that sets the font to TextViews at inflation time. And if you use MvvmCross, the MvvmCross inflater wins.</p>
<h2 class="section-heading">Why not use Calligraphy Xamarin?</h2>
<p>There is already the NuGet package / Xamarin component called <a href="https://developer.xamarin.com/guides/xamarin-forms/effects/creating/" target="_blank" onclick="return tol(this);">Calligraphy Xamarin</a>. But it doesn’t offer the non public class <code class="language-plaintext highlighter-rouge">CalligraphyFactory</code> that you need for the fix. If you install it and follow the setup instructions, you will notice, that it won’t have any effect.</p>
<h2 class="section-heading">How does it work?</h2>
<p>The package provides a custom <code class="language-plaintext highlighter-rouge">MvxAndroidViewBinder</code> called <code class="language-plaintext highlighter-rouge">CalligraphyMvxAndroidViewBinder</code> that applies calligraphy changes for every layout that is inflated with <code class="language-plaintext highlighter-rouge">BindingInflate</code> (which should be basically every view). All what’s left, is to put the pieces together. Use <code class="language-plaintext highlighter-rouge">CalligraphyMvxAndroidViewFactory</code> to construct it and <code class="language-plaintext highlighter-rouge">CalligraphyMvxAndroidBindingBuilder</code> to return the factory in <code class="language-plaintext highlighter-rouge">CreateAndroidViewBinderFactory()</code>. In your code you only have to return a new <code class="language-plaintext highlighter-rouge">CalligraphyMvxAndroidBindingBuilder</code> in your Setup’s <code class="language-plaintext highlighter-rouge">CreateBindingBuilder()</code> method.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">class</span> <span class="nc">Setup</span> <span class="p">:</span> <span class="n">MvxAndroidSetup</span>
<span class="p">{</span>
<span class="k">protected</span> <span class="k">override</span> <span class="n">MvxAndroidBindingBuilder</span> <span class="nf">CreateBindingBuilder</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">CalligraphyMvxAndroidBindingBuilder</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span> </code></pre></figure>
<p><img src="/img/mvvmcross-calligraphy.jpg" style="margin:0 auto; cursor: pointer;" /></p>
<h2 class="section-heading">Conclusion</h2>
<p>There is a nice side effect, using the factory directly. You don’t have to wrap every context (Activity, Fragment) with the <code class="language-plaintext highlighter-rouge">CalligraphyContextWrapper</code>, because the binding inflater is used globally :) Yay! <a href="https://www.nuget.org/packages/MvvmCross.Calligraphy/" target="_blank" onclick="return tol(this);">MvvmCross.Calligraphy on NuGet</a></p>
<p><b>Code</b></p>
<p>The code is available on github <i class="fa fa-github"></i><a href="https://github.com/smstuebe/mvvmcross-calligraphy" target="_blank" onclick="return tol(this);">mvvmcross-calligraphy</a>. It includes a sample project, too.</p>
<center>If you like the answer, I'd appreciate an upvote ;) <br /><a href="http://stackoverflow.com/a/42184315/1489968" target="_blank" title="Answer" onclick="return tol(this);">http://stackoverflow.com/a/42184315/1489968</a><br />Thanks!</center>
<p><br />
<small><a href="https://flic.kr/p/fJNKB" title="Photo">Background Photo</a> by <a href="https://www.flickr.com/photos/56832361@N00/" title="Karen">Karen</a> / <a href="http://creativecommons.org/licenses/by/2.0/" title="CC BY">CC BY</a></small>
<br />
<small>Found a typo? Send me a pull request!</small></p>Sven-Michael StübeCalligraphy is a nice library that provides custom font handling in Android. I once had issues with the MvvmCross layout inflation and knew that using Calligraphy with MvvmCross will cause some problems and I pushed it onto my Blog todo list - but I was lazy. But this week I saw a question on stackoverflow.Validating new github issues with Azure Functions2017-01-12T12:00:00+00:002017-01-12T12:00:00+00:00http://smstuebe.de/2017/01/12/github-issue-checker-azure-function<p>In 2016 github added a feature, that allows users to create templates for new issues and pull requests. This is nice, because if the maintainers can help users to provide all necessary data without asking for them several times.
Unfortunately, some people just delete all of the template and start typing. In this blog post I’ll show my solution for this problem.</p>
<h2 class="section-heading">Functionality</h2>
<p><img src="/img/issue-checker-system.png" /></p>
<p>When a user opens a new issue, github sends the issue information to a Azure Function web hook. The function analyses the issue data and compares the issue text with the issue template. After computing the matching quote, it adds an comment using the github API.</p>
<h2 class="section-heading">Setup</h2>
<h3 class="section-heading">Azure Function</h3>
<h4 class="section-heading">Creation</h4>
<p>Creating a new Azure Function is very easy.</p>
<ul>
<li>open <a href="https://portal.azure.com/" target="_blank">https://portal.azure.com/</a></li>
<li>add a Function app and open it</li>
<li>click <code class="language-plaintext highlighter-rouge">New Function</code></li>
<li>select scenario <code class="language-plaintext highlighter-rouge">Webhook + API</code></li>
<li>select language <code class="language-plaintext highlighter-rouge">C#</code></li>
<li>click <code class="language-plaintext highlighter-rouge">Create this function</code></li>
</ul>
<p>If you need a more detailed tutorial, click <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-a-web-hook-or-api-function" target="_blank">here</a>.</p>
<h4 class="section-heading">Configuration</h4>
<p>The github API for .NET is implemented in <code class="language-plaintext highlighter-rouge">Octokit</code> and it’s available on NuGet.
To add the NuGet package to your Function:</p>
<ul>
<li>click <code class="language-plaintext highlighter-rouge">View files</code></li>
<li>click <code class="language-plaintext highlighter-rouge">Add</code></li>
<li>name it <code class="language-plaintext highlighter-rouge">project.json</code></li>
<li>paste the following code and save it</li>
</ul>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
</span><span class="nl">"frameworks"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"net46"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"Octokit"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.23.0"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span></code></pre></figure>
<h3 class="section-heading">Github</h3>
<h4 class="section-heading">Webhook</h4>
<p>Now, you have to configure github to call your webhook when a new issue was opened.</p>
<ul>
<li>Open the settings of the repository that you want to monitor.</li>
<li>click <code class="language-plaintext highlighter-rouge">Webhooks</code></li>
<li>click <code class="language-plaintext highlighter-rouge">Add Webhook</code></li>
<li>enter the Azure Function url into <code class="language-plaintext highlighter-rouge">Payload URL</code></li>
<li>enter the GitHub Secret into <code class="language-plaintext highlighter-rouge">Secret</code></li>
<li>select <code class="language-plaintext highlighter-rouge">Let me select individual events</code></li>
<li>select <code class="language-plaintext highlighter-rouge">Issues</code></li>
</ul>
<p><img src="/img/issue-checker-webhook.png" /></p>
<p>To verify your setup, you can open an issue. If you look at the log output of your function, you’ll see something like:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">2017-01-11T22:22:42.222 C# HTTP trigger function processed a request.</code></pre></figure>
<p><strong>Pro tip:</strong> Before opening the issue, add this line to your function:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="n">log</span><span class="p">.</span><span class="nf">Info</span><span class="p">(</span><span class="s">$"Data: </span><span class="p">{</span><span class="n">data</span><span class="p">}</span><span class="s">"</span><span class="p">);</span></code></pre></figure>
<p>The request body will be written to the log. You can then copy it and paste it into the <code class="language-plaintext highlighter-rouge">Test</code> section and every time you press <code class="language-plaintext highlighter-rouge">Run</code> the “real” request will be processed. So you don’t need to open a new issue on github to test you function.</p>
<h4 class="section-heading">OAuth API token</h4>
<p>The github API requires an access token. Create a new one following these steps:</p>
<ul>
<li>open <a href="https://github.com/" target="_blank">https://github.com/</a></li>
<li>log in and open your settings</li>
<li>click <code class="language-plaintext highlighter-rouge">Personal access tokens</code></li>
<li>click <code class="language-plaintext highlighter-rouge">Generate new token</code></li>
<li>enter a name and select <code class="language-plaintext highlighter-rouge">public_repo</code></li>
<li>click <code class="language-plaintext highlighter-rouge">Generate token</code></li>
<li>save the generated token</li>
</ul>
<p><img src="/img/issue-checker-token.png" /></p>
<h4 class="section-heading">Issue template</h4>
<p>If your repository doesn’t contain an issue template <code class="language-plaintext highlighter-rouge">ISSUE_TEMPLATE.md</code>, add one. Click <a href="https://github.com/blog/2111-issue-and-pull-request-templates" target="_blank">here</a> for more information on issue templates.
Additionally add the file <code class="language-plaintext highlighter-rouge">ISSUE_TEMPLATE_CHECK.md</code>. This file contains the lines that should be included in the issue in the given order. I’ve chosen the headings and some bold bullet points.</p>
<figure class="highlight"><pre><code class="language-markdown" data-lang="markdown"><span class="gu">## Steps to reproduce</span>
<span class="gu">## Expected behavior</span>
<span class="gu">## Actual behavior</span>
<span class="gu">### Crashlog</span>
<span class="gu">## Configuration</span>
<span class="gs">**Version of the Plugin:**</span>
<span class="gs">**Platform:**</span>
<span class="gs">**Device:**</span></code></pre></figure>
<h2 class="section-heading">Let's code!</h2>
<p>Now, everything is setup correctly and you can code (or copy paste) your Azure Function. The code is available on <i class="fa fa-github"></i><a href="https://github.com/smstuebe/github-issue-template-checker" target="_blank">github</a>.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">HttpResponseMessage</span><span class="p">></span> <span class="nf">Run</span><span class="p">(</span><span class="n">HttpRequestMessage</span> <span class="n">req</span><span class="p">,</span> <span class="n">TraceWriter</span> <span class="n">log</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">dynamic</span> <span class="n">data</span> <span class="p">=</span> <span class="k">await</span> <span class="n">req</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="n">ReadAsAsync</span><span class="p"><</span><span class="kt">object</span><span class="p">>();</span>
<span class="n">log</span><span class="p">.</span><span class="nf">Info</span><span class="p">(</span><span class="s">$"Data. </span><span class="p">{</span><span class="n">data</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
<span class="k">await</span> <span class="nf">ProcessIssueAsync</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
<span class="k">return</span> <span class="n">req</span><span class="p">.</span><span class="nf">CreateResponse</span><span class="p">(</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">OK</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">ProcessIssueAsync</span><span class="p">(</span><span class="kt">dynamic</span> <span class="n">data</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">data</span><span class="p">?.</span><span class="n">action</span> <span class="p">!=</span> <span class="s">"opened"</span><span class="p">)</span>
<span class="k">return</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">creator</span> <span class="p">=</span> <span class="p">(</span><span class="kt">string</span><span class="p">)</span><span class="n">data</span><span class="p">.</span><span class="n">issue</span><span class="p">.</span><span class="n">user</span><span class="p">.</span><span class="n">login</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">owner</span> <span class="p">=</span> <span class="p">(</span><span class="kt">string</span><span class="p">)</span><span class="n">data</span><span class="p">.</span><span class="n">repository</span><span class="p">.</span><span class="n">owner</span><span class="p">.</span><span class="n">login</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">repository</span> <span class="p">=</span> <span class="p">(</span><span class="kt">string</span><span class="p">)</span><span class="n">data</span><span class="p">.</span><span class="n">repository</span><span class="p">.</span><span class="n">name</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">repositoryId</span> <span class="p">=</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">data</span><span class="p">.</span><span class="n">repository</span><span class="p">.</span><span class="n">id</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">branch</span> <span class="p">=</span> <span class="p">(</span><span class="kt">string</span><span class="p">)</span><span class="n">data</span><span class="p">.</span><span class="n">repository</span><span class="p">.</span><span class="n">default_branch</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">issueLines</span> <span class="p">=</span> <span class="nf">GetLines</span><span class="p">((</span><span class="kt">string</span><span class="p">)</span><span class="n">data</span><span class="p">.</span><span class="n">issue</span><span class="p">.</span><span class="n">body</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">templateLines</span> <span class="p">=</span> <span class="p">(</span><span class="k">await</span> <span class="nf">GetTemplateElements</span><span class="p">(</span><span class="n">owner</span><span class="p">,</span> <span class="n">repository</span><span class="p">,</span> <span class="n">branch</span><span class="p">)).</span><span class="nf">ToArray</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">matchingQuote</span> <span class="p">=</span> <span class="nf">CheckIssueWithTemplate</span><span class="p">(</span><span class="n">issueLines</span><span class="p">,</span> <span class="n">templateLines</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">message</span> <span class="p">=</span> <span class="nf">GetMessage</span><span class="p">(</span><span class="n">creator</span><span class="p">,</span> <span class="n">matchingQuote</span><span class="p">);</span>
<span class="k">await</span> <span class="nf">CreateCommentAsync</span><span class="p">(</span><span class="n">repositoryId</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">data</span><span class="p">.</span><span class="n">issue</span><span class="p">.</span><span class="n">number</span><span class="p">,</span> <span class="n">message</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>First, call <code class="language-plaintext highlighter-rouge">ProcessIssueAsync</code> from your <code class="language-plaintext highlighter-rouge">Run</code> function. <code class="language-plaintext highlighter-rouge">ProcessIssueAsync</code> implements the workflow of the issue. You need to check if the <code class="language-plaintext highlighter-rouge">action</code> is <code class="language-plaintext highlighter-rouge">"opened"</code>, because github sends all issue related events to your webhook.
The function then:</p>
<ul>
<li>reads some properties from the request</li>
<li>splits the issue text into lines</li>
<li>gets all lines of <code class="language-plaintext highlighter-rouge">ISSUE_TEMPLATE_CHECK.md</code></li>
<li>checks the <code class="language-plaintext highlighter-rouge">issueLines</code> using the <code class="language-plaintext highlighter-rouge">templateLines</code> and calculates a matching quote in percent</li>
<li>generates a message based on the matching quote</li>
<li>creates a new comment on the issue</li>
</ul>
<p>The format of the issue request is documented <a href="https://developer.github.com/v3/activity/events/types/#issuesevent" target="_blank">here</a>.</p>
<p><strong>CheckIssueWithTemplate</strong></p>
<p><code class="language-plaintext highlighter-rouge">CheckIssueWithTemplate</code> counts the lines of <code class="language-plaintext highlighter-rouge">templateLines</code> that occur in <code class="language-plaintext highlighter-rouge">issueLines</code> and calculates a quote that expresses how well the check condition was satisfied. You can implement any logic you want. It just has to project two string arrays to a number between <code class="language-plaintext highlighter-rouge">0</code> (=issue template wasn’t used at all) and <code class="language-plaintext highlighter-rouge">1</code> (=100% sure that the issue template was used).</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">static</span> <span class="kt">double</span> <span class="nf">CheckIssueWithTemplate</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">issueLines</span><span class="p">,</span> <span class="kt">string</span><span class="p">[]</span> <span class="n">templateLines</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">templateLines</span><span class="p">.</span><span class="n">Length</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
<span class="k">return</span> <span class="m">1</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">templateList</span> <span class="p">=</span> <span class="n">templateLines</span><span class="p">.</span><span class="nf">ToList</span><span class="p">();</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">issueLine</span> <span class="k">in</span> <span class="n">issueLines</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">found</span> <span class="p">=</span> <span class="n">templateList</span><span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">tpl</span> <span class="p">=></span> <span class="n">issueLine</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="n">tpl</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">found</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">templateList</span><span class="p">.</span><span class="nf">Remove</span><span class="p">(</span><span class="n">found</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">var</span> <span class="n">matches</span> <span class="p">=</span> <span class="n">templateLines</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="n">templateList</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span>
<span class="k">return</span> <span class="n">matches</span> <span class="p">/</span> <span class="p">(</span><span class="kt">double</span><span class="p">)</span><span class="n">templateLines</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p><strong>GetMessage</strong></p>
<p>The <code class="language-plaintext highlighter-rouge">GetMessage</code> generates a praisingly message if the user used the template and a sad one if he probably didn’t. You can add your on text and thresholds if you want.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">static</span> <span class="kt">string</span> <span class="nf">GetMessage</span><span class="p">(</span><span class="kt">string</span> <span class="n">userName</span><span class="p">,</span> <span class="kt">double</span> <span class="n">matchingQuote</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">string</span> <span class="n">message</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">matchingQuote</span> <span class="p">></span> <span class="m">0.9</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">message</span> <span class="p">=</span> <span class="s">"Thanks for using the issue template :kissing_heart:\n"</span> <span class="p">+</span>
<span class="s">"I appreciate it very much. I'm sure, the maintainers of this repository will answer, soon."</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">message</span> <span class="p">=</span> <span class="s">$"It seems like (</span><span class="p">{(</span><span class="m">1</span> <span class="p">-</span> <span class="n">matchingQuote</span><span class="p">):</span><span class="n">P</span><span class="p">}</span><span class="s">) you haven't used our issue template :cry: "</span> <span class="p">+</span>
<span class="s">$"I think it is very frustrating for the repository owners, if you ignore them.\n\n"</span> <span class="p">+</span>
<span class="s">$"If you think it's fine to make an exception, just ignore this message.\n"</span> <span class="p">+</span>
<span class="s">$"**But if you think it was a mistake to delete the template, please close the issue and create a new one.**\n\n"</span> <span class="p">+</span>
<span class="s">$"Thanks!"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="s">$"Hi @</span><span class="p">{</span><span class="n">userName</span><span class="p">}</span><span class="s">,\n\n"</span> <span class="p">+</span>
<span class="s">$"I'm the friendly issue checker.\n"</span> <span class="p">+</span>
<span class="n">message</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p><strong>CreateCommentAsync</strong>
Last you have just to send the comment to github. The API makes this very easy. The developers of github (and ofc. the contributers) have done a great job!
You have to replace the token with your own one.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">CreateCommentAsync</span><span class="p">(</span><span class="kt">long</span> <span class="n">repositoryId</span><span class="p">,</span> <span class="kt">int</span> <span class="n">issueNumber</span><span class="p">,</span> <span class="kt">string</span> <span class="n">message</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">GitHubClient</span><span class="p">(</span><span class="k">new</span> <span class="nf">ProductHeaderValue</span><span class="p">(</span><span class="s">"github-issue-checker"</span><span class="p">));</span>
<span class="kt">var</span> <span class="n">tokenAuth</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Credentials</span><span class="p">(</span><span class="s">"<github OAuth token>"</span><span class="p">);</span>
<span class="n">client</span><span class="p">.</span><span class="n">Credentials</span> <span class="p">=</span> <span class="n">tokenAuth</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">comment</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="n">Issue</span><span class="p">.</span><span class="n">Comment</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="n">repositoryId</span><span class="p">,</span> <span class="n">issueNumber</span><span class="p">,</span> <span class="n">message</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<h2 class="section-heading">Result</h2>
<p><img src="/img/issue-checker-create-issue.png" />
will result in:
<img src="/img/issue-checker-bad.png" /></p>
<p>A nice looking issue will be commented like:
<img src="/img/issue-checker-good.png" /></p>
<h2 class="section-heading">Possible additions</h2>
<p>I think you are now aware of the possibilities that the connection of github webhooks, Azure Functions and the github API offers you. The Function can be easily enhanced with features like</p>
<ul>
<li>close issues automatically if match qoute is smaller than 10%</li>
<li>save issue opener to database and block them after 3 bad issues</li>
<li>make the matching algorithm <code class="language-plaintext highlighter-rouge">CheckIssueWithTemplate</code> more intelligent</li>
<li>make the messages configurable like <code class="language-plaintext highlighter-rouge">ISSUE_TEMPLATE_CHECK.md</code></li>
<li>…</li>
</ul>
<p><i class="fa fa-twitter"></i> <a href="https://twitter.com/intent/tweet?text=@stuebe2k7" target="_blank">Tweet</a> me your best idea and win a like :)</p>
<p><small>Found a typo? Send me a pull request!</small></p>Sven-Michael StübeIn 2016 github added a feature, that allows users to create templates for new issues and pull requests. This is nice, because if the maintainers can help users to provide all necessary data without asking for them several times. Unfortunately, some people just delete all of the template and start typing. In this blog post I’ll show my solution for this problem.Fixing Xamarin.Android build errors2016-10-29T12:00:00+00:002016-10-29T12:00:00+00:00http://smstuebe.de/2016/10/29/fix-android-app-compat<p>Today we had an awesome Xamarin Dev Day in Munich. Unfortunately a lot of people, especially people (and MVPs :P) that have installed Xamarin freshly, had problems building the sample apps.</p>
<p>If you just installed Xamarin or updated the Xamarin.Forms NuGet package or one of the Xamarin.Android packages and run into one of the listed error messages, reading further will probably relieve your pain.</p>
<h2 class="section-heading">Errors</h2>
<p>We saw a variety of build errors. Here are a couple of them.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">Please install package: 'Xamarin.Android.Support.v4' available in SDK installer. Java library file ...\AppData\Local\Xamarin\Xamarin.Android.Support.v4\23.3.0.0\content\libs/internal_impl-23.3.0.jar doesn't exist.
Please install package: 'Xamarin.Android.Support.Vector.Drawable' available in SDK installer. Android resource directory ...\AppData\Local\Xamarin\Xamarin.Android.Support.Vector.Drawable\23.3.0.0\content\./ doesn't exist.
Please install package: 'Xamarin.Android.Support.v7.RecyclerView' available in SDK installer. Android resource directory ...\AppData\Local\Xamarin\Xamarin.Android.Support.v7.RecyclerView\23.3.0.0\content\./ doesn't
Please install package: 'Xamarin.Android.Support.Vector.Drawable' available in SDK installer. Java library file ...\AppData\Local\Xamarin\Xamarin.Android.Support.Vector.Drawable\23.3.0.0\content\classes.jar doesn't
Please install package: 'Xamarin.Android.Support.v4' available in SDK installer. Android resource directory ...\AppData\Local\Xamarin\Xamarin.Android.Support.v4\23.3.0.0\content\./ doesn't exist.
Please install package: 'Xamarin.Android.Support.Design' available in SDK installer. Java library file ...\AppData\Local\Xamarin\Xamarin.Android.Support.Design\23.3.0.0\content\classes.jar doesn't exist.
Please install package: 'Xamarin.Android.Support.v7.MediaRouter' available in SDK installer. Java library file ...\AppData\Local\Xamarin\Xamarin.Android.Support.v7.MediaRouter\23.3.0.0\content\classes.jar doesn't exist.
Please install package: 'Xamarin.Android.Support.Animated.Vector.Drawable' available in SDK installer. Java library file ...\AppData\Local\Xamarin\Xamarin.Android.Support.Animated.Vector.Drawable\23.3.0.0\content\classes.jar doesn't exist.
Reason: File ...\AppData\Local\Xamarin\zips\2A3A8A6D6826EF6CC653030E7D695C41.zip is not a ZIP archive
Please install package: 'Xamarin.Android.Support.v7.MediaRouter' available in SDK installer. Android resource directory ...\AppData\Local\Xamarin\Xamarin.Android.Support.v7.MediaRouter\23.3.0.0\content\./ doesn't exist.
Unzipping failed. Please download https://dl-ssl.google.com/android/repository/android_m2repository_r29.zip and extract it to the ...\AppData\Local\Xamarin\Xamarin.Android.Support.Animated.Vector.Drawable\23.3.0.0\content directory.
Please install package: 'Xamarin.Android.Support.v7.CardView' available in SDK installer. Android resource directory ...\AppData\Local\Xamarin\Xamarin.Android.Support.v7.CardView\23.3.0.0\content\./ doesn't exist.
Please install package: 'Xamarin.Android.Support.v7.CardView' available in SDK installer. Java library file ...\AppData\Local\Xamarin\Xamarin.Android.Support.v7.CardView\23.3.0.0\content\classes.jar doesn't exist.
Please install package: 'Xamarin.Android.Support.v4' available in SDK installer. Java library file ...\AppData\Local\Xamarin\Xamarin.Android.Support.v4\23.3.0.0\content\classes.jar doesn't exist.
Please install package: 'Xamarin.Android.Support.v7.MediaRouter' available in SDK installer. Java library file ...\AppData\Local\Xamarin\Xamarin.Android.Support.v7.MediaRouter\23.3.0.0\content\libs/internal_impl-23.3.0.jar doesn't exist.
Please install package: 'Xamarin.Android.Support.v7.AppCompat' available in SDK installer. Android resource directory ...\AppData\Local\Xamarin\Xamarin.Android.Support.v7.AppCompat\23.3.0.0\content\./ doesn't exist.
Please install package: 'Xamarin.Android.Support.Animated.Vector.Drawable' available in SDK installer. Android resource directory ...\AppData\Local\Xamarin\Xamarin.Android.Support.Animated.Vector.Drawable\23.3.0.0\content\./ doesn't exist.
Please install package: 'Xamarin.Android.Support.v7.RecyclerView' available in SDK installer. Java library file ...\AppData\Local\Xamarin\Xamarin.Android.Support.v7.RecyclerView\23.3.0.0\content\classes.jar doesn't exist.
Please install package: 'Xamarin.Android.Support.v7.AppCompat' available in SDK installer. Java library file ...\AppData\Local\Xamarin\Xamarin.Android.Support.v7.AppCompat\23.3.0.0\content\classes.jar doesn't exist.
Please install package: 'Xamarin.Android.Support.Design' available in SDK installer. Android resource directory ...\AppData\Local\Xamarin\Xamarin.Android.Support.Design\23.3.0.0\content\./ doesn't exist.</code></pre></figure>
<p>or</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">...\Resources\values\styles.xml
error APT0000: Error retrieving parent for item: No resource found that matches the given name 'Theme.AppCompat.Light.DarkActionBar'.
error APT0000: No resource found that matches the given name: attr 'colorAccent'.
error APT0000: No resource found that matches the given name: attr 'colorPrimary'.
error APT0000: No resource found that matches the given name: attr 'colorPrimaryDark'.
error APT0000: No resource found that matches the given name: attr 'windowActionBar'.
error APT0000: No resource found that matches the given name: attr 'windowNoTitle'.
error APT0000: Error retrieving parent for item: No resource found that matches the given name 'style/Widget.AppCompat.Light.ActionBar.Solid'.
error APT0000: No resource found that matches the given name: attr 'elevation'.</code></pre></figure>
<h2 class="section-heading">Cause</h2>
<p>After a package restore, Xamarin is downloading missing Android SDK packages into <code class="language-plaintext highlighter-rouge">%UserProfile%\AppData\Local\Xamarin\zips</code>. This will take a while (depending on your internet connection and the google servers) and the build seems frozen. If you are impatient and kill Visual Studio or cancel the build, you end up in a undefined state. If clean / rebuild doesn’t fix it, you have to call the artillery. Unfortunately, deleting the packages, bin, obj folders isn’t enough because NuGet and Xamarin are caching the packages.</p>
<h2 class="section-heading">Fix</h2>
<ul>
<li>close visual studio</li>
<li>open the console at the solution root (where <code class="language-plaintext highlighter-rouge">*.sln</code> and <code class="language-plaintext highlighter-rouge">packages</code> folder are located)</li>
<li>Execute the following commands</li>
</ul>
<figure class="highlight"><pre><code class="language-bat" data-lang="bat"><span class="c">REM ARTILLERY SCRIPT</span>
<span class="c">REM delete bin and obj folders</span>
<span class="k">for</span> <span class="na">/d /r </span>. <span class="vm">%d</span> <span class="k">in</span> <span class="o">(</span><span class="kd">obj</span><span class="o">)</span> <span class="k">do</span> @if <span class="ow">exist</span> <span class="s2">"</span><span class="vm">%d</span><span class="s2">"</span> <span class="nb">rd</span> <span class="na">/s /q </span><span class="s2">"</span><span class="vm">%d</span><span class="s2">"</span>
<span class="k">for</span> <span class="na">/d /r </span>. <span class="vm">%d</span> <span class="k">in</span> <span class="o">(</span><span class="kd">bin</span><span class="o">)</span> <span class="k">do</span> @if <span class="ow">exist</span> <span class="s2">"</span><span class="vm">%d</span><span class="s2">"</span> <span class="nb">rd</span> <span class="na">/s /q </span><span class="s2">"</span><span class="vm">%d</span><span class="s2">"</span>
<span class="c">REM delete packages folder</span>
<span class="nb">rd</span> <span class="na">/s /q </span><span class="kd">packages</span>
<span class="c">REM delete Xamarin.Android packages from NuGet cache</span>
<span class="k">for</span> <span class="na">/d </span><span class="vm">%d</span> <span class="k">in</span> <span class="o">(</span><span class="s2">"</span><span class="nv">%UserProfile%</span><span class="s2">\.nuget\packages\Xamarin.Android.*"</span><span class="o">)</span> <span class="k">do</span> @if <span class="ow">exist</span> <span class="s2">"</span><span class="vm">%d</span><span class="s2">"</span> <span class="nb">rd</span> <span class="na">/s /q </span><span class="s2">"</span><span class="vm">%d</span><span class="s2">"</span>
<span class="c">REM delete Xamarin.Android packages from Xamarin cache</span>
<span class="k">for</span> <span class="na">/d </span><span class="vm">%d</span> <span class="k">in</span> <span class="o">(</span><span class="s2">"</span><span class="nv">%UserProfile%</span><span class="s2">\AppData\Local\Xamarin\Xamarin.Android.*"</span><span class="o">)</span> <span class="k">do</span> @if <span class="ow">exist</span> <span class="s2">"</span><span class="vm">%d</span><span class="s2">"</span> <span class="nb">rd</span> <span class="na">/s /q </span><span class="s2">"</span><span class="vm">%d</span><span class="s2">"</span>
<span class="c">REM delete broken downloads</span>
<span class="nb">rd</span> <span class="na">/s /q </span><span class="s2">"</span><span class="nv">%UserProfile%</span><span class="s2">\AppData\Local\Xamarin\zips"</span></code></pre></figure>
<ul>
<li>rebuild and wait</li>
</ul>
<p><strong>Warning:</strong> This will delete a bunch of folders silently. If you don’t trust me, replace <code class="language-plaintext highlighter-rouge">rd /s /q</code> with <code class="language-plaintext highlighter-rouge">echo</code> and execute the commands and you will see the folders that will be deleted. If you want to confirm each deletion, remove the <code class="language-plaintext highlighter-rouge">/q</code> switch.</p>
<p><br />
<small><a href="https://flic.kr/p/94qMp8" title="Photo">Background Photo</a> by <a href="https://www.flickr.com/photos/robandstephanielevy/" title="Rob and Stephanie Levy">Rob and Stephanie Levy</a> / <a href="http://creativecommons.org/licenses/by/2.0/" title="CC BY">CC BY</a></small>
<br />
<small>Found a typo? Send me a pull request!</small></p>Sven-Michael StübeToday we had an awesome Xamarin Dev Day in Munich. Unfortunately a lot of people, especially people (and MVPs :P) that have installed Xamarin freshly, had problems building the sample apps.Fingerprint plugin 1.3.0 for Xamarin released2016-10-13T12:00:00+00:002016-10-13T12:00:00+00:00http://smstuebe.de/2016/10/13/fingerprintv1.3<p>I recently pushed the stable version 1.3.0 of my fingerprint plugin for xamarin to NuGet. The code is available on <i class="fa fa-github"></i><a href="https://github.com/smstuebe/xamarin-fingerprint" title="github">github</a>.</p>
<p>If you want to try it you have two options.
You can simply clone the repository and build and execute the sample applications, or integrate it directly into your own app via NuGet <span class="inline"><a href="https://www.nuget.org/packages/Plugin.Fingerprint/"><img src="https://img.shields.io/nuget/v/Plugin.Fingerprint.svg?label=NuGet" alt="NuGet" /></a> <a href="https://www.nuget.org/packages/MvvmCross.Plugins.Fingerprint/"><img src="https://img.shields.io/nuget/v/MvvmCross.Plugins.Fingerprint.svg?label=NuGet MvvmCross" alt="NuGet MvvmCross" /></a>.</span></p>
<h2 class="section-heading">Changes</h2>
<h3>Breaking</h3>
<p>The property <code class="language-plaintext highlighter-rouge">IsAvailable</code> got replaced with <code class="language-plaintext highlighter-rouge">GetAvailabilityAsync()</code> which gives you a more detailed feedback why the fingerprint authentication is not available if it isn’t available. The possible options are:</p>
<ul>
<li>Available: Fingerprint authentication can be used.</li>
<li>NoImplementation: This plugin has no implementation for the current platform.</li>
<li>NoApi: Operating system has no supported fingerprint API.</li>
<li>NoPermission: App is not allowed to access the fingerprint sensor.</li>
<li>NoSensor: Device has no fingerprint sensor.</li>
<li>NoFingerprint: Fingerprint isn’t set up.</li>
<li>Unknown: An unknown, platform specific error occurred.</li>
</ul>
<p>If you are not interested in the reason, you can easily check the availability via <code class="language-plaintext highlighter-rouge">IsAvailableAsync()</code>.</p>
<h3>Samsung Support</h3>
<p>Samsung devices (even > Android 6.0) are using a own API for fingerprint authentication called Samsung Pass. The plugin supports it now. Authentication on pre Marshmallow Devices is possible, now. The used pass SDK version is 1.2.0, because 1.2.1 wasn’t working on a Lollipop device. If you are interested how I bound the obfuscated library, have a look at <a href="https://github.com/smstuebe/xamarin-fingerprint/blob/master/src/samsung/readme.md" target="_blank">Samsung Binding</a>. Tweet me if you have some better ideas.</p>
<h3>Animations</h3>
<p>The built in Authentication dialog on Android gives animated feedback to the user.
The following example shows a failed attempt followed by requesting the fallback.</p>
<p><img src="/img/fp-fallback.gif" /></p>
<h3>Localization</h3>
<p>The reason text and button labels are now fully customizable. But there are some <a href="https://github.com/smstuebe/xamarin-fingerprint/blob/master/README.md#limitations" target="_blank">platform specific limitations</a>.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="kt">var</span> <span class="n">dialogConfig</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AuthenticationRequestConfiguration</span><span class="p">(</span><span class="s">"Beweise, dass du Finger hast!"</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">CancelTitle</span> <span class="p">=</span> <span class="s">"Abbrechen"</span><span class="p">,</span>
<span class="n">FallbackTitle</span> <span class="p">=</span> <span class="s">"Anders!"</span>
<span class="p">};</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">Plugin</span><span class="p">.</span><span class="n">Fingerprint</span><span class="p">.</span><span class="n">CrossFingerprint</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="nf">AuthenticateAsync</span><span class="p">(</span><span class="n">dialogConfig</span><span class="p">);</span></code></pre></figure>
<h3>Other changes</h3>
<p><strong>.NET standard support</strong></p>
<p>All PCLs are now targeting .NET standard 1.0. The nuget configuration has been adjusted.</p>
<p><strong>Bugfixes</strong></p>
<p>Of course I fixed some bugs and did some minor improvements. If you are interested in more detail have a look at the <a href="https://github.com/smstuebe/xamarin-fingerprint/blob/master/doc/changelog.md" target="_blank">changelog</a>.</p>
<h2 class="section-heading">What next?</h2>
<p>I could test the samsung integration only on two devices and the standard implementation only on a simulator. I’d be happy to get some feedback/bug reports/feature request from you :)</p>
<p><br />
<small><a href="https://flic.kr/p/DW1sP" title="Photo">Background Photo</a> by <a href="https://www.flickr.com/photos/c0t0s0d0/" title="c0t0s0d0">c0t0s0d0</a> / <a href="http://creativecommons.org/licenses/by/2.0/" title="CC BY">CC BY</a></small>
<br />
<small>Found a typo? Send me a pull request!</small></p>Sven-Michael StübeI recently pushed the stable version 1.3.0 of my fingerprint plugin for xamarin to NuGet. The code is available on github.Awaitable Animations on Android2016-09-20T00:00:00+00:002016-09-20T00:00:00+00:00http://smstuebe.de/2016/09/20/android-async-animation<p>Xamarin has done a great job on bringing async/await to animations on iOS (as Kerry <a href="http://kerry.lothrop.de/animation-fun/" target="_blank">shows on his blog</a>) and on <a href="https://blog.xamarin.com/creating-animations-with-xamarin-forms/" target="_blank">Xamarin.Forms</a>. For my Fingerprint Plugin I was playing around with some animations on Android and I expected to find a similar animation extension - but I did not (If I’ve overseen something, tweet me). But, awaiting animations is pretty easy with a small extension method.</p>
<h3>Problem</h3>
<p>I want to colorize a icon, animate it and remove the color after the animation. In another use case I want to animate the icon and close the dialog afterwards.</p>
<h3>Solution</h3>
<p>The Android way would be to implement a <code class="language-plaintext highlighter-rouge">IAnimatorListener</code>, set the color in its <code class="language-plaintext highlighter-rouge">OnAnimationStart</code> method and reset it in <code class="language-plaintext highlighter-rouge">OnAnimationEnd</code>. But with this approach the code isn’t readable linearly and you would have to implement a <code class="language-plaintext highlighter-rouge">IAnimatorListener</code> for each use case. I wanted to write my Animation like this:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="n">_icon</span><span class="p">.</span><span class="nf">SetColorFilter</span><span class="p">(</span><span class="n">NegativeColor</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">shake</span> <span class="p">=</span> <span class="n">ObjectAnimator</span><span class="p">.</span><span class="nf">OfFloat</span><span class="p">(</span><span class="n">_icon</span><span class="p">,</span> <span class="s">"translationX"</span><span class="p">,</span> <span class="p">-</span><span class="m">10f</span><span class="p">,</span> <span class="m">10f</span><span class="p">);</span>
<span class="n">shake</span><span class="p">.</span><span class="nf">SetDuration</span><span class="p">(</span><span class="m">500</span><span class="p">);</span>
<span class="n">shake</span><span class="p">.</span><span class="nf">SetInterpolator</span><span class="p">(</span><span class="k">new</span> <span class="nf">CycleInterpolator</span><span class="p">(</span><span class="m">5</span><span class="p">));</span>
<span class="k">await</span> <span class="n">shake</span><span class="p">.</span><span class="nf">StartAsync</span><span class="p">();</span>
<span class="n">_icon</span><span class="p">.</span><span class="nf">ClearColorFilter</span><span class="p">();</span></code></pre></figure>
<p>You only need these two classes and are ready to animate Android with C# style.</p>
<p><strong>Extension</strong></p>
<p>The <code class="language-plaintext highlighter-rouge">AnimatorExtensions</code> only contains the <code class="language-plaintext highlighter-rouge">StartAsync</code> method that you should use to start a animation. It adds a <code class="language-plaintext highlighter-rouge">TaskAnimationListener</code> as listener, starts the animation and returns the a Task. The method can be used with all Animator classes (e.g. <code class="language-plaintext highlighter-rouge">ObjectAnimator, AnimatorSet, ValueAnimator</code>)</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">AnimatorExtensions</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="n">Task</span> <span class="nf">StartAsync</span><span class="p">(</span><span class="k">this</span> <span class="n">Animator</span> <span class="n">animator</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">listener</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">TaskAnimationListener</span><span class="p">();</span>
<span class="n">animator</span><span class="p">.</span><span class="nf">AddListener</span><span class="p">(</span><span class="n">listener</span><span class="p">);</span>
<span class="n">animator</span><span class="p">.</span><span class="nf">Start</span><span class="p">();</span>
<span class="k">return</span> <span class="n">listener</span><span class="p">.</span><span class="n">Task</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><strong>TaskAnimationListener</strong></p>
<p>The <code class="language-plaintext highlighter-rouge">TaskAnimationListener</code> implements <code class="language-plaintext highlighter-rouge">IAnimatorListener</code> and uses a <code class="language-plaintext highlighter-rouge">TaskCompletionSource</code> to convert the events <code class="language-plaintext highlighter-rouge">OnAnimationCancel</code> and <code class="language-plaintext highlighter-rouge">OnAnimationEnd</code> to the Canceled or Completed states of a Task.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">class</span> <span class="nc">TaskAnimationListener</span> <span class="p">:</span> <span class="n">Java</span><span class="p">.</span><span class="n">Lang</span><span class="p">.</span><span class="n">Object</span><span class="p">,</span> <span class="n">Animator</span><span class="p">.</span><span class="n">IAnimatorListener</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">TaskCompletionSource</span><span class="p"><</span><span class="kt">int</span><span class="p">></span> <span class="n">_tcs</span><span class="p">;</span>
<span class="k">public</span> <span class="n">Task</span> <span class="n">Task</span> <span class="p">=></span> <span class="n">_tcs</span><span class="p">.</span><span class="n">Task</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">TaskAnimationListener</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_tcs</span> <span class="p">=</span> <span class="k">new</span> <span class="n">TaskCompletionSource</span><span class="p"><</span><span class="kt">int</span><span class="p">>();</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnAnimationCancel</span><span class="p">(</span><span class="n">Animator</span> <span class="n">animation</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_tcs</span><span class="p">.</span><span class="nf">TrySetCanceled</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnAnimationEnd</span><span class="p">(</span><span class="n">Animator</span> <span class="n">animation</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_tcs</span><span class="p">.</span><span class="nf">TrySetResult</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnAnimationRepeat</span><span class="p">(</span><span class="n">Animator</span> <span class="n">animation</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnAnimationStart</span><span class="p">(</span><span class="n">Animator</span> <span class="n">animation</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><strong>Result</strong></p>
<p><img src="/img/android-animation.gif" /></p>
<p><small>Found a typo? Send me a pull request!</small></p>Sven-Michael StübeXamarin has done a great job on bringing async/await to animations on iOS (as Kerry shows on his blog) and on Xamarin.Forms. For my Fingerprint Plugin I was playing around with some animations on Android and I expected to find a similar animation extension - but I did not (If I’ve overseen something, tweet me). But, awaiting animations is pretty easy with a small extension method.Underlined Label Text in Xamarin.Forms2016-08-29T12:00:00+00:002016-08-29T12:00:00+00:00http://smstuebe.de/2016/08/29/underlinedlabel.xamarin.forms<p>I saw <a href="http://stackoverflow.com/q/39199975/1489968" target="_blank">a question on stackoverflow</a> and thought it would be a nice use case for Effects.</p>
<h2 class="section-heading">Problem</h2>
<p>The <a href="https://developer.xamarin.com/api/type/Xamarin.Forms.FontAttributes/" target="_blank">FontAttributes</a> contain only <code class="language-plaintext highlighter-rouge">None</code>, <code class="language-plaintext highlighter-rouge">Bold</code> and <code class="language-plaintext highlighter-rouge">Italic</code>. If you want to underline your text, you have to implement it on your own.</p>
<h2 class="section-heading">Effects</h2>
<p><a href="https://developer.xamarin.com/guides/xamarin-forms/effects/creating/">Xamarin.Forms Effects</a> allow you to customize the underlying native controls without writing custom renderers and custom controls. Creating your own effects is really easy if you follow the linked tutorial.</p>
<p><b>Core</b><br />
To be able to use the effect in you Xaml, you have to create a <code class="language-plaintext highlighter-rouge">RoutingEffect</code> in your Core project.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">class</span> <span class="nc">UnderlineEffect</span> <span class="p">:</span> <span class="n">RoutingEffect</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">EffectNamespace</span> <span class="p">=</span> <span class="s">"Example"</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">UnderlineEffect</span><span class="p">()</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">EffectNamespace</span><span class="p">}</span><span class="s">.</span><span class="p">{</span><span class="k">nameof</span><span class="p">(</span><span class="n">UnderlineEffect</span><span class="p">)}</span><span class="s">"</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>I introduced <code class="language-plaintext highlighter-rouge">EffectNamespace</code> to add some refactoring safety, because this namespace is used in the platform specific implementation, too.</p>
<p>Add the Effect to your Label:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><Label</span> <span class="na">Text=</span><span class="s">"Welcome to underlined Xamarin Forms!"</span>
<span class="na">VerticalOptions=</span><span class="s">"Center"</span>
<span class="na">HorizontalOptions=</span><span class="s">"Center"</span><span class="nt">></span>
<span class="nt"><Label.Effects></span>
<span class="nt"><local:UnderlineEffect</span> <span class="nt">/></span>
<span class="nt"></Label.Effects></span>
<span class="nt"></Label></span></code></pre></figure>
<p><b>Android</b><br /></p>
<p>On Android it’s pretty easy to underline the whole text. You just have to set the paint flag for it. You should listen to some property changes (e.g. <code class="language-plaintext highlighter-rouge">Text</code> and <code class="language-plaintext highlighter-rouge">FormattedText</code>), because the renderer might reset the paint flags. You should add the flag using the OR-operator in combination with the old flags, to keep the already set flags. When you remove the effect, you should remove via the inverse bit operation.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="p">[</span><span class="n">assembly</span><span class="p">:</span> <span class="nf">ResolutionGroupName</span><span class="p">(</span><span class="n">UnderlineLabel</span><span class="p">.</span><span class="n">UnderlineEffect</span><span class="p">.</span><span class="n">EffectNamespace</span><span class="p">)]</span>
<span class="p">[</span><span class="n">assembly</span><span class="p">:</span> <span class="nf">ExportEffect</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">UnderlineEffect</span><span class="p">),</span> <span class="k">nameof</span><span class="p">(</span><span class="n">UnderlineEffect</span><span class="p">))]</span>
<span class="k">namespace</span> <span class="nn">UnderlineLabel.Droid</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">UnderlineEffect</span> <span class="p">:</span> <span class="n">PlatformEffect</span>
<span class="p">{</span>
<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnAttached</span><span class="p">()</span>
<span class="p">{</span>
<span class="nf">SetUnderline</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnDetached</span><span class="p">()</span>
<span class="p">{</span>
<span class="nf">SetUnderline</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnElementPropertyChanged</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">ComponentModel</span><span class="p">.</span><span class="n">PropertyChangedEventArgs</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">base</span><span class="p">.</span><span class="nf">OnElementPropertyChanged</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">PropertyName</span> <span class="p">==</span> <span class="n">Label</span><span class="p">.</span><span class="n">TextProperty</span><span class="p">.</span><span class="n">PropertyName</span> <span class="p">||</span> <span class="n">args</span><span class="p">.</span><span class="n">PropertyName</span> <span class="p">==</span> <span class="n">Label</span><span class="p">.</span><span class="n">FormattedTextProperty</span><span class="p">.</span><span class="n">PropertyName</span><span class="p">)</span>
<span class="p">{</span>
<span class="nf">SetUnderline</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">SetUnderline</span><span class="p">(</span><span class="kt">bool</span> <span class="n">underlined</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">textView</span> <span class="p">=</span> <span class="p">(</span><span class="n">TextView</span><span class="p">)</span><span class="n">Control</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">underlined</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">textView</span><span class="p">.</span><span class="n">PaintFlags</span> <span class="p">|=</span> <span class="n">PaintFlags</span><span class="p">.</span><span class="n">UnderlineText</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">textView</span><span class="p">.</span><span class="n">PaintFlags</span> <span class="p">&=</span> <span class="p">~</span><span class="n">PaintFlags</span><span class="p">.</span><span class="n">UnderlineText</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"Cannot underline Label. Error: "</span><span class="p">,</span> <span class="n">ex</span><span class="p">.</span><span class="n">Message</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><b>iOS</b><br /></p>
<p>Underlining a Text on iOS is as easy as on Android. You have to add the <code class="language-plaintext highlighter-rouge">UnderlineStyle</code> attribute to the <code class="language-plaintext highlighter-rouge">AttributedText</code>.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="p">[</span><span class="n">assembly</span><span class="p">:</span> <span class="nf">ResolutionGroupName</span><span class="p">(</span><span class="n">UnderlineLabel</span><span class="p">.</span><span class="n">UnderlineEffect</span><span class="p">.</span><span class="n">EffectNamespace</span><span class="p">)]</span>
<span class="p">[</span><span class="n">assembly</span><span class="p">:</span> <span class="nf">ExportEffect</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">UnderlineEffect</span><span class="p">),</span> <span class="k">nameof</span><span class="p">(</span><span class="n">UnderlineEffect</span><span class="p">))]</span>
<span class="k">namespace</span> <span class="nn">UnderlineLabel.iOS</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">UnderlineEffect</span> <span class="p">:</span> <span class="n">PlatformEffect</span>
<span class="p">{</span>
<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnAttached</span><span class="p">()</span>
<span class="p">{</span>
<span class="nf">SetUnderline</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnDetached</span><span class="p">()</span>
<span class="p">{</span>
<span class="nf">SetUnderline</span><span class="p">(</span><span class="k">false</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnElementPropertyChanged</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">ComponentModel</span><span class="p">.</span><span class="n">PropertyChangedEventArgs</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">base</span><span class="p">.</span><span class="nf">OnElementPropertyChanged</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">PropertyName</span> <span class="p">==</span> <span class="n">Label</span><span class="p">.</span><span class="n">TextProperty</span><span class="p">.</span><span class="n">PropertyName</span> <span class="p">||</span> <span class="n">args</span><span class="p">.</span><span class="n">PropertyName</span> <span class="p">==</span> <span class="n">Label</span><span class="p">.</span><span class="n">FormattedTextProperty</span><span class="p">.</span><span class="n">PropertyName</span><span class="p">)</span>
<span class="p">{</span>
<span class="nf">SetUnderline</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">SetUnderline</span><span class="p">(</span><span class="kt">bool</span> <span class="n">underlined</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">label</span> <span class="p">=</span> <span class="p">(</span><span class="n">UILabel</span><span class="p">)</span><span class="n">Control</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">text</span> <span class="p">=</span> <span class="p">(</span><span class="n">NSMutableAttributedString</span><span class="p">)</span><span class="n">label</span><span class="p">.</span><span class="n">AttributedText</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">range</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">NSRange</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">text</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">underlined</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">text</span><span class="p">.</span><span class="nf">AddAttribute</span><span class="p">(</span><span class="n">UIStringAttributeKey</span><span class="p">.</span><span class="n">UnderlineStyle</span><span class="p">,</span> <span class="n">NSNumber</span><span class="p">.</span><span class="nf">FromInt32</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">NSUnderlineStyle</span><span class="p">.</span><span class="n">Single</span><span class="p">),</span> <span class="n">range</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">text</span><span class="p">.</span><span class="nf">RemoveAttribute</span><span class="p">(</span><span class="n">UIStringAttributeKey</span><span class="p">.</span><span class="n">UnderlineStyle</span><span class="p">,</span> <span class="n">range</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"Cannot underline Label. Error: "</span><span class="p">,</span> <span class="n">ex</span><span class="p">.</span><span class="n">Message</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 class="section-heading">Conclusion</h2>
<p>It’s very easy to customize native controls with Effects.<br />
Keep in mind, that this is a solution for Labels with simple text. If you want to underline just some parts of the Text using <code class="language-plaintext highlighter-rouge">FormattedText</code>, you might have a problem :) See my <a href="/2016/04/03/formattedtext.xamrin.forms/">post on FormattedText</a> for more information on the problem.</p>
<h2 class="section-heading">Code Sample</h2>
<p>The implementation of the Effect are pushed to a github project <br />(<i class="fa fa-github"></i><a href="https://github.com/smstuebe/stackoverflow-answers/tree/master/xamarin-forms-underline" target="_blank"> stackoverflow-answers/xamarin-forms-underline</a>). Feel free to use these if you want to play around or have a closer look to effects.</p>
<center>If you like the answer, I'd appreciate an upvote ;) <br /><a href="http://stackoverflow.com/a/39200923/1489968" target="_blank" title="Answer">http://stackoverflow.com/a/39200923/1489968</a><br />Thanks!</center>
<p><br />
<small><a href="https://flic.kr/p/fJNKB" title="Photo">Background Photo</a> by <a href="https://www.flickr.com/photos/56832361@N00/" title="Karen">Karen</a> / <a href="http://creativecommons.org/licenses/by/2.0/" title="CC BY">CC BY</a></small>
<br />
<small>Found a typo? Send me a pull request!</small></p>Sven-Michael StübeI saw a question on stackoverflow and thought it would be a nice use case for Effects.RecyclerView TemplateSelector in MvvmCross2016-06-12T00:00:00+00:002016-06-12T00:00:00+00:00http://smstuebe.de/2016/06/12/mvvmcross-recycler-templates<p>There is a old tutorial from Stuart Lodge (Polymorphic lists in the MvvmCross tutorials) where he explains how to show different table cells for different types of ViewModels. This post will show you how to do the same with a <code class="language-plaintext highlighter-rouge">MvxRecyclerView</code>. The example code is available on <i class="fa fa-github"></i><a href="https://github.com/smstuebe/mvvmcross-examples/tree/master/RecyclerViewDifferentTemplates" target="_blank">github</a>.</p>
<h3>Problem</h3>
<p>Sometimes there is the need to layout the items of RecyclerView differently. This is mostly the case, if your <code class="language-plaintext highlighter-rouge">ItemsSource</code> stores items of different classes.</p>
<p><strong>ViewModels</strong></p>
<p>For this example, we use the following ViewModels:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">class</span> <span class="nc">FirstViewModel</span> <span class="p">:</span> <span class="n">MvxViewModel</span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">PetViewModelBase</span><span class="p">></span> <span class="n">Pets</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="nf">FirstViewModel</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Pets</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ObservableCollection</span><span class="p"><</span><span class="n">PetViewModelBase</span><span class="p">></span>
<span class="p">{</span>
<span class="k">new</span> <span class="n">CatViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">10</span><span class="p">,</span> <span class="n">LifesLeft</span> <span class="p">=</span> <span class="m">6</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Paul"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">CatViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span> <span class="n">LifesLeft</span> <span class="p">=</span> <span class="m">9</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Horst"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">FishViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span> <span class="n">Fins</span> <span class="p">=</span> <span class="m">2</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Nemo"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">CatViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">16</span><span class="p">,</span> <span class="n">LifesLeft</span> <span class="p">=</span> <span class="m">4</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Erna"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">CatViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">5</span><span class="p">,</span> <span class="n">LifesLeft</span> <span class="p">=</span> <span class="m">6</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Nougat"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">FishViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">12</span><span class="p">,</span> <span class="n">Fins</span> <span class="p">=</span> <span class="m">2</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Marlin"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">FishViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">9</span><span class="p">,</span> <span class="n">Fins</span> <span class="p">=</span> <span class="m">5</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Sharky"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">CatViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">7</span><span class="p">,</span> <span class="n">LifesLeft</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Tirebiter"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">CatViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">8</span><span class="p">,</span> <span class="n">LifesLeft</span> <span class="p">=</span> <span class="m">6</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Moritz"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">FishViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">2</span><span class="p">,</span> <span class="n">Fins</span> <span class="p">=</span> <span class="m">5</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Barsch"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">FishViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">4</span><span class="p">,</span> <span class="n">Fins</span> <span class="p">=</span> <span class="m">20</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Finny"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">FishViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">8</span><span class="p">,</span> <span class="n">Fins</span> <span class="p">=</span> <span class="m">3</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Klaus"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">FishViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span> <span class="n">Fins</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Pirat"</span><span class="p">},</span>
<span class="k">new</span> <span class="n">CatViewModel</span> <span class="p">{</span><span class="n">Age</span> <span class="p">=</span> <span class="m">3</span><span class="p">,</span> <span class="n">LifesLeft</span> <span class="p">=</span> <span class="m">9</span><span class="p">,</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Mickey"</span><span class="p">}</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Items </span>
<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">PetViewModelBase</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Age</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">CatViewModel</span> <span class="p">:</span> <span class="n">PetViewModelBase</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">LifesLeft</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">FishViewModel</span> <span class="p">:</span> <span class="n">PetViewModelBase</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">Fins</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3>Solution</h3>
<p>Luckily MvvmCorss includes a neat mechanism for this since version <strong>4.1.5</strong>. It’s called <code class="language-plaintext highlighter-rouge">TemplateSelector</code> and defined in the interface <code class="language-plaintext highlighter-rouge">IMvxTemplateSelector</code>. The purpose of this interface is to map your item to a layout id based on a rule that you can define.</p>
<p><strong>Selecting by ViewModel type</strong></p>
<p>In this scenario, we want to show different layouts for cats (<code class="language-plaintext highlighter-rouge">CatViewModel</code>) and fishes (<code class="language-plaintext highlighter-rouge">FishViewModel</code>).</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">class</span> <span class="nc">TypeTemplateSelector</span> <span class="p">:</span> <span class="n">IMvxTemplateSelector</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p"><</span><span class="n">Type</span><span class="p">,</span> <span class="kt">int</span><span class="p">></span> <span class="n">_typeMapping</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">TypeTemplateSelector</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_typeMapping</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p"><</span><span class="n">Type</span><span class="p">,</span> <span class="kt">int</span><span class="p">></span>
<span class="p">{</span>
<span class="p">{</span><span class="k">typeof</span><span class="p">(</span><span class="n">CatViewModel</span><span class="p">),</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Layout</span><span class="p">.</span><span class="n">pet_cat</span><span class="p">},</span>
<span class="p">{</span><span class="k">typeof</span><span class="p">(</span><span class="n">FishViewModel</span><span class="p">),</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Layout</span><span class="p">.</span><span class="n">pet_fish</span><span class="p">}</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">int</span> <span class="nf">GetItemViewType</span><span class="p">(</span><span class="kt">object</span> <span class="n">forItemObject</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">_typeMapping</span><span class="p">[</span><span class="n">forItemObject</span><span class="p">.</span><span class="nf">GetType</span><span class="p">()];</span>
<span class="p">}</span>
<span class="k">public</span> <span class="kt">int</span> <span class="nf">GetItemLayoutId</span><span class="p">(</span><span class="kt">int</span> <span class="n">fromViewType</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">fromViewType</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Therefore we create a lookup table <code class="language-plaintext highlighter-rouge">_typeMapping</code> and use it in <code class="language-plaintext highlighter-rouge">GetItemViewType</code> that is called for each item of the list. The parameter <code class="language-plaintext highlighter-rouge">forItemObject</code> is the current item for which we should return the view type. We return the layout id directly, because we have one layout id per view type. If you want to know more about the purpose of view types have a look at: <a href="https://developer.android.com/reference/android/widget/Adapter.html#getItemViewType(int)" target="_blank">Adapter.getItemViewType(int)</a>.</p>
<p><strong>Selecting by a property value</strong></p>
<p>In our second scenario we want to show different layouts for old, adult and young pets using the Age property. We have a common base class and there is a generic base class <code class="language-plaintext highlighter-rouge">MvxTemplateSelector<T></code> that allows us to implement <code class="language-plaintext highlighter-rouge">IMvxTemplateSelector</code> without casting manually.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">class</span> <span class="nc">AgeTemplateSelector</span> <span class="p">:</span> <span class="n">MvxTemplateSelector</span><span class="p"><</span><span class="n">PetViewModelBase</span><span class="p">></span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">override</span> <span class="kt">int</span> <span class="nf">GetItemLayoutId</span><span class="p">(</span><span class="kt">int</span> <span class="n">fromViewType</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">fromViewType</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">protected</span> <span class="k">override</span> <span class="kt">int</span> <span class="nf">SelectItemViewType</span><span class="p">(</span><span class="n">PetViewModelBase</span> <span class="n">forItemObject</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">forItemObject</span><span class="p">.</span><span class="n">Age</span> <span class="p"><=</span> <span class="m">1</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Layout</span><span class="p">.</span><span class="n">pet_age_young</span><span class="p">;</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">forItemObject</span><span class="p">.</span><span class="n">Age</span> <span class="p"><=</span> <span class="m">10</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Layout</span><span class="p">.</span><span class="n">pet_age_adult</span><span class="p">;</span>
<span class="k">return</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Layout</span><span class="p">.</span><span class="n">pet_age_old</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>This time we override <code class="language-plaintext highlighter-rouge">SelectItemViewType</code> that is called with a <code class="language-plaintext highlighter-rouge">PetViewModelBase</code> as parameter. We return the layout id directly as view type again. You can use the <code class="language-plaintext highlighter-rouge">MvxTemplateSelector<T></code> in the first scenario, too.</p>
<p><strong>Setting the Selector</strong></p>
<p>You can set the selector in your code behind:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnCreate</span><span class="p">(</span><span class="n">Bundle</span> <span class="n">bundle</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">base</span><span class="p">.</span><span class="nf">OnCreate</span><span class="p">(</span><span class="n">bundle</span><span class="p">);</span>
<span class="nf">SetContentView</span><span class="p">(</span><span class="n">Resource</span><span class="p">.</span><span class="n">Layout</span><span class="p">.</span><span class="n">FirstView</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">petList</span> <span class="p">=</span> <span class="n">FindViewById</span><span class="p"><</span><span class="n">MvxRecyclerView</span><span class="p">>(</span><span class="n">Resource</span><span class="p">.</span><span class="n">Id</span><span class="p">.</span><span class="n">recyclerView</span><span class="p">);</span>
<span class="n">petList</span><span class="p">.</span><span class="n">ItemTemplateSelector</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">TypeTemplateSelector</span><span class="p">();</span>
<span class="p">}</span></code></pre></figure>
<p>And you can set it in the xml. But this code is not as refactoring safe as the code behind version and you can’t initialize the selector if you create a configurable one. The attribute is called <code class="language-plaintext highlighter-rouge">MvxTemplateSelector</code> and has to be set with the full qualified class name of the selector followed by the assembly name.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView</span>
<span class="na">android:id=</span><span class="s">"@+id/recyclerView"</span>
<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"0dp"</span>
<span class="na">android:layout_weight=</span><span class="s">"1"</span>
<span class="na">local:MvxBind=</span><span class="s">"ItemsSource Pets"</span>
<span class="na">local:MvxTemplateSelector=</span><span class="s">"RecyclerViewDifferentTemplates.Droid.TypeTemplateSelector,RecyclerViewDifferentTemplates.Droid"</span><span class="nt">></span>
<span class="nt"></MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView></span></code></pre></figure>
<p>It is possible to exchange the selector at the runtime as well. The items will update immediately. In the sample the two buttons <code class="language-plaintext highlighter-rouge">Type</code> and <code class="language-plaintext highlighter-rouge">Age</code> are switching between our two custom selectors. Just give it a try.</p>
<h3>Result</h3>
<p>The Result looks like:</p>
<p><img src="/img/polyrecyclerresult.png" /></p>
<p><br />
<small><a href="https://flic.kr/p/9TRC1f" title="Photo">Background Photo</a> by <a href="https://www.flickr.com/photos/andyarthur/" title="Andy Arthur">Andy Arthur</a> / <a href="http://creativecommons.org/licenses/by/2.0/" title="CC BY">CC BY</a></small>
<br /></p>
<p><small>Found a typo? Send me a pull request!</small></p>Sven-Michael StübeThere is a old tutorial from Stuart Lodge (Polymorphic lists in the MvvmCross tutorials) where he explains how to show different table cells for different types of ViewModels. This post will show you how to do the same with a MvxRecyclerView. The example code is available on github.