wislon.io2024-03-16T00:09:25+00:00http://blog.wislon.iowislon.ioXamarin Forms - Change a Switch's True/False Colour2017-05-15T00:00:00+00:00http://blog.wislon.io/posts/2017/05/15/xamforms-change-switch-colour<p>In a similar vein to <a href="http://wislon.io/posts/2017/04/11/xamforms-listview-selected-colour">my previous post</a>, I recently had to set the colour for a Xamarin Forms <code class="language-plaintext highlighter-rouge">Switch</code>’s true/false colour to something other than the default, on Android. The colour scheme for the app meant that on Android, the default <code class="language-plaintext highlighter-rouge">false</code> colour caused the Switch’s track to blend into the background, making it look like a circle floating in the middle of nowhere:</p>
<p><img class="img-responsive img-thumbnail inline" alt="Android - before implementation" title="Android - before implementation" src="https://github.com/wislon/xfdemos/blob/master/screenshots/customcontrols/switch-before-droid.gif?raw=true" /></p>
<p>iOS, of course, was fine:</p>
<p><img class="img-responsive img-thumbnail inline" alt="iOS - before implementation" title="iOS - before implementation" src="https://github.com/wislon/xfdemos/blob/master/screenshots/customcontrols/switch-before-ios.gif?raw=true" /></p>
<h3 id="switching-colours-using-an-effect">Switching Colours Using an Effect</h3>
<p>Rather than use a custom renderer for this one, I thought it could better be handled as an effect, and a bit of googling and stack-overflowing bore this out. There’s some very good examples of effects at the <a href="https://github.com/FormsCommunityToolkit/Effects">Xamarin Forms Community Toolkit</a>.</p>
<p>The switch-colour effect example they have there got me close, but wasn’t quite enough to seal the deal, but I’ll get to that shortly.</p>
<p>The Xamarin Forms switch-colour effect is pretty simple. It uses a basic Effect ‘skeleton’ and adds three bindable properties to it, a <code class="language-plaintext highlighter-rouge">TrueColorProperty</code>, a <code class="language-plaintext highlighter-rouge">FalseColorProperty</code> and a <code class="language-plaintext highlighter-rouge">ThumbColorProperty</code>. The last one enables us to set the colour of the actual ‘thumb-slider’ component of the switch control, whereas the <code class="language-plaintext highlighter-rouge">true</code> and <code class="language-plaintext highlighter-rouge">false</code> colours are for the ‘track’ within which the slider sits.</p>
<p>Here’s the code for the Xamarin Forms abstract <code class="language-plaintext highlighter-rouge">ChangeColorSwitchEffect</code>. Nothing special, just those bindable properties, some getters and setters for them, and attachment/detachment code for when the <code class="language-plaintext highlighter-rouge">Switch</code> control is bound at runtime.</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">ChangeColorSwitchEffect</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">BindableProperty</span> <span class="n">FalseColorProperty</span> <span class="p">=</span> <span class="n">BindableProperty</span><span class="p">.</span><span class="nf">CreateAttached</span><span class="p">(</span><span class="s">"FalseColor"</span><span class="p">,</span>
<span class="k">typeof</span><span class="p">(</span><span class="n">Color</span><span class="p">),</span>
<span class="k">typeof</span><span class="p">(</span><span class="n">ChangeColorSwitchEffect</span><span class="p">),</span>
<span class="n">Color</span><span class="p">.</span><span class="n">Transparent</span><span class="p">,</span>
<span class="n">propertyChanged</span><span class="p">:</span> <span class="n">OnColorChanged</span><span class="p">);</span>
<span class="c1">// removed TrueColorProperty and ThumbColorProperty declarations
</span>
<span class="k">private</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">OnColorChanged</span><span class="p">(</span><span class="n">BindableObject</span> <span class="n">bindable</span><span class="p">,</span> <span class="kt">object</span> <span class="n">oldValue</span><span class="p">,</span> <span class="kt">object</span> <span class="n">newValue</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">control</span> <span class="p">=</span> <span class="n">bindable</span> <span class="k">as</span> <span class="n">Switch</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">control</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
<span class="k">return</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">color</span> <span class="p">=</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="n">newValue</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">attachedEffect</span> <span class="p">=</span> <span class="n">control</span><span class="p">.</span><span class="n">Effects</span><span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">e</span> <span class="p">=></span> <span class="n">e</span> <span class="k">is</span> <span class="n">ChangeColorSwitchEffect</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">color</span> <span class="p">!=</span> <span class="n">Color</span><span class="p">.</span><span class="n">Transparent</span> <span class="p">&&</span> <span class="n">attachedEffect</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
<span class="n">control</span><span class="p">.</span><span class="n">Effects</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">ChangeColorSwitchEffect</span><span class="p">());</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">color</span> <span class="p">==</span> <span class="n">Color</span><span class="p">.</span><span class="n">Transparent</span> <span class="p">&&</span> <span class="n">attachedEffect</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="n">control</span><span class="p">.</span><span class="n">Effects</span><span class="p">.</span><span class="nf">Remove</span><span class="p">(</span><span class="n">attachedEffect</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">static</span> <span class="n">Color</span> <span class="nf">GetFalseColor</span><span class="p">(</span><span class="n">BindableObject</span> <span class="n">view</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span> <span class="n">view</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">FalseColorProperty</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">SetFalseColor</span><span class="p">(</span><span class="n">BindableObject</span> <span class="n">view</span><span class="p">,</span> <span class="kt">string</span> <span class="n">color</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">view</span><span class="p">.</span><span class="nf">SetValue</span><span class="p">(</span><span class="n">FalseColorProperty</span><span class="p">,</span> <span class="n">color</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// removed Get/SetTrueColor method
</span>
<span class="c1">// removed Get/SetThumbColor method
</span>
<span class="p">}</span></code></pre></figure>
<p><em>For the sake of brevity, I’ve removed some of the duplicate-looking stuff. The fully working example is linked at the end of this post.</em></p>
<h4 id="xaml-implementation">XAML Implementation</h4>
<p>Now just define a switch as you normally would, and then add values for the BindableProperties you defined above:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><StackLayout</span> <span class="na">HorizontalOptions=</span><span class="s">"FillAndExpand"</span> <span class="na">VerticalOptions=</span><span class="s">"FillAndExpand"</span>
<span class="na">Orientation=</span><span class="s">"Vertical"</span>
<span class="na">Padding=</span><span class="s">"16"</span> <span class="na">Spacing=</span><span class="s">"32"</span><span class="nt">></span>
<span class="nt"><StackLayout</span> <span class="na">VerticalOptions=</span><span class="s">"StartAndExpand"</span>
<span class="na">HorizontalOptions=</span><span class="s">"FillAndExpand"</span>
<span class="na">Orientation=</span><span class="s">"Horizontal"</span>
<span class="na">Padding=</span><span class="s">"0"</span> <span class="na">Spacing=</span><span class="s">"16"</span><span class="nt">></span>
<span class="nt"><Label</span> <span class="na">TextColor=</span><span class="s">"Silver"</span>
<span class="na">Text=</span><span class="s">"Coloured Switch Example"</span>
<span class="na">HorizontalOptions=</span><span class="s">"StartAndExpand"</span>
<span class="na">HorizontalTextAlignment=</span><span class="s">"Start"</span> <span class="nt">/></span>
<span class="nt"><Switch</span> <span class="na">VerticalOptions=</span><span class="s">"StartAndExpand"</span>
<span class="na">HorizontalOptions=</span><span class="s">"EndAndExpand"</span>
<span class="na">IsToggled=</span><span class="s">"False"</span>
<span class="na">effects:ChangeColorSwitchEffect.TrueColor=</span><span class="s">"Lime"</span>
<span class="na">effects:ChangeColorSwitchEffect.FalseColor=</span><span class="s">"Aqua"</span>
<span class="na">effects:ChangeColorSwitchEffect.ThumbColor=</span><span class="s">"Blue"</span>
<span class="nt">/></span>
<span class="nt"></StackLayout></span></code></pre></figure>
<p>Those colours will be a little garish. But as you’ll see, they make it pretty clear what we’ve done in the platform-specific effect implementations. So let’s have a look at those next.</p>
<h4 id="android-implementation">Android Implementation</h4>
<p><em>I’ve left out some of the usings declarations and the effect-export stuff to keep this shorter.</em></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">ChangeColorSwitchEffect</span> <span class="p">:</span> <span class="n">PlatformEffect</span>
<span class="p">{</span>
<span class="k">private</span> <span class="n">Color</span> <span class="n">_trueColor</span><span class="p">;</span>
<span class="k">private</span> <span class="n">Color</span> <span class="n">_falseColor</span><span class="p">;</span>
<span class="k">private</span> <span class="n">Color</span> <span class="n">_thumbColor</span><span class="p">;</span>
<span class="c1">// tracks are slightly darker for some 'virtual depth'
</span>
<span class="k">private</span> <span class="n">Color</span> <span class="n">_falseColorDarker</span><span class="p">;</span>
<span class="k">private</span> <span class="n">Color</span> <span class="n">_trueColorDarker</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="k">if</span> <span class="p">(</span><span class="n">Android</span><span class="p">.</span><span class="n">OS</span><span class="p">.</span><span class="n">Build</span><span class="p">.</span><span class="n">VERSION</span><span class="p">.</span><span class="n">SdkInt</span> <span class="p">>=</span> <span class="n">Android</span><span class="p">.</span><span class="n">OS</span><span class="p">.</span><span class="n">BuildVersionCodes</span><span class="p">.</span><span class="n">JellyBean</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_thumbColor</span> <span class="p">=</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span><span class="n">Element</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">XECCSwitchEffect</span><span class="p">.</span><span class="n">ThumbColorProperty</span><span class="p">);</span>
<span class="n">_trueColor</span> <span class="p">=</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span><span class="n">Element</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">XECCSwitchEffect</span><span class="p">.</span><span class="n">TrueColorProperty</span><span class="p">);</span>
<span class="n">_falseColor</span> <span class="p">=</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span><span class="n">Element</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">XECCSwitchEffect</span><span class="p">.</span><span class="n">FalseColorProperty</span><span class="p">);</span>
<span class="n">_falseColorDarker</span> <span class="p">=</span> <span class="n">_falseColor</span><span class="p">.</span><span class="nf">AddLuminosity</span><span class="p">(-</span><span class="m">0.25</span><span class="p">);</span>
<span class="n">_trueColorDarker</span> <span class="p">=</span> <span class="n">_trueColor</span><span class="p">.</span><span class="nf">AddLuminosity</span><span class="p">(-</span><span class="m">0.25</span><span class="p">);</span>
<span class="p">((</span><span class="n">SwitchCompat</span><span class="p">)</span><span class="n">Control</span><span class="p">).</span><span class="n">CheckedChange</span> <span class="p">+=</span> <span class="n">OnCheckedChange</span><span class="p">;</span>
<span class="p">((</span><span class="n">SwitchCompat</span><span class="p">)</span><span class="n">Control</span><span class="p">).</span><span class="n">TrackDrawable</span><span class="p">.</span><span class="nf">SetColorFilter</span><span class="p">(</span><span class="n">_falseColorDarker</span><span class="p">.</span><span class="nf">ToAndroid</span><span class="p">(),</span>
<span class="n">PorterDuff</span><span class="p">.</span><span class="n">Mode</span><span class="p">.</span><span class="n">Multiply</span><span class="p">);</span>
<span class="p">((</span><span class="n">SwitchCompat</span><span class="p">)</span><span class="n">Control</span><span class="p">).</span><span class="n">ThumbDrawable</span><span class="p">.</span><span class="nf">SetColorFilter</span><span class="p">(</span><span class="n">_thumbColor</span><span class="p">.</span><span class="nf">ToAndroid</span><span class="p">(),</span>
<span class="n">PorterDuff</span><span class="p">.</span><span class="n">Mode</span><span class="p">.</span><span class="n">Multiply</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">OnCheckedChange</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">CompoundButton</span><span class="p">.</span><span class="n">CheckedChangeEventArgs</span> <span class="n">checkedChangeEventArgs</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">checkedChangeEventArgs</span><span class="p">.</span><span class="n">IsChecked</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">((</span><span class="n">SwitchCompat</span><span class="p">)</span><span class="n">Control</span><span class="p">).</span><span class="n">TrackDrawable</span><span class="p">.</span><span class="nf">SetColorFilter</span><span class="p">(</span><span class="n">_trueColorDarker</span><span class="p">.</span><span class="nf">ToAndroid</span><span class="p">(),</span>
<span class="n">PorterDuff</span><span class="p">.</span><span class="n">Mode</span><span class="p">.</span><span class="n">Multiply</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="p">((</span><span class="n">SwitchCompat</span><span class="p">)</span><span class="n">Control</span><span class="p">).</span><span class="n">TrackDrawable</span><span class="p">.</span><span class="nf">SetColorFilter</span><span class="p">(</span><span class="n">_falseColorDarker</span><span class="p">.</span><span class="nf">ToAndroid</span><span class="p">(),</span>
<span class="n">PorterDuff</span><span class="p">.</span><span class="n">Mode</span><span class="p">.</span><span class="n">Multiply</span><span class="p">);</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="k">if</span> <span class="p">(</span><span class="n">Android</span><span class="p">.</span><span class="n">OS</span><span class="p">.</span><span class="n">Build</span><span class="p">.</span><span class="n">VERSION</span><span class="p">.</span><span class="n">SdkInt</span> <span class="p">>=</span> <span class="n">Android</span><span class="p">.</span><span class="n">OS</span><span class="p">.</span><span class="n">BuildVersionCodes</span><span class="p">.</span><span class="n">JellyBean</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">((</span><span class="n">Switch</span><span class="p">)</span><span class="n">Control</span><span class="p">).</span><span class="n">CheckedChange</span> <span class="p">-=</span> <span class="n">OnCheckedChange</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><strong>A couple of points:</strong></p>
<ul>
<li>On Android, if you make the track and thumb colours the same, it doesn’t come out quite like you expect. It looks flat and undifferentiated, because the track has no ‘depth’ beneath the thumb-slider. So I’ve made the track a little darker than the thumb slider by reducing the luminosity (‘brightness’) by 25%.</li>
<li>The example project shows a few extra things you can do with the the thumb-colour, for instance changing the thumb-colour depending on the state of the switch.</li>
<li>Yes, I know I could have used the newer <code class="language-plaintext highlighter-rouge">State</code> array stuff to set tint/hint colours on Android, but this is backwardly compatible, AND enables one to change the colour of the thumb-slider based on the switch-state.</li>
</ul>
<h4 id="ios-implementation">iOS Implementation</h4>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">class</span> <span class="nc">ChangeColorSwitchEffect</span> <span class="p">:</span> <span class="n">PlatformEffect</span>
<span class="p">{</span>
<span class="k">private</span> <span class="n">Color</span> <span class="n">_trueColor</span><span class="p">;</span>
<span class="k">private</span> <span class="n">Color</span> <span class="n">_falseColor</span><span class="p">;</span>
<span class="k">private</span> <span class="n">Color</span> <span class="n">_thumbColor</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="n">_trueColor</span> <span class="p">=</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span><span class="n">Element</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">XECCSwitchEffect</span><span class="p">.</span><span class="n">TrueColorProperty</span><span class="p">);</span>
<span class="n">_falseColor</span> <span class="p">=</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span><span class="n">Element</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">XECCSwitchEffect</span><span class="p">.</span><span class="n">FalseColorProperty</span><span class="p">);</span>
<span class="n">_thumbColor</span> <span class="p">=</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span><span class="n">Element</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">XECCSwitchEffect</span><span class="p">.</span><span class="n">ThumbColorProperty</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">switchControl</span> <span class="p">=</span> <span class="n">Control</span> <span class="k">as</span> <span class="n">UISwitch</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">switchControl</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">switchControl</span><span class="p">.</span><span class="n">TintColor</span> <span class="p">=</span> <span class="n">UIColor</span><span class="p">.</span><span class="nf">FromRGBA</span><span class="p">((</span><span class="n">nfloat</span><span class="p">)</span><span class="n">_falseColor</span><span class="p">.</span><span class="n">R</span><span class="p">,</span>
<span class="p">(</span><span class="n">nfloat</span><span class="p">)</span><span class="n">_falseColor</span><span class="p">.</span><span class="n">G</span><span class="p">,</span>
<span class="p">(</span><span class="n">nfloat</span><span class="p">)</span><span class="n">_falseColor</span><span class="p">.</span><span class="n">B</span><span class="p">,</span>
<span class="m">0.75f</span><span class="p">);</span>
<span class="c1">// see example code for caveat about changing background colour...
</span>
<span class="n">switchControl</span><span class="p">.</span><span class="n">OnTintColor</span> <span class="p">=</span> <span class="n">UIColor</span><span class="p">.</span><span class="nf">FromRGBA</span><span class="p">((</span><span class="n">nfloat</span><span class="p">)</span><span class="n">_trueColor</span><span class="p">.</span><span class="n">R</span><span class="p">,</span>
<span class="p">(</span><span class="n">nfloat</span><span class="p">)</span><span class="n">_trueColor</span><span class="p">.</span><span class="n">G</span><span class="p">,</span>
<span class="p">(</span><span class="n">nfloat</span><span class="p">)</span><span class="n">_trueColor</span><span class="p">.</span><span class="n">B</span><span class="p">,</span>
<span class="m">0.75f</span><span class="p">);</span>
<span class="n">switchControl</span><span class="p">.</span><span class="n">ThumbTintColor</span> <span class="p">=</span> <span class="n">UIColor</span><span class="p">.</span><span class="nf">FromRGBA</span><span class="p">((</span><span class="n">nfloat</span><span class="p">)</span><span class="n">_thumbColor</span><span class="p">.</span><span class="n">R</span><span class="p">,</span>
<span class="p">(</span><span class="n">nfloat</span><span class="p">)</span><span class="n">_thumbColor</span><span class="p">.</span><span class="n">G</span><span class="p">,</span>
<span class="p">(</span><span class="n">nfloat</span><span class="p">)</span><span class="n">_thumbColor</span><span class="p">.</span><span class="n">B</span><span class="p">,</span>
<span class="m">1.0f</span><span class="p">);</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="p">}</span>
<span class="p">}</span></code></pre></figure>
<ul>
<li>If you want to change the background colour of the switch, then you need to set a corner-radius on the UISwitch layout, or it draws the component with squared-off corners. See the example code for how to do this.</li>
<li>Unfortunately on iOS it’s not easy to change the thumb-slider colour depending on the switch-state</li>
<li>Similarly to Android, I made the track colours (<code class="language-plaintext highlighter-rouge">TintColor</code> is ‘off’ or ‘false’, <code class="language-plaintext highlighter-rouge">OnTintColor</code> is ‘on’ or ‘true’) a bit dimmer, by 25%. <em>One wonders why they didn’t call it <code class="language-plaintext highlighter-rouge">OffTintColor</code>…</em></li>
</ul>
<p>So how does it look?</p>
<h4 id="on-android">On Android</h4>
<p><img class="img-responsive img-thumbnail inline" alt="Android - after implementation" title="Android - after implementation" src="https://github.com/wislon/xfdemos/blob/master/screenshots/customcontrols/switch-after-droid.gif?raw=true" /></p>
<h4 id="on-ios">On iOS</h4>
<p><img class="img-responsive img-thumbnail inline" alt="iOS - after implementation" title="iOS - after implementation" src="https://github.com/wislon/xfdemos/blob/master/screenshots/customcontrols/switch-after-ios.gif?raw=true" /></p>
<p>There’s a simple Xamarin Forms project <a href="https://github.com/wislon/xfdemos">over in GitHub</a> so you can try it out for yourself. As usual it’s published under the MIT licence, so you can do what you like with the code. Hopefully it can save you some time, or get you where you need to be ;)</p>
Xamarin Forms - Change a ListView's Selected Item Colour2017-04-11T00:00:00+00:00http://blog.wislon.io/posts/2017/04/11/xamforms-listview-selected-colour<h3 id="update-2018-10-02">(Update 2018-10-02)</h3>
<p><em>Hey folks, I’m sorry but the latest versions of Xamarin Forms (> 2.5 I think) seem to have broken everything around this, and this solution no longer works. There’s a little more information in the comments below, along with some work I did, specifically: <a href="http://disq.us/p/1srl4w8">http://disq.us/p/1srl4w8</a>, but that’s as far as I managed to get.</em></p>
<p>I recently had to set the colour for a Xamarin Forms ListView’s <code class="language-plaintext highlighter-rouge">SelectedItem</code> to something other than the default. This was primarily because the colour scheme for the app I was building on iOS didn’t play nice with the selected-item highlight when it was tapped.</p>
<p>This was actually a little bit more work than I thought it would be (since there’s no ‘SelectedItemColor’ property).</p>
<p>ListView items are based on <code class="language-plaintext highlighter-rouge">Cell</code>-derived classes like <code class="language-plaintext highlighter-rouge">ViewCell</code> and <code class="language-plaintext highlighter-rouge">TextCell</code>, and so it’s not actually the ListView’s responsibility to handle the colour change. It’s up to the Cell-based object to do that itself. I guess they could have made it a little easier by creating a ‘SelectedBackgroundColour’ (or similar) property. But given how simple it is to implement, it’s actually not that big a deal.</p>
<p>The important thing to take away from this is that it’s NOT the ListView you’re modifying, it’s the Cell-based object (embedded in the ListView item’s <code class="language-plaintext highlighter-rouge">DataTemplate</code>) which needs to change. The ListView will happily pass on the fact that something has been (un)selected, but leaves it up to the developer to handle what happens when it is.</p>
<p>There was a quick and dirty <a href="http://stackoverflow.com/a/38457882/1135847">Android-only</a> solution, which was to set a couple of styles in the Android <code class="language-plaintext highlighter-rouge">Resources</code> folder, but (a) this was an Android-only solution (and I need it to work on iOS as well), and (b) by setting the style of pressed and highlighted items, it changed it for ALL controls which used that style. Suddenly ALL my (Android) Xamarin Forms ListViews had the same selected colour, and everything else I touched, tapped or long-pressed had that colour too.</p>
<p>There’s a couple of other examples in that thread which show how to react to the <code class="language-plaintext highlighter-rouge">ItemTapped</code> and/or <code class="language-plaintext highlighter-rouge">ItemSelected</code> events. But they struck me as a little clunky, mainly because they’d have to be done manually and individually for every ListView.</p>
<p>However, a little bit more digging through that thread led me <a href="http://stackoverflow.com/a/41754721/1135847">to an example</a> using a custom renderer and a <code class="language-plaintext highlighter-rouge">CustomTextCell</code>.</p>
<p>Since all I needed to do was change the background colour of the <code class="language-plaintext highlighter-rouge">ViewCell</code> in my ListView when it was selected, and then reset it back once it was deselected, it was pretty painless to port it over.</p>
<p>Here’s the code:</p>
<h3 id="original-listview-item-datatemplate">Original ListView Item DataTemplate</h3>
<p>Before changes, it looked something like this (bog-standard ViewCell, nothing special. However the ListView it’s contained in had to have a background colour of <code class="language-plaintext highlighter-rouge">Black</code> because reasons):</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><ListView.ItemTemplate></span>
<span class="nt"><DataTemplate></span>
<span class="nt"><ViewCell></span>
<span class="nt"><ViewCell.View></span>
<span class="nt"><StackLayout</span> <span class="na">HorizontalOptions=</span><span class="s">"FillAndExpand"</span>
<span class="na">VerticalOptions=</span><span class="s">"FillAndExpand"</span>
<span class="na">Orientation=</span><span class="s">"Vertical"</span>
<span class="na">Padding=</span><span class="s">"4"</span> <span class="na">Spacing=</span><span class="s">"8"</span><span class="nt">></span>
<span class="nt"><Label</span> <span class="na">TextColor=</span><span class="s">"White"</span> <span class="na">Text=</span><span class="s">"{Binding .ItemName}"</span><span class="nt">/></span>
<span class="nt"><Label</span> <span class="na">TextColor=</span><span class="s">"Yellow"</span> <span class="na">Text=</span><span class="s">"{Binding .LastUpdated, StringFormat='Last seen: {0:HH:mm:ss}'}"</span><span class="nt">/></span>
<span class="nt"></StackLayout></span>
<span class="nt"></ViewCell.View></span>
<span class="nt"></ViewCell></span>
<span class="nt"></DataTemplate></span>
<span class="nt"></ListView.ItemTemplate></span>
<span class="nt"></ListView></span></code></pre></figure>
<p>Note that on iOS, you can’t tell when an item is selected…</p>
<p><img class="img-responsive img-thumbnail inline" alt="iOS - before implementation" title="iOS - before implementation" src="https://github.com/wislon/xfdemos/blob/master/screenshots/listviewcolor/ios-before.gif?raw=true" /></p>
<p>On Android, you can, but the shocking orange default colour is a bit offensive…</p>
<p><img class="img-responsive img-thumbnail inline" alt="Android - before implementation" title="Android - before implementation" src="https://github.com/wislon/xfdemos/blob/master/screenshots/listviewcolor/droid-before.gif?raw=true" /></p>
<p>Let’s fix it with a custom renderer.</p>
<h3 id="custom-viewcell-xaml-placeholder-control">Custom ViewCell XAML ‘placeholder’ control</h3>
<p>This guy just inherits directly from a <code class="language-plaintext highlighter-rouge">ViewCell</code> and adds a single <code class="language-plaintext highlighter-rouge">BindableProperty</code> so we can hook up the colour we’d like to use when the item is selected.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">using</span> <span class="nn">Xamarin.Forms</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">xamformsdemo.CustomControls</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ExtendedViewCell</span> <span class="p">:</span> <span class="n">ViewCell</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">BindableProperty</span> <span class="n">SelectedBackgroundColorProperty</span> <span class="p">=</span>
<span class="n">BindableProperty</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="s">"SelectedBackgroundColor"</span><span class="p">,</span>
<span class="k">typeof</span><span class="p">(</span><span class="n">Color</span><span class="p">),</span>
<span class="k">typeof</span><span class="p">(</span><span class="n">ExtendedViewCell</span><span class="p">),</span>
<span class="n">Color</span><span class="p">.</span><span class="n">Default</span><span class="p">);</span>
<span class="k">public</span> <span class="n">Color</span> <span class="n">SelectedBackgroundColor</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="n">Color</span><span class="p">)</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">SelectedBackgroundColorProperty</span><span class="p">);</span> <span class="p">}</span>
<span class="k">set</span> <span class="p">{</span> <span class="nf">SetValue</span><span class="p">(</span><span class="n">SelectedBackgroundColorProperty</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="android-custom-renderer">Android custom renderer</h3>
<p>As the cell is rendered, this one captures the current (unselected, default theme) colour of the Android view cell, and then sets the colour of the view-cell’s background whenever the <code class="language-plaintext highlighter-rouge">IsSelected</code> property of the Android view-cell changes from false to true. And then resets it to the originally captured colour when when it flips back.</p>
<p><em>Obviously this may get a little more complex if you need to select multiple items in your list, but this is as far as I needed to go for my solution.</em></p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="p">[</span><span class="n">assembly</span><span class="p">:</span> <span class="nf">ExportRenderer</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">ExtendedViewCell</span><span class="p">),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">ExtendedViewCellRenderer</span><span class="p">))]</span>
<span class="k">namespace</span> <span class="nn">xamformsdemo.Droid.CustomRenderers</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ExtendedViewCellRenderer</span> <span class="p">:</span> <span class="n">ViewCellRenderer</span>
<span class="p">{</span>
<span class="k">private</span> <span class="n">Android</span><span class="p">.</span><span class="n">Views</span><span class="p">.</span><span class="n">View</span> <span class="n">_cellCore</span><span class="p">;</span>
<span class="k">private</span> <span class="n">Drawable</span> <span class="n">_unselectedBackground</span><span class="p">;</span>
<span class="k">private</span> <span class="kt">bool</span> <span class="n">_selected</span><span class="p">;</span>
<span class="k">protected</span> <span class="k">override</span> <span class="n">Android</span><span class="p">.</span><span class="n">Views</span><span class="p">.</span><span class="n">View</span> <span class="nf">GetCellCore</span><span class="p">(</span><span class="n">Cell</span> <span class="n">item</span><span class="p">,</span>
<span class="n">Android</span><span class="p">.</span><span class="n">Views</span><span class="p">.</span><span class="n">View</span> <span class="n">convertView</span><span class="p">,</span>
<span class="n">ViewGroup</span> <span class="n">parent</span><span class="p">,</span>
<span class="n">Context</span> <span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_cellCore</span> <span class="p">=</span> <span class="k">base</span><span class="p">.</span><span class="nf">GetCellCore</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">convertView</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="n">context</span><span class="p">);</span>
<span class="n">_selected</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="n">_unselectedBackground</span> <span class="p">=</span> <span class="n">_cellCore</span><span class="p">.</span><span class="n">Background</span><span class="p">;</span>
<span class="k">return</span> <span class="n">_cellCore</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">OnCellPropertyChanged</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</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">OnCellPropertyChanged</span><span class="p">(</span><span class="n">sender</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="s">"IsSelected"</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_selected</span> <span class="p">=</span> <span class="p">!</span><span class="n">_selected</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_selected</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">extendedViewCell</span> <span class="p">=</span> <span class="n">sender</span> <span class="k">as</span> <span class="n">ExtendedViewCell</span><span class="p">;</span>
<span class="n">_cellCore</span><span class="p">.</span><span class="nf">SetBackgroundColor</span><span class="p">(</span><span class="n">extendedViewCell</span><span class="p">.</span><span class="n">SelectedBackgroundColor</span><span class="p">.</span><span class="nf">ToAndroid</span><span class="p">());</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">_cellCore</span><span class="p">.</span><span class="nf">SetBackground</span><span class="p">(</span><span class="n">_unselectedBackground</span><span class="p">);</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><em>Note that this set/reset code will run <strong>every time</strong> you (de)select and item in the list.</em></p>
<h3 id="ios-custom-renderer">iOS custom renderer</h3>
<p>It doesn’t get much simpler than this one. The cool thing about the iOS implementation is that it sets the selected colour as a property of the cell <strong>as it’s being constructed</strong>. Once it’s done, the UI doesn’t need to come back to ‘ask’ the custom renderer what colour it should change to (unlike the Android one, above).</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="p">[</span><span class="n">assembly</span><span class="p">:</span> <span class="nf">ExportRenderer</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">ExtendedViewCell</span><span class="p">),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">ExtendedViewCellRenderer</span><span class="p">))]</span>
<span class="k">namespace</span> <span class="nn">xamformsdemo.iOS.CustomRenderers</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ExtendedViewCellRenderer</span> <span class="p">:</span> <span class="n">ViewCellRenderer</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">override</span> <span class="n">UITableViewCell</span> <span class="nf">GetCell</span><span class="p">(</span><span class="n">Cell</span> <span class="n">item</span><span class="p">,</span> <span class="n">UITableViewCell</span> <span class="n">reusableCell</span><span class="p">,</span> <span class="n">UITableView</span> <span class="n">tv</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">cell</span> <span class="p">=</span> <span class="k">base</span><span class="p">.</span><span class="nf">GetCell</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">reusableCell</span><span class="p">,</span> <span class="n">tv</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">view</span> <span class="p">=</span> <span class="n">item</span> <span class="k">as</span> <span class="n">ExtendedViewCell</span><span class="p">;</span>
<span class="n">cell</span><span class="p">.</span><span class="n">SelectedBackgroundView</span> <span class="p">=</span> <span class="k">new</span> <span class="n">UIView</span>
<span class="p">{</span>
<span class="n">BackgroundColor</span> <span class="p">=</span> <span class="n">view</span><span class="p">.</span><span class="n">SelectedBackgroundColor</span><span class="p">.</span><span class="nf">ToUIColor</span><span class="p">(),</span>
<span class="p">};</span>
<span class="k">return</span> <span class="n">cell</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></figure>
<p>After dropping in the <code class="language-plaintext highlighter-rouge">ExtendedViewCell</code> to replace the original <code class="language-plaintext highlighter-rouge">ViewCell</code>, and setting the <code class="language-plaintext highlighter-rouge">SelectedBackgroundColor</code> to <code class="language-plaintext highlighter-rouge">Teal</code>:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><ListView.ItemTemplate></span>
<span class="nt"><DataTemplate></span>
<span class="nt"><customControls:ExtendedViewCell</span> <span class="na">SelectedBackgroundColor=</span><span class="s">"Teal"</span><span class="nt">></span>
<span class="nt"><ViewCell.View></span>
<span class="nt"><StackLayout</span> <span class="na">HorizontalOptions=</span><span class="s">"FillAndExpand"</span>
<span class="na">VerticalOptions=</span><span class="s">"FillAndExpand"</span> <span class="na">Orientation=</span><span class="s">"Vertical"</span>
<span class="na">Padding=</span><span class="s">"4"</span> <span class="na">Spacing=</span><span class="s">"8"</span><span class="nt">></span>
<span class="nt"><Label</span> <span class="na">TextColor=</span><span class="s">"White"</span> <span class="na">Text=</span><span class="s">"{Binding .ItemName}"</span><span class="nt">/></span>
<span class="nt"><Label</span> <span class="na">TextColor=</span><span class="s">"Yellow"</span> <span class="na">Text=</span><span class="s">"{Binding .LastUpdated, StringFormat='Last seen: {0:HH:mm:ss}'}"</span><span class="nt">/></span>
<span class="nt"></StackLayout></span>
<span class="nt"></ViewCell.View></span>
<span class="nt"></customControls:ExtendedViewCell></span>
<span class="nt"></DataTemplate></span>
<span class="nt"></ListView.ItemTemplate></span>
<span class="nt"></ListView></span></code></pre></figure>
<h3 id="how-does-it-look-now">How does it look now?</h3>
<h4 id="ios">iOS</h4>
<p><img class="img-responsive img-thumbnail inline" alt="iOS - after implementation" title="iOS - after implementation" src="https://github.com/wislon/xfdemos/blob/master/screenshots/listviewcolor/ios-after.gif?raw=true" /></p>
<p><em>apologies for the janky iOS screen caps, I was using the iOS simulator over a very poor, long distance wifi connection</em></p>
<h4 id="android">Android</h4>
<p><img class="img-responsive img-thumbnail inline" alt="Android - after implementation" title="Android - after implementation" src="https://github.com/wislon/xfdemos/blob/master/screenshots/listviewcolor/droid-after.gif?raw=true" /></p>
<p>There’s a simple Xamarin Forms project <a href="https://github.com/wislon/xfdemos">over in GitHub</a> so you can try it out for yourself. As usual it’s published under the MIT licence, so you can do what you like with the code.</p>
Xamarin Forms (Android) - Why won't it store my Azure B2C Auth Token?2016-12-05T00:00:00+00:00http://blog.wislon.io/posts/2016/12/05/msal-token-not-stored-on-android<p>I’ve been playing with the <a href="https://azure.microsoft.com/en-us/services/active-directory-b2c/">Microsoft Azure Active Directory B2C</a> sign-in and sign-up management on mobile devices recently. It’s actually quite good, and does a lot of heavy lifting. Anyone who’s ever had to roll their own back-end authentication & authorisation services will appreciate the time and effort that this saves.</p>
<p>However it can be a little quirky. One of the gotchas I came across was that it wasn’t persisting the MSAL auth token on Xamarin Android when I built a <strong>release</strong> version of the app. Every time I restarted the app, it would ask me to sign in again. Rebuilding and/or running it in debug mode would ‘fix’ it, but the moment I switched back to a release version, it would stop working again. I realised it had something to do with the token not being read or persisted properly; though why it would work in debug mode but not release mode was beyond me.</p>
<p>But I can’t be the only person experiencing this problem, right?</p>
<p>First stop (as usual): Google, followed by Stack Overflow. Oh look, here’s someone with the <a href="https://stackoverflow.com/questions/40461242/msal-usertokencache-not-persisted-on-android">same problem</a>. Sadly he hasn’t been able to fix it either…</p>
<p>My initial thought it might be related to some arcane (or brand new) Android permissions thing. Tho access to the Android Shared Preferences store isn’t anything special, and it certainly shouldn’t be dependent on your build config…</p>
<p>I started to take a look at their <code class="language-plaintext highlighter-rouge">UserTokenCache</code> implementation to figure out why it wasn’t working, and then fix it. But it was late, and I was tired, so I found a simpler solution in one of the example repos in GtHub for building a <a href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-native-dotnet">native desktop app</a>.</p>
<p>In that example, they point the MSAL <code class="language-plaintext highlighter-rouge">PublicClientApplication</code> at a rudimentary <code class="language-plaintext highlighter-rouge">FileCache</code> object for storing the auth tokens:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="n">ClientApplication</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">PublicClientApplication</span><span class="p">(</span><span class="n">your</span><span class="p">.</span><span class="n">b2c</span><span class="p">.</span><span class="n">AuthContext</span><span class="p">,</span> <span class="n">your</span><span class="p">.</span><span class="n">b2c</span><span class="p">.</span><span class="n">ClientId</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">RedirectUri</span> <span class="p">=</span> <span class="s">"urn:ietf:wg:oauth:2.0:oob"</span><span class="p">,</span>
<span class="n">UserTokenCache</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">FileCache</span><span class="p">(),</span>
<span class="p">};</span></code></pre></figure>
<p><em>by default most examples leave the UserTokenCache property empty, which forces the engine to use whatever they’ve defined as the platform default, which of course is where this is falling down</em></p>
<p>So I had a look at their <code class="language-plaintext highlighter-rouge">FileCache</code> <a href="https://github.com/AzureADQuickStarts/B2C-NativeClient-DotNet/blob/complete/TaskClient/FileCache.cs">object in that same repo</a>. Yep. Simple enough.</p>
<p>I plugged that into my code, and added a bunch of logging to the bits where it reads and writes the auth token, to see what it was doing. My theory was that it was more likely to have a problem writing the token than when reading it.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">void</span> <span class="nf">AfterAccessNotification</span><span class="p">(</span><span class="n">TokenCacheNotificationArgs</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// if the access operation resulted in a cache update
</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">Log</span><span class="p">().</span><span class="nf">Debug</span><span class="p">(</span><span class="s">"About to update token cache (if it's changed)..."</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">HasStateChanged</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">Log</span><span class="p">().</span><span class="nf">Debug</span><span class="p">(</span><span class="s">"State has changed, updating cache file..."</span><span class="p">);</span>
<span class="k">lock</span> <span class="p">(</span><span class="n">FileLock</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_file</span><span class="p">.</span><span class="nf">WriteAllBytes</span><span class="p">(</span><span class="n">CacheFilePath</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">());</span>
<span class="c1">// once the write operation took place, restore the HasStateChanged bit to false
</span>
<span class="k">this</span><span class="p">.</span><span class="n">HasStateChanged</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">this</span><span class="p">.</span><span class="nf">Log</span><span class="p">().</span><span class="nf">Debug</span><span class="p">(</span><span class="s">"Token cache file updated"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">this</span><span class="p">.</span><span class="nf">Log</span><span class="p">().</span><span class="nf">Debug</span><span class="p">(</span><span class="s">"Finished updating token cache file"</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="k">this</span><span class="p">.</span><span class="nf">Log</span><span class="p">().</span><span class="nf">ErrorException</span><span class="p">(</span><span class="s">$"Something went wrong in AfterAccessNotification: </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="s">"</span><span class="p">,</span> <span class="n">ex</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>True enough, when running the app in debug, the storage and retrieval of auth tokens worked just fine. But when I rebuilt in release mode and ran it (I broke the exception message up over several lines for legibility):</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">12-05 09:22:03.434 LGE Nexus 5X Error 11208 ZB.Droid at System.Runtime.Serialization.Json.JsonFormatWriterInterpreter.WriteValue
(System.Type memberType, System.Object memberValue) [0x00268] in <40b76ff8e1454ef49c9939320c700e1b>:0
12-05 09:22:03.434 LGE Nexus 5X Error 11208 ZB.Droid
FileCache: Something went wrong during token
AfterAccessNotification: No set method for property 'OffsetMinutes' in type
'System.Runtime.Serialization.DateTimeOffsetAdapter'.:
System.Runtime.Serialization.InvalidDataContractException:
No set method for property 'OffsetMinutes' in type
'System.Runtime.Serialization.DateTimeOffsetAdapter'.
12-05 09:22:03.362 LGE Nexus 5X Info 11208 4/12/2016 10:22:03 PM: - TokenCache.cs: Serializing token cache with 1 items.</code></pre></figure>
<p>OK. Well that answers that then. An exception related to there not being a setter for a <code class="language-plaintext highlighter-rouge">DateTimeOffset</code>-related property somewhere deep in the bowels of the MSAL library. Ugh!</p>
<p>However, once I wrapped the contents of the <code class="language-plaintext highlighter-rouge">AfterAccessNotification</code> in a try-catch with that handler to log that the exception had occurred, it all started working perfectly.</p>
<p>This doesn’t fix the problem though. I mean sure, you can stick with using this <code class="language-plaintext highlighter-rouge">FileCache</code> implementation, but it does mean that the auth tokens <strong>aren’t encrypted or protected</strong> like they would be if they were in the platform’s SharedPreferences store. Keep this in mind if you go this route to solve any other problems: you should implement a more secure storage mechanism than this one.</p>
<h3 id="the-fix">The Fix</h3>
<p>Turns out it’s a pretty simple fix, after this. Henrik (from the original Stack Overflow question) indicated that simply telling the compiler to NOT link in the <code class="language-plaintext highlighter-rouge">System.Runtime.Serializaton</code> library during a release build fixed the problem at his end.</p>
<p>This is easy to do: simply open the project properties for the Android project, make sure your build output is set to your <code class="language-plaintext highlighter-rouge">Release</code> config, and then in the “Android Options / Linker” tab, add <code class="language-plaintext highlighter-rouge">System.Runtime.Serialization</code> to the text box.</p>
<p><img class="img-responsive img-thumbnail inline" src="/images/b2cauthandroid/skip-linking-srs.jpg" alt="Don't link System.Runtime.Serialization" title="Don't link System.Runtime.Serialization" /></p>
<p>This will ensure that when the app is compiled, the serialsation code/setter for that <code class="language-plaintext highlighter-rouge">DateTimeOffset</code> property won’t be excluded by being ‘linked out’.</p>
<p>Alternatively, you can add an entry to your Android project file, under the <code class="language-plaintext highlighter-rouge">Release</code> property group condition, which does the same thing:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><AndroidUseSharedRuntime></span>False<span class="nt"></AndroidUseSharedRuntime></span>
<span class="nt"><AndroidLinkSkip></span>System.Runtime.Serialization;<span class="nt"></AndroidLinkSkip></span>
<span class="nt"><AndroidLinkMode></span>SdkOnly<span class="nt"></AndroidLinkMode></span></code></pre></figure>
<p>Remember to CLEAN and then rebuild, and then it should Just Work. Thanks Henrik for the update! :)</p>
<p><em>If you now remove the <code class="language-plaintext highlighter-rouge">FileCache</code> class and all references to it, it’ll all go back to working the way it should have.</em></p>
<p>Also: There is an issue on the iOS Simulator (there’s a shock!), that requires you to fiddle with the <code class="language-plaintext highlighter-rouge">Entitlements.plist</code> file in order to access the secure Shared Storage. If this is an issue for you on the simulator, and you’re only using the Shared Storage by default to store this token, then you can use this FileCache method to get around it.</p>
<h3 id="some-personal-comments-on-the-azure-b2c-service">Some (personal) comments on the Azure B2C Service</h3>
<p>Azure AD B2C has its quirks, and imo they have some work to do. Especially around enabling us to do ‘headless’ signups and logins. And I really wish they’d finish it up and bring their nuget library out of <code class="language-plaintext highlighter-rouge">-alpha</code>. Come on guys, you promoted the hell out of this thing at <strong>//Build</strong>, but it’s like you’ve barely touched it since?</p>
<h3 id="pros">Pros:</h3>
<ul>
<li>
<p>It’ll get you up and running quickly. There’s a ton of blog posts out there which tell you how to get it done and up and running. And I have to say, it is relatively painless.</p>
</li>
<li>
<p>It’s (mostly) platform agnostic, so there’s ways to get it done using .NET, JavaScript, UWP, Cordova (may the gods have mercy on your soul) and so on.</p>
</li>
<li>
<p>It’s Azure, and it’s ‘GA’ (‘Generally Available’) in more than the USA now.</p>
</li>
<li>
<p>It’s based on Microsoft’s Active Directory, which has been around for a <em>very</em> long time. It’s probably not going away any time soon.</p>
</li>
<li>If you want to be able to provide email sign-up services to your users, instead of having to proxy their auth via one of the other social network identity providers, this does a reasonable job.
<ul>
<li>Speaking of using other social network IDPs (e.g. Google, Facebook, Twitter), if you’ve felt the refresh-token-sync pain, then you should definitely give it a go.</li>
</ul>
</li>
<li>
<p>As of fairly recently, you can now completely customise and style the web interface for the sign-up/sign-in WebView that pops up on mobile devices.</p>
</li>
<li>It has flows for user initiated password reset, and allowing users to manage their details themselves. If you’ve ever had to implement this kind of thing yourself, you’ll know what a time saver this is!</li>
</ul>
<h3 id="cons">Cons:</h3>
<ul>
<li>
<p>Auth tokens can’t be written away properly to the Android SharedPreferences secure store when the app is built in ‘release’ mode without footling with the linker settings. Heh :)</p>
</li>
<li>
<p>Actually, the biggest gripe I have with it is that I can’t (at the time of writing) do a ‘headless’ login. I’m forced to hook up their ‘use a platspec custom renderer so we can embed a webview in your content page’ process. It’s S.L.O.W af, and I hate using ‘loading…’ spinners to tell my users “no we haven’t crashed or got stuck, but you can stare at this blank screen while we’re waiting for something to happen”.</p>
</li>
<li>
<p>If you have a shitty mobile internet connection (hello Australia!), it can take upwards of a minute to load the aforementioned sign-in/sign-up pages. Substantially less than optimal. I, and many others, would like to be able to use the standard mobile interface controls and drive an OAuth2/OpenIdConnect flow behind the scenes.</p>
</li>
</ul>
<p>They keep alluding that you <em>can</em> do this. But I’ve not been able to find a way that doesn’t require popping up a web browser instance to load a web page to pull in some some javascript to pull in some form elements and an internal token which is then submitted as part of the request to get an access token… I mean… <a href="https://en.wikipedia.org/wiki/Rube_Goldberg_machine">Rube Goldberg</a> much?!</p>
<p>If anyone out there has managed to crack the ‘headless login’ issue without using a web browser of some sort, then please let us know in the comments!</p>
Xamarin Forms - Quick and Dirty Cookies Access (iOS and Android)2016-01-24T00:00:00+00:00http://blog.wislon.io/posts/2016/01/24/xamforms-quick-and-dirty-cookies-access<h3 id="cross-platform-cookie-access-on-ios-and-android-in-xamarin-forms">Cross-platform Cookie Access on iOS and Android in Xamarin Forms</h3>
<p>On one of my previous gigs I was building a Xamarin Forms app targeting iOS and Android, and needed to be able to access an OAuth-based cookie obtained from a WebView-based login flow.</p>
<p>The cookie in question needed to be attached to some pseudo-REST-based HTTP requests (made using the standard .NET <code class="language-plaintext highlighter-rouge">HttpClient</code>) to GET and POST data to an ASP.NET Web API serviced back-end system.</p>
<p>In order to maximise the cross-platform availability (and testability) of the library making those requests, it was built as a portable class library (PCL).</p>
<p>And because it needed to be xplat, there was no way to directly access the iOS and Android platform-specific Cookie managers.</p>
<p>On both iOS and Android, both platforms have a shared “Cookie Store” which is used by web browsers installed on the device. This includes the Xamarin Forms xplat <code class="language-plaintext highlighter-rouge">WebView</code> implementation, since this is just a cross-platform “placeholder” for the native web view’s custom renderer. So if the native browser has stored a cookie, the WebView can pick it up, and vice versa.</p>
<p>The good news is that the platspec cookie managers are easily exposed on both iOS and Android platforms (on iOS it’s a static instance of <code class="language-plaintext highlighter-rouge">NSHttpCookieStorage.SharedStorage.Cookies</code> and on Android it’s a static instance of <code class="language-plaintext highlighter-rouge">Android.WebKit.CookieManager</code>). More importantly they don’t even <em>need</em> a WebView for you to be able to get at the device’s cookies.</p>
<p>This means it’s really easy to provide a cross-platform solution using dependency injection.</p>
<h3 id="what-can-it-do">What Can It Do?</h3>
<p>My use case was pretty simple:</p>
<ul>
<li>read the cookie(s) for a site I had logged in with,</li>
<li>extract the oauth-related one(s) I needed,
<ul>
<li>use them in the <code class="language-plaintext highlighter-rouge">HttpClient</code> headers to auth my requests with the back-end</li>
</ul>
</li>
<li>delete those cookie(s) when done, from the cookie manager (to enforce a log-out function)</li>
</ul>
<p>The rest of this post only deals with the methods I required, but I think in most cases that will suit your requirements too. And there’s ample documentation on how to use the respective cookie managers, should you need to expand your functionality.</p>
<h3 id="the-cross-platform-shared-interface">The Cross-Platform (‘Shared’) Interface</h3>
<p>In order to be able to pass the platspec instance of the cookie manager down to the cross-platform PCL (which actually uses it), I need to use a dependency injection. I used <a href="https://github.com/paulcbetts/splat">Splat</a> for this, because it’s awesome, but obviously you can use whichever DI container you’re most comfortable with.</p>
<p>I created a shared <code class="language-plaintext highlighter-rouge">IPlatformCookieStore</code> interface which I can then use to register the actual platspec cookie manager instance in my DI container’s registry. The interface looks like this:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"> <span class="k">public</span> <span class="k">interface</span> <span class="nc">IPlatformCookieStore</span>
<span class="p">{</span>
<span class="c1">/// <summary>
</span>
<span class="c1">/// List of cookies pulled from the cookie storage manager
</span>
<span class="c1">/// on the device/platform you're on. Can be quite an expensive call.
</span>
<span class="c1">/// </summary>
</span>
<span class="n">IEnumerable</span><span class="p"><</span><span class="n">Cookie</span><span class="p">></span> <span class="n">CurrentCookies</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
<span class="c1">/// <summary>
</span>
<span class="c1">/// Debug method, just lists all cookies in the <see cref="CurrentCookies"/> list
</span>
<span class="c1">/// </summary>
</span>
<span class="k">void</span> <span class="nf">DumpAllCookiesToLog</span><span class="p">();</span>
<span class="c1">/// <summary>
</span>
<span class="c1">/// Clear cookies for site/url (otherwise auth tokens for your provider
</span>
<span class="c1">/// will hang around after a logout, which causes problems if you want
</span>
<span class="c1">/// to log in as someone else)
</span>
<span class="c1">/// </summary>
</span>
<span class="k">void</span> <span class="nf">DeleteAllCookiesForSite</span><span class="p">(</span><span class="kt">string</span> <span class="n">url</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>Pretty straightforward: Give me a list of the current cookies. Let me dump all the cookies to a log so I can see what’s in them. And let me delete cookies from the platspec instance.</p>
<p>Then, a quick and dirty wrapper class with those interfaced methods wrapped around the iOS Cookie Manager (<code class="language-plaintext highlighter-rouge">NSHttpCookieStorage.SharedStorage.Cookies</code>).:</p>
<h4 id="ioscookiestore-implementation">IOSCookieStore Implementation</h4>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"> <span class="k">public</span> <span class="k">class</span> <span class="nc">IOSCookieStore</span> <span class="p">:</span> <span class="n">IPlatformCookieStore</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="kt">object</span> <span class="n">_refreshLock</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">object</span><span class="p">();</span>
<span class="k">public</span> <span class="n">IEnumerable</span><span class="p"><</span><span class="n">Cookie</span><span class="p">></span> <span class="n">CurrentCookies</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">RefreshCookies</span><span class="p">();</span> <span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="nf">IOSCookieStore</span><span class="p">(</span><span class="kt">string</span> <span class="n">url</span> <span class="p">=</span> <span class="s">""</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">}</span>
<span class="k">private</span> <span class="n">IEnumerable</span><span class="p"><</span><span class="n">Cookie</span><span class="p">></span> <span class="nf">RefreshCookies</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">lock</span> <span class="p">(</span><span class="n">_refreshLock</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">cookie</span> <span class="k">in</span> <span class="n">NSHttpCookieStorage</span><span class="p">.</span><span class="n">SharedStorage</span><span class="p">.</span><span class="n">Cookies</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">yield</span> <span class="k">return</span> <span class="k">new</span> <span class="n">Cookie</span>
<span class="p">{</span>
<span class="n">Comment</span> <span class="p">=</span> <span class="n">cookie</span><span class="p">.</span><span class="n">Comment</span><span class="p">,</span>
<span class="n">Domain</span> <span class="p">=</span> <span class="n">cookie</span><span class="p">.</span><span class="n">Domain</span><span class="p">,</span>
<span class="n">HttpOnly</span> <span class="p">=</span> <span class="n">cookie</span><span class="p">.</span><span class="n">IsHttpOnly</span><span class="p">,</span>
<span class="n">Name</span> <span class="p">=</span> <span class="n">cookie</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span>
<span class="n">Path</span> <span class="p">=</span> <span class="n">cookie</span><span class="p">.</span><span class="n">Path</span><span class="p">,</span>
<span class="n">Secure</span> <span class="p">=</span> <span class="n">cookie</span><span class="p">.</span><span class="n">IsSecure</span><span class="p">,</span>
<span class="n">Value</span> <span class="p">=</span> <span class="n">cookie</span><span class="p">.</span><span class="n">Value</span><span class="p">,</span>
<span class="c1">/// TODO expires? / expired?
</span>
<span class="n">Version</span> <span class="p">=</span> <span class="n">Convert</span><span class="p">.</span><span class="nf">ToInt32</span><span class="p">(</span><span class="n">cookie</span><span class="p">.</span><span class="n">Version</span><span class="p">)</span>
<span class="p">};</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">DumpAllCookiesToLog</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(!</span><span class="n">CurrentCookies</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="nf">LogDebug</span><span class="p">(</span><span class="s">"No cookies in your iOS cookie store. Srsly? No cookies? At all?!?"</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">CurrentCookies</span><span class="p">.</span><span class="nf">ToList</span><span class="p">()</span>
<span class="p">.</span><span class="nf">ForEach</span><span class="p">(</span><span class="n">cookie</span> <span class="p">=></span>
<span class="nf">LogDebug</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"Cookie dump: {0} = {1}"</span><span class="p">,</span>
<span class="n">cookie</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span>
<span class="n">cookie</span><span class="p">.</span><span class="n">Value</span><span class="p">)));</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">DeleteAllCookiesForSite</span><span class="p">(</span><span class="kt">string</span> <span class="n">url</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">cookieStorage</span> <span class="p">=</span> <span class="n">NSHttpCookieStorage</span><span class="p">.</span><span class="n">SharedStorage</span><span class="p">;</span>
<span class="k">foreach</span><span class="p">(</span><span class="kt">var</span> <span class="n">cookie</span> <span class="k">in</span> <span class="n">cookieStorage</span><span class="p">.</span><span class="nf">CookiesForUrl</span><span class="p">(</span><span class="k">new</span> <span class="nf">NSUrl</span><span class="p">(</span><span class="n">url</span><span class="p">)).</span><span class="nf">ToList</span><span class="p">())</span>
<span class="p">{</span>
<span class="n">cookieStorage</span><span class="p">.</span><span class="nf">DeleteCookie</span><span class="p">(</span><span class="n">cookie</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// you MUST call the .Sync method or those cookies may hang around for a bit
</span>
<span class="n">NSUserDefaults</span><span class="p">.</span><span class="n">StandardUserDefaults</span><span class="p">.</span><span class="nf">Synchronize</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><em>The <code class="language-plaintext highlighter-rouge">LogDebug</code> method is just abstract I’ve created here for demo purposes, this could just as easily point to your logging library instance, or something like <code class="language-plaintext highlighter-rouge">Console.WriteLine()</code>.</em></p>
<h4 id="droidcookiestore-implementation">DroidCookieStore Implementation</h4>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"> <span class="k">public</span> <span class="k">class</span> <span class="nc">DroidCookieStore</span> <span class="p">:</span> <span class="n">IPlatformCookieStore</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="kt">string</span> <span class="n">_url</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="kt">object</span> <span class="n">_refreshLock</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">object</span><span class="p">();</span>
<span class="k">public</span> <span class="n">IEnumerable</span><span class="p"><</span><span class="n">Cookie</span><span class="p">></span> <span class="n">CurrentCookies</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">RefreshCookies</span><span class="p">();</span> <span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="nf">DroidCookieStore</span><span class="p">(</span><span class="kt">string</span> <span class="n">url</span> <span class="p">=</span> <span class="s">""</span><span class="p">)</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">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">url</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="s">"url"</span><span class="p">,</span> <span class="s">"On Android, 'url' cannot be empty,
</span> <span class="n">please</span> <span class="n">provide</span> <span class="n">a</span> <span class="k">base</span> <span class="n">URL</span> <span class="k">for</span> <span class="n">it</span> <span class="n">to</span> <span class="n">use</span> <span class="n">when</span> <span class="n">loading</span> <span class="n">related</span> <span class="n">cookies</span><span class="s">");
</span> <span class="p">}</span>
<span class="n">_url</span> <span class="p">=</span> <span class="n">url</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">private</span> <span class="n">IEnumerable</span><span class="p"><</span><span class="n">Cookie</span><span class="p">></span> <span class="nf">RefreshCookies</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">lock</span><span class="p">(</span><span class="n">_refreshLock</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// .GetCookie returns ALL cookies related to the URL as a single, long
</span>
<span class="c1">// string which we have to split and parse
</span>
<span class="kt">var</span> <span class="n">allCookiesForUrl</span> <span class="p">=</span> <span class="n">CookieManager</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="nf">GetCookie</span><span class="p">(</span><span class="n">_url</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">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">allCookiesForUrl</span><span class="p">))</span>
<span class="p">{</span>
<span class="nf">LogDebug</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"No cookies found for '{0}'. Exiting."</span><span class="p">,</span> <span class="n">_url</span><span class="p">));</span>
<span class="k">yield</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">Cookie</span><span class="p">(</span><span class="s">"none"</span><span class="p">,</span> <span class="s">"none"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="nf">LogDebug</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"\r\n===== CookieHeader : '{0}'\r\n"</span><span class="p">,</span> <span class="n">allCookiesForUrl</span><span class="p">));</span>
<span class="kt">var</span> <span class="n">cookiePairs</span> <span class="p">=</span> <span class="n">allCookiesForUrl</span><span class="p">.</span><span class="nf">Split</span><span class="p">(</span><span class="sc">' '</span><span class="p">);</span>
<span class="k">foreach</span><span class="p">(</span><span class="kt">var</span> <span class="n">cookiePair</span> <span class="k">in</span> <span class="n">cookiePairs</span><span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">cp</span> <span class="p">=></span> <span class="n">cp</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="s">"="</span><span class="p">)))</span>
<span class="p">{</span>
<span class="c1">// yeah, I know, but this is a quick-and-dirty, remember? ;)
</span>
<span class="kt">var</span> <span class="n">cookiePieces</span> <span class="p">=</span> <span class="n">cookiePair</span><span class="p">.</span><span class="nf">Split</span><span class="p">(</span><span class="k">new</span><span class="p">[]</span> <span class="p">{</span><span class="sc">'='</span><span class="p">},</span> <span class="n">StringSplitOptions</span><span class="p">.</span><span class="n">RemoveEmptyEntries</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">cookiePieces</span><span class="p">.</span><span class="n">Length</span> <span class="p">>=</span> <span class="m">2</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">cookiePieces</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="p">=</span> <span class="n">cookiePieces</span><span class="p">[</span><span class="m">0</span><span class="p">].</span><span class="nf">Contains</span><span class="p">(</span><span class="s">":"</span><span class="p">)</span>
<span class="p">?</span> <span class="n">cookiePieces</span><span class="p">[</span><span class="m">0</span><span class="p">].</span><span class="nf">Substring</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">cookiePieces</span><span class="p">[</span><span class="m">0</span><span class="p">].</span><span class="nf">IndexOf</span><span class="p">(</span><span class="s">":"</span><span class="p">))</span>
<span class="p">:</span> <span class="n">cookiePieces</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
<span class="c1">// strip off trailing ';' if it's there (some implementations
</span>
<span class="c1">// on droid have it, some do not)
</span>
<span class="n">cookiePieces</span><span class="p">[</span><span class="m">1</span><span class="p">]</span> <span class="p">=</span> <span class="n">cookiePieces</span><span class="p">[</span><span class="m">1</span><span class="p">].</span><span class="nf">EndsWith</span><span class="p">(</span><span class="s">";"</span><span class="p">)</span>
<span class="p">?</span> <span class="n">cookiePieces</span><span class="p">[</span><span class="m">1</span><span class="p">].</span><span class="nf">Substring</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">cookiePieces</span><span class="p">[</span><span class="m">1</span><span class="p">].</span><span class="n">Length</span> <span class="p">-</span> <span class="m">1</span><span class="p">)</span>
<span class="p">:</span> <span class="n">cookiePieces</span><span class="p">[</span><span class="m">1</span><span class="p">];</span>
<span class="k">yield</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">Cookie</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">Name</span> <span class="p">=</span> <span class="n">cookiePieces</span><span class="p">[</span><span class="m">0</span><span class="p">],</span>
<span class="n">Value</span> <span class="p">=</span> <span class="n">cookiePieces</span><span class="p">[</span><span class="m">1</span><span class="p">],</span>
<span class="n">Path</span> <span class="p">=</span> <span class="s">"/"</span><span class="p">,</span>
<span class="n">Domain</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="n">_url</span><span class="p">).</span><span class="n">DnsSafeHost</span><span class="p">,</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="p">}</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">DumpAllCookiesToLog</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// same as for iOS
</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">DeleteAllCookiesForSite</span><span class="p">(</span><span class="kt">string</span> <span class="n">url</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// TODO remove specific cookies by name...?
</span>
<span class="c1">// coz this may be a bit scorched-earth...
</span>
<span class="n">CookieManager</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="nf">RemoveAllCookie</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></figure>
<h3 id="registering-and-using-the-cookie-store">Registering and Using the Cookie Store</h3>
<p>Now we add our shared <code class="language-plaintext highlighter-rouge">IPlatformCookieStore</code> to our DI container’s registry (as I mentioned earlier, I use <a href="https://github.com/paulcbetts/splat">Splat</a>, but you can use whatever you like).</p>
<h4 id="ios">iOS</h4>
<p>In my iOS project’s <strong>AppDelegate</strong>, in the <code class="language-plaintext highlighter-rouge">FinishedLaunching</code> method:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"> <span class="k">public</span> <span class="k">override</span> <span class="kt">bool</span> <span class="nf">FinishedLaunching</span><span class="p">(</span><span class="n">UIApplication</span> <span class="n">app</span><span class="p">,</span> <span class="n">NSDictionary</span> <span class="n">options</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Locator</span><span class="p">.</span><span class="n">CurrentMutable</span><span class="p">.</span><span class="nf">RegisterConstant</span><span class="p">(</span><span class="k">new</span> <span class="nf">IOSCookieStore</span><span class="p">(),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">IPlatformCookieStore</span><span class="p">));</span>
<span class="k">global</span><span class="p">::</span><span class="n">Xamarin</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="nf">Init</span><span class="p">();</span>
<span class="nf">LoadApplication</span><span class="p">(</span><span class="k">new</span> <span class="nf">App</span><span class="p">());</span>
<span class="k">return</span> <span class="k">base</span><span class="p">.</span><span class="nf">FinishedLaunching</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">options</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p><em><code class="language-plaintext highlighter-rouge">Locator</code> is just my static Splat DI instance</em></p>
<h4 id="android">Android</h4>
<p>In my Android project’s <strong>MainActivity</strong>, in the <code class="language-plaintext highlighter-rouge">OnCreate</code> method:</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"> <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="n">Locator</span><span class="p">.</span><span class="n">CurrentMutable</span><span class="p">.</span><span class="nf">Register</span><span class="p">(()</span> <span class="p">=></span> <span class="k">new</span> <span class="nf">DroidCookieStore</span><span class="p">(</span><span class="s">"http://your.web.url"</span><span class="p">),</span>
<span class="k">typeof</span><span class="p">(</span><span class="n">IPlatformCookieStore</span><span class="p">));</span>
<span class="k">global</span><span class="p">::</span><span class="n">Xamarin</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="nf">Init</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">bundle</span><span class="p">);</span>
<span class="nf">LoadApplication</span><span class="p">(</span><span class="k">new</span> <span class="nf">App</span><span class="p">());</span>
<span class="p">}</span></code></pre></figure>
<p><em>Note that the constructor for the <code class="language-plaintext highlighter-rouge">DroidCookieStore</code> requires a website base URL for proper initialisation</em></p>
<h3 id="wrapping-up">Wrapping Up</h3>
<p>Now all that remains is to access your platspec cookie store from your shared cross-platform code by interacting with the instance you registered with your DI container.</p>
<p>How and where you use it will depend on your implementation. A simple (contrived) solution can be done something like the code below (NB: I’ve not compiled the class below, but the intent should be clear):</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"> <span class="k">public</span> <span class="k">class</span> <span class="nc">LameCookieStoreDemo</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">private</span> <span class="n">IPlatformCookieStore</span> <span class="n">_cookieStore</span><span class="p">;</span>
<span class="k">private</span> <span class="n">System</span><span class="p">.</span><span class="n">Net</span><span class="p">.</span><span class="n">Cookie</span> <span class="n">_oauthCookie</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">LameCookieManagerDemo</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_cookieStore</span> <span class="p">=</span> <span class="n">Splat</span><span class="p">.</span><span class="n">Locator</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">GetService</span><span class="p"><</span><span class="n">IPlatformCookieStore</span><span class="p">>();</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">RunMeWhenWebViewIsDoneNavigating</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_cookieStore</span><span class="p">.</span><span class="nf">DumpAllCookiesToLog</span><span class="p">();</span>
<span class="n">_oauthCookie</span> <span class="p">=</span> <span class="n">_cookieStore</span><span class="p">.</span><span class="n">CurrentCookies</span>
<span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">cc</span> <span class="p">=></span> <span class="n">cc</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">"oauthCookieName"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_oauthCookie</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// do stuff with it, like hand it off to your Web
</span>
<span class="c1">// Api's REST/RPC call, or whatever
</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">LogoutByDeletingOAuthCookies</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_cookieStore</span><span class="p">.</span><span class="nf">DeleteAllCookiesForSite</span><span class="p">(</span><span class="s">"http://your.web.url"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><strong>Another option</strong> is to inherit from the xplat <code class="language-plaintext highlighter-rouge">WebView</code> control in Xamarin Forms, and add an <code class="language-plaintext highlighter-rouge">IPlatformCookieStore</code> object to it. You can then go hunting for your cookies whenever (for example) the <code class="language-plaintext highlighter-rouge">WebView.OnNavigated</code> or <code class="language-plaintext highlighter-rouge">OnNavigating</code> methods fire.</p>
<h3 id="so-where-is-it">So where is it?</h3>
<p>The source code is on GitHub as a gist, at <a href="https://gist.github.com/wislon/260438ee77e8de9e4ffc">https://gist.github.com/wislon/260438ee77e8de9e4ffc</a>. I haven’t had time to pull all the bits together into a buildable, standalone project, but this will definitely be enough to get you going.</p>
<h3 id="license">License</h3>
<p><em>The code in this gist/repo is released under the free-for-all MIT License, so if you want to copy it and do better stuff with it, go right ahead! :)</em></p>
A Couple of Simple Continuous Integration Build-Revisioning Tools2015-03-11T00:00:00+00:00http://blog.wislon.io/posts/2015/03/11/a-couple-of-simple-continuous-integration-build-revisioning-tools<h3 id="what-even-is-this">What even is this?</h3>
<p>Here’s a couple of .NET exes I built over a couple of hours to automate the incrementing of build revision numbers in</p>
<ul>
<li>Xamarin/Android <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> files</li>
<li>.NET <code class="language-plaintext highlighter-rouge">AssemblyInfo</code> related files</li>
</ul>
<h4 id="why-bother">Why bother?</h4>
<p>Xamarin.Android apps contain both kinds of version resources. And I got tired of incrementing one and forgetting to do the others. And if it’s friction, or a pain point for me, I prefer to automate it away if I can.</p>
<p>So this is the stupidest thing that actually works.</p>
<p>All these utilities do is load and parse a given file name looking for specific string/regex matches. If they are able to locate the requested bits of information, they’ll auto-increment the <strong>build revision #</strong>, and write the file back out again.</p>
<p>And that is <em>all</em> they do. Their primary function is simply to operate as part of a custom build/build-tool chain, so it’s up to the developer to integrate them with their CI process (examples below).</p>
<p><em>Yes, I know, TeamCity and some other CI toolkits can do this too. I can’t use TeamCity in a couple of current projects, because ‘not invented here’. So I slapped these little guys together.</em></p>
<p>Application versions are often displayed as a set of numbers indicating</p>
<p><code class="language-plaintext highlighter-rouge">major_version.minor_version.build_#.build_revision</code></p>
<p>This is usually displayed in the format: <code class="language-plaintext highlighter-rouge">1.0.1.2</code> or <code class="language-plaintext highlighter-rouge">1.0.2.0-alpha</code> or something similar. Normally what happens when you (re)build an app, these would be incremented automatically for you. Some “increments” aren’t actually increments. They could be git commit hashes, timestamps, you name it.</p>
<p>In the case of these utilities, all they do is try and locate something that looks like a set of numbers like this, in a file you specify. And if they find it, they’ll increment the build revision # (the last number in the quartet).</p>
<p>For example, if the existing version # is 1.2.3.4, then running these utilities will simply update that last digit to 1.2.3.5.</p>
<p><em>Yes, they could do so much more, and I’m sure you’ve got a million ideas about how yours would be 10x more awesome. But this solved an immediate problem I had.</em></p>
<h3 id="androidmanifestutil-for-androidmanifestxml-files">AndroidManifestUtil (for AndroidManifest.xml files)</h3>
<p>This updates the <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> file commonly found in Android and Xamarin.Android projects.</p>
<p>Normally when you build your Android app, you have to provide two version values, a version <strong>code</strong> (<code class="language-plaintext highlighter-rouge">versionCode</code> in the manifest file) and a version <strong>name</strong> (<code class="language-plaintext highlighter-rouge">versionName</code> in the manifest file).</p>
<p>The <code class="language-plaintext highlighter-rouge">versionCode</code> value is a single integer used by the Google Play store to determine whether the app is a newer version than one it already has.</p>
<p>The <code class="language-plaintext highlighter-rouge">versionName</code> is a set of numbers (and perhaps other identifiers), as described above, which you can use for “proper” version tracking in your app; maybe for analytics or crash reporting or something else).</p>
<p><code class="language-plaintext highlighter-rouge">AndroidManifestUtil</code> extracts the <code class="language-plaintext highlighter-rouge">versionName</code> attribute from the <code class="language-plaintext highlighter-rouge">manifest</code> xml node, tries to parse it to extract the 4 numbers, increments the last one (the build revision), and writes it back to the file.</p>
<p>It will also extract the <code class="language-plaintext highlighter-rouge">versionCode</code> attribute from the <code class="language-plaintext highlighter-rouge">manifest</code> xml node, increment it, and writes it back to the file.</p>
<p><em>The Google Play store depends on this <code class="language-plaintext highlighter-rouge">versionCode</code> attribute to know that you’ve updated your app in the store, but the numbers don’t need to be sequential. As long as your new version has a higher <code class="language-plaintext highlighter-rouge">versionCode</code> value than the previous one, that’s cool.</em></p>
<p>If you’ve got some funky version numbering you do (e.g. ‘1.0.rc-02.02Jan2014’), then you’re on your own.</p>
<h4 id="usage">Usage</h4>
<p>Theres’s a test <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> file in the project folder which looks something like:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="c"><!-- dummy xml file for /manifest/android:versionName attribute revision # increment testing --></span>
<span class="nt"><manifest</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">android:installLocation=</span><span class="s">"auto"</span> <span class="na">package=</span><span class="s">"io.wislon.testApp"</span>
<span class="na">android:versionCode=</span><span class="s">"1"</span>
<span class="na">android:versionName=</span><span class="s">"1.0.2.0"</span><span class="nt">></span>
<span class="nt"><uses-sdk</span> <span class="na">android:minSdkVersion=</span><span class="s">"15"</span> <span class="na">android:targetSdkVersion=</span><span class="s">"19"</span> <span class="nt">/></span>
<span class="nt"><application</span> <span class="na">android:icon=</span><span class="s">"@drawable/Icon"</span> <span class="na">android:label=</span><span class="s">"testApp"</span><span class="nt">></span>
<span class="nt"></application></span>
<span class="nt"><uses-permission</span> <span class="na">android:name=</span><span class="s">"android.permission.ACCESS_WIFI_STATE"</span> <span class="nt">/></span>
<span class="nt"><uses-permission</span> <span class="na">android:name=</span><span class="s">"android.permission.READ_EXTERNAL_STORAGE"</span> <span class="nt">/></span>
<span class="nt"><uses-permission</span> <span class="na">android:name=</span><span class="s">"android.permission.ACCESS_NETWORK_STATE"</span> <span class="nt">/></span>
<span class="nt"><uses-permission</span> <span class="na">android:name=</span><span class="s">"android.permission.INTERNET"</span> <span class="nt">/></span>
<span class="nt"></manifest></span></code></pre></figure>
<p>As you can see, the <code class="language-plaintext highlighter-rouge">versionName</code> is set to 1.0.2.0.</p>
<p>Run the utility from the command line (or script):</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">AndroidManifestUtil.exe -filename=.\AndroidManifest.xml</code></pre></figure>
<p>It’ll give you some basic info about what it’s doing, or if it’s got a problem:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">Loading D:\Test\AndroidManifest.xml
Loaded D:\Test\AndroidManifest.xml
Loading manifest node...
Getting manifest attributes...
Found android:versionCode attribute: 1
Updating android:versionName attribute to 2
Writing out updated manifest file: D:\Test\AndroidManifest.xml
Done
Loading D:\Test\AndroidManifest.xml
Loaded D:\Test\AndroidManifest.xml
Loading manifest node...
Getting manifest attributes...
Found android:versionName attribute: 1.0.2.0
Current build revision: '1.0.2.0'
New build revision: 1.0.2.1
Updating android:versionName attribute to 1.0.2.1
Writing out updated manifest file: D:\Test\AndroidManifest.xml
Done</code></pre></figure>
<p>…and now it looks like:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="c"><!-- dummy xml file for /manifest/android:versionName attribute revision # increment testing --></span>
<span class="nt"><manifest</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">android:installLocation=</span><span class="s">"auto"</span> <span class="na">package=</span><span class="s">"io.wislon.testApp"</span>
<span class="na">android:versionCode=</span><span class="s">"2"</span>
<span class="na">android:versionName=</span><span class="s">"1.0.2.1"</span><span class="nt">></span>
<span class="nt"><snip</span> <span class="nt">/></span>
<span class="nt"></manifest></span></code></pre></figure>
<h3 id="assemblyinfoutil-for-updating-net-assemblyinfo-related-files">AssemblyInfoUtil (for updating .NET AssemblyInfo-related files)</h3>
<p>This updates the <code class="language-plaintext highlighter-rouge">AssemblyVersion</code> and <code class="language-plaintext highlighter-rouge">AssemblyFileVersion</code> values commonly found in <code class="language-plaintext highlighter-rouge">AssemblyInfo.cs</code> (or global/shared versions of them) in .NET projects.</p>
<p>It simply opens the file you specify (as text), and searches for for the quartet of numbers. If it finds one (or both), it will parse out the build revision #, increment it, and then write it back to the file.</p>
<h4 id="usage-1">Usage</h4>
<p>Similarly to the AndroidManifestUtil, simply point the <code class="language-plaintext highlighter-rouge">-filename</code> parameter at the file containing the version strings you’re interested in updating:</p>
<p>Run the utility from the command line (or script):</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">AssemblyInfoUtil.exe -filename=.\GlobalVersionInfo.cs.txt</code></pre></figure>
<p><em>(it has a ‘.txt’ extension because Visual Studio kept trying to compile it into the application, and then couldn’t write to it when running the debugger :)). This won’t be a problem when you point it at a ‘real’ file from a command line.</em></p>
<p>This file will look something like:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">using</span> <span class="nn">System.Reflection</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Runtime.CompilerServices</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Runtime.InteropServices</span><span class="p">;</span>
<span class="c1">// sample text file for revision # increment update testing. </span>
<span class="c1">// Application just reads and writes text, it doesn't </span>
<span class="c1">// care about the file extension</span>
<span class="c1">// You can specify all the values or you can default the Build and Revision </span>
<span class="c1">// Numbers by using the '*' as shown below:</span>
<span class="c1">// [assembly: AssemblyVersion("1.0.*")]</span>
<span class="p">[</span><span class="n">assembly</span><span class="p">:</span> <span class="nf">AssemblyVersion</span><span class="p">(</span><span class="s">"1.0.2.0"</span><span class="p">)]</span>
<span class="p">[</span><span class="n">assembly</span><span class="p">:</span> <span class="nf">AssemblyFileVersion</span><span class="p">(</span><span class="s">"1.0.2.0"</span><span class="p">)]</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-text" data-lang="text">D:\test\AssemblyInfoUtil.exe -filename=.\GlobalVersionInfo.cs.txt
Loading .\GlobalVersionInfo.cs.txt
Loaded .\GlobalVersionInfo.cs.txt
Looking for AssemblyVersion line...
Current build revision: '1.0.2.0'
New build revision: 1.0.2.1
Looking for AssemblyFileVersion line...
Current build revision: '1.0.2.0'
New build revision: 1.0.2.1
Revision updated. Writing out new .\GlobalVersionInfo.cs.txt
Done</code></pre></figure>
<p>With the result:</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">AssemblyVersion</span><span class="p">(</span><span class="s">"1.0.2.1"</span><span class="p">)]</span>
<span class="p">[</span><span class="n">assembly</span><span class="p">:</span> <span class="nf">AssemblyFileVersion</span><span class="p">(</span><span class="s">"1.0.2.1"</span><span class="p">)]</span></code></pre></figure>
<p>And that’s pretty much it.</p>
<p>Sure, they can be tweaked to add flags for turn this on, but don’t do this, or ignore this thing and do that instead. But this was the simplest thing that actually worked, and did exactly what I need it to do.</p>
<h3 id="so-where-is-it">So where is it?</h3>
<p>The source code (as always) is on GitHub at <a href="https://github.com/wislon/ci-build-revision-utils">https://github.com/wislon/ci-build-revision-utils</a>.</p>
<p><em>Thanks to <a href="https://disqus.com/by/joshquintus/">Josh Quintus</a> for spotting that I’d forgotten to put the link in!</em></p>
<h3 id="build-loops">Build Loops</h3>
<p>Be careful when you integrate this kind of thing with CI servers or services (like TeamCity) that commit, push and pull code changes, and rebuild on new changes that appear in your source control system.</p>
<p>If your build script increments the number (as it’s supposed to), and then pushes the update back to source control, and your source control triggers a build because of the new code change, you may end up in a build/commit/push/trigger/pull/increment/build loop.</p>
<p><img class="img-responsive img-thumbnail inline" src="https://github.com/wislon/ci-build-revision-utils/raw/master/res/tesseract.gif" alt="CC image of http://en.wikipedia.org/wiki/File:Tesseract.gif" title="build loop mindfuck" /></p>
<p>A hypercube? I can’t even…</p>
<p>This is not your build server’s fault. Nor your source control’s. But it’s not always an easy fix. One of them has to be told how to ignore the other in certain cases, so it doesn’t start the loop.</p>
<p>…and in the spirit of “I needed this now, and since I’m publishing it for me, I may as well put it up for other folks”, Andrew Harcourt (<a href="https://twitter.com/uglybugger">@uglybugger</a>) provides a possible solution over at <a href="https://teamcity-github-filter.azurewebsites.net/">https://teamcity-github-filter.azurewebsites.net/</a>.</p>
<h3 id="license">License</h3>
<p><em>The utils in this repo released under the free-for-all MIT License, so if you want to copy it and do better stuff with it, you go right ahead! :)</em></p>
Xamarin Android - A Staggered Grid Layout, via a RecyclerView. And no Java bindings!2015-03-05T00:00:00+00:00http://blog.wislon.io/posts/2015/03/05/xamarin-android-staggered-grid-layout<p>At the end of my <a href="http://blog.wislon.io/posts/2015/02/21/xamarin-android-gridview-improve-performance">previous blog post</a>, about improving your scrolling performance using a view holder, I said I’d show you how to build a staggered grid layout, pretty much the same way that <a href="http://pinterest.com">Pinterest</a> does on their website.</p>
<p><img class="img-responsive img-thumbnail inline" src="/images/gridviewinfinitescroll/pinterest-grid.jpg" alt="pinterest grid" title="Sample of a Pinterest grid layout" /></p>
<p>The source code for the app in this article is <a href="https://github.com/wislon/xam-gridview-infinite-scroll/tree/staggered-grid">over on GitHub</a>. Note that it’s in a <code class="language-plaintext highlighter-rouge">staggered-grid</code> branch of the orginal, next to the <code class="language-plaintext highlighter-rouge">add-viewholder</code> branch from <a href="http://blog.wislon.io/posts/2015/02/21/xamarin-android-gridview-improve-performance">the previous post</a>. I did it this way so that you can refer to all three, and compare the differences between them.</p>
<p>Personally I think it looks nicer than a strictly regimented set of tiles running vertically and horizontally, like you’d find in a regular grid. It means you don’t have to worry about some items overlapping other items, or having big gaps between some items, or having to ensure that your images and descriptions are limited to specific sizes and lengths.</p>
<p>In my opinion, it also just seems to flow more naturally.</p>
<p>Google not-so-recently introduced something called a <a href="https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html"><code class="language-plaintext highlighter-rouge">RecyclerView</code></a>, and if you’re using something pre-Lollipop, you can get to it via one of their ‘Support Library’ packages (<code class="language-plaintext highlighter-rouge">Xamarin.Android.Support.v7.RecyclerView</code>).</p>
<p>If you’ve been following along from the previous post, then you’ve probably already got an idea, just by looking at the name, of how this new component works.</p>
<h3 id="why-a-recyclerview-whats-wrong-with-the-way-things-were">Why a RecyclerView? What’s wrong with the way things were?</h3>
<p>Different kinds of views which involved displaying multiple items (e.g. <code class="language-plaintext highlighter-rouge">ListView</code>, <code class="language-plaintext highlighter-rouge">GridView</code>) all had to implement the same kind of view-recycling functionality, as well as implementing their own ways of displaying data. This worked, but it means an awful lot of similar code being written over and over for every different kind of view, with slight differences and corner cases and idiosyncracies in each one.</p>
<p>Google has taken a step back, and redone this. Now there’s only one kind of view, which can be coded pretty much exactly the same way for all data sets (generally speaking). All it has to do now is specialise in loading and recycling existing data items. The actual layout of your items has now been pushed down into <a href="https://developer.android.com/reference/android/support/v7/widget/RecyclerView.LayoutManager.html">a <code class="language-plaintext highlighter-rouge">LayoutManager</code> abstract</a>, so now you get things like <code class="language-plaintext highlighter-rouge">LinearLayoutManager</code> and (you guessed it) <code class="language-plaintext highlighter-rouge">StaggeredGridLayoutManager</code>.</p>
<p>OK, enough with the wall of text. Let’s see some code.</p>
<h3 id="install-the-recyclerview-component">Install the RecyclerView Component</h3>
<p>First things first, we need to add the <code class="language-plaintext highlighter-rouge">Xamarin.Android.Support.v7.RecyclerView</code> component from the Xamarin store:</p>
<p><img class="img-responsive img-thumbnail inline" src="/images/gridviewinfinitescroll/all-recyclerview-component.jpg" alt="Xamarin.Android.Support.v7.RecyclerView component" title="Xamarin component store: Xamarin.Android.Support.v7.RecyclerView" /></p>
<p>This will also install the <code class="language-plaintext highlighter-rouge">Xamarin.Android.Support.v4</code> package, via NuGet.</p>
<p><em>If you have problems installing the component, or it complains that some package dependencies weren’t downloaded, ensure that you’re NOT using the ‘Use Latest Platform’ selection in your ‘Compile Using Android version:’ setting in your project. Set it, instead, to a specific API level (21 is recommended), you can leave the other targets as they are.</em></p>
<p><img class="img-responsive img-thumbnail inline" src="/images/gridviewinfinitescroll/api-level-21.jpg" alt="use specific api level" title="Use a specific API level" /></p>
<p>Swap out the <code class="language-plaintext highlighter-rouge">GridView</code> you were using in the <code class="language-plaintext highlighter-rouge">Main.axml</code> (some <code class="language-plaintext highlighter-rouge">GridView</code> attributes removed for brevity):</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><RelativeLayout></span>
<span class="nt"><GridView</span>
<span class="na">android:id=</span><span class="s">"@+id/gridView"</span>
<span class="na">android:layout_width=</span><span class="s">"fill_parent"</span>
<span class="na">android:background=</span><span class="s">"#020202"</span> <span class="nt">/></span>
<span class="nt"></RelativeLayout></span></code></pre></figure>
<p>for the <code class="language-plaintext highlighter-rouge">RecyclerView</code>:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><RelativeLayout></span>
<span class="nt"><android.support.v7.widget.RecyclerView</span>
<span class="na">android:id=</span><span class="s">"@+id/recyclerGridView"</span>
<span class="na">android:scrollbars=</span><span class="s">"vertical"</span>
<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
<span class="na">android:verticalSpacing=</span><span class="s">"8dp"</span> <span class="nt">/></span>
<span class="nt"></RelativeLayout></span></code></pre></figure>
<p><em>(you can play with some of the other attributes on your own, this is about as simple as it gets)</em></p>
<p>Then add a new class inheriting from <code class="language-plaintext highlighter-rouge">RecyclerView.Adapter</code> (I called it <code class="language-plaintext highlighter-rouge">MyRecyclerAdapter</code>). This will replace the <code class="language-plaintext highlighter-rouge">MyGridViewAdapter</code> we used previously.</p>
<p><em>All this <code class="language-plaintext highlighter-rouge">MyRecyclerAdapter</code> does is create <code class="language-plaintext highlighter-rouge">ViewHolder</code> objects which correspond to your data items, and give you a place to bind their ‘control’ or ‘view’ properties to the values within your data items.</em></p>
<p>Add a constructor which allows you to access the <code class="language-plaintext highlighter-rouge">MySimpleItemLoader</code> object we created previously. That way we can access the data items we’ve loaded. Also, add overrides for the <code class="language-plaintext highlighter-rouge">ItemCount</code> property, and <code class="language-plaintext highlighter-rouge">OnCreateViewHolder</code>, <code class="language-plaintext highlighter-rouge">OnBindViewHolder</code> and <code class="language-plaintext highlighter-rouge">GetItemViewType</code> methods:</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">MyRecyclerAdapter</span> <span class="p">:</span> <span class="n">RecyclerView</span><span class="p">.</span><span class="n">Adapter</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">MySimpleItemLoader</span> <span class="n">_mySimpleItemLoader</span><span class="p">;</span>
<span class="k">public</span> <span class="k">override</span> <span class="kt">int</span> <span class="n">ItemCount</span>
<span class="p">{</span>
<span class="c1">// get - snip</span>
<span class="p">}</span>
<span class="k">public</span> <span class="nf">MyRecyclerAdapter</span><span class="p">(</span><span class="n">MySimpleItemLoader</span> <span class="n">mySimpleItemLoader</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_mySimpleItemLoader</span> <span class="p">=</span> <span class="n">mySimpleItemLoader</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">override</span> <span class="n">RecyclerView</span><span class="p">.</span><span class="n">ViewHolder</span> <span class="nf">OnCreateViewHolder</span><span class="p">(</span><span class="n">ViewGroup</span> <span class="n">parent</span><span class="p">,</span> <span class="kt">int</span> <span class="n">viewType</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnBindViewHolder</span><span class="p">(</span><span class="n">RecyclerView</span><span class="p">.</span><span class="n">ViewHolder</span> <span class="n">holder</span><span class="p">,</span> <span class="kt">int</span> <span class="n">position</span><span class="p">)</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">GetItemViewType</span><span class="p">(</span><span class="kt">int</span> <span class="n">position</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Tell the <code class="language-plaintext highlighter-rouge">ItemCount</code> property to simply return the number of items we have in our <code class="language-plaintext highlighter-rouge">MySimpleItemLoader</code>:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="kt">int</span> <span class="n">ItemCount</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Tell <code class="language-plaintext highlighter-rouge">GetItemViewType</code> to give you an integer value back, which you can then use to determine what specific <strong>type of view</strong> you’ll be creating or inflating, to display your data item:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="kt">int</span> <span class="nf">GetItemViewType</span><span class="p">(</span><span class="kt">int</span> <span class="n">position</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">[</span><span class="n">position</span><span class="p">].</span><span class="nf">GetType</span><span class="p">()</span> <span class="p">==</span> <span class="k">typeof</span><span class="p">(</span><span class="n">MySimpleItem</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">return</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p><em>Yes, it’s a little contrived, but this integer can be any value you like, and is the <code class="language-plaintext highlighter-rouge">viewType</code> value that the adapter will pass in to the <code class="language-plaintext highlighter-rouge">OnCreateViewHolder</code> method for you.</em></p>
<p>It’s up to you to use that value, to create (or inflate) the view you need to display your item’s data. It could also be an enum. Or, the Layout resource ID of the view you want to inflate. Or anything else. It’s up to you.</p>
<p><em>Sometimes you’ll also have more than one item type in your list. In our case, we just have one kind of item (a <code class="language-plaintext highlighter-rouge">MySimpleItem</code>), but this can easily be extended to as many different kinds of items as you like.</em></p>
<p>You can then flesh out the <code class="language-plaintext highlighter-rouge">OnCreateViewHolder</code> method, to tell it which view type to inflate, based on the integer you chose for that kind of data item:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="n">RecyclerView</span><span class="p">.</span><span class="n">ViewHolder</span> <span class="nf">OnCreateViewHolder</span><span class="p">(</span><span class="n">ViewGroup</span> <span class="n">parent</span><span class="p">,</span> <span class="kt">int</span> <span class="n">viewType</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">layoutInflater</span> <span class="p">=</span> <span class="n">LayoutInflater</span><span class="p">.</span><span class="nf">From</span><span class="p">(</span><span class="n">parent</span><span class="p">.</span><span class="n">Context</span><span class="p">);</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">viewType</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 0 corresponds to the value we chose to use in `GetItemViewType`</span>
<span class="k">case</span> <span class="m">0</span><span class="p">:</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">view</span> <span class="p">=</span> <span class="n">layoutInflater</span><span class="p">.</span><span class="nf">Inflate</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">MyGridViewCell</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">viewHolder</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MySimpleItemViewHolder</span><span class="p">(</span><span class="n">view</span><span class="p">);</span>
<span class="k">return</span> <span class="n">viewHolder</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">default</span><span class="p">:</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span> <span class="c1">// this may cause you to crash if there's a type in your list you forgot about...</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><em>In this case, we just told it to inflate the <code class="language-plaintext highlighter-rouge">MyGridViewCell</code> we’ve been using all along, and use it and the <code class="language-plaintext highlighter-rouge">MySimpleItemViewHolder</code> as the <code class="language-plaintext highlighter-rouge">ViewHolder</code> for it.</em></p>
<h3 id="tweak-the-mysimpleitemviewholder">Tweak the <code class="language-plaintext highlighter-rouge">MySimpleItemViewHolder</code></h3>
<p>We need to modify the <code class="language-plaintext highlighter-rouge">MySimpleItemViewHolder</code> slightly, so that it fits in with what the <code class="language-plaintext highlighter-rouge">MyRecyclerAdapter</code> expects. This is easy to do, we just change the parent from <code class="language-plaintext highlighter-rouge">Java.Lang.Object</code> to <code class="language-plaintext highlighter-rouge">RecyclerView.ViewHolder</code>, and add a default constructor:</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">MySimpleItemViewHolder</span> <span class="p">:</span> <span class="n">RecyclerView</span><span class="p">.</span><span class="n">ViewHolder</span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">TextView</span> <span class="n">DisplayName</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="n">ImageView</span> <span class="n">Thumbnail</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="nf">MySimpleItemViewHolder</span><span class="p">(</span><span class="n">View</span> <span class="n">itemView</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">itemView</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">DisplayName</span> <span class="p">=</span> <span class="n">itemView</span><span class="p">.</span><span class="n">FindViewById</span><span class="p"><</span><span class="n">TextView</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">tvDisplayName</span><span class="p">);</span>
<span class="n">Thumbnail</span> <span class="p">=</span> <span class="n">itemView</span><span class="p">.</span><span class="n">FindViewById</span><span class="p"><</span><span class="n">ImageView</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">imgThumbnail</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Then you can build out the the <code class="language-plaintext highlighter-rouge">OnBindViewHolder</code> method, which simply takes the data item at the given position, and binds the data in that item to the properties in the ViewHolder (this is exactly the same methodology as in the previous post):</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnBindViewHolder</span><span class="p">(</span><span class="n">RecyclerView</span><span class="p">.</span><span class="n">ViewHolder</span> <span class="n">holder</span><span class="p">,</span> <span class="kt">int</span> <span class="n">position</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">gridItem</span> <span class="p">=</span> <span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">[</span><span class="n">position</span><span class="p">];</span>
<span class="c1">// you could also put an 'item type' enum field or property in your </span>
<span class="c1">// data item and do a 'switch/case' on that. It's less expensive </span>
<span class="c1">// than reflection...</span>
<span class="k">if</span> <span class="p">(</span><span class="n">holder</span><span class="p">.</span><span class="nf">GetType</span><span class="p">()</span> <span class="p">==</span> <span class="k">typeof</span><span class="p">(</span><span class="n">MySimpleItemViewHolder</span><span class="p">))</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">viewHolder</span> <span class="p">=</span> <span class="n">holder</span> <span class="k">as</span> <span class="n">MySimpleItemViewHolder</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">viewHolder</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">viewHolder</span><span class="p">.</span><span class="n">DisplayName</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="n">gridItem</span><span class="p">.</span><span class="n">DisplayName</span><span class="p">;</span>
<span class="n">viewHolder</span><span class="p">.</span><span class="n">Thumbnail</span><span class="p">.</span><span class="nf">SetImageResource</span><span class="p">(</span><span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">Icon</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>And that’s it. You’re done with the <code class="language-plaintext highlighter-rouge">MyRecyclerAdapter</code>. Now we just have to tell the <code class="language-plaintext highlighter-rouge">MainActivity</code> to use the <code class="language-plaintext highlighter-rouge">RecyclerView</code>, tack on an ‘Infinite Scroller’ of some sort, and we’re done!</p>
<h3 id="update-the-mainactivity">Update the <code class="language-plaintext highlighter-rouge">MainActivity</code></h3>
<p>We can chuck out just about all the <code class="language-plaintext highlighter-rouge">GridView</code>-related stuff (from the previous post), and swap it for the (simpler) RecyclerView implementation now.</p>
<p><code class="language-plaintext highlighter-rouge">OnCreate</code> hasn’t changed at all, but <code class="language-plaintext highlighter-rouge">SetupUiElements</code> now does the following:</p>
<ul>
<li>Loads the items as before
<ul>
<li>…with a very minor change which affects the <code class="language-plaintext highlighter-rouge">DisplayName</code>, which I’ll come back to.</li>
</ul>
</li>
<li>Instantiates a new <code class="language-plaintext highlighter-rouge">MyRecyclerAdapter</code></li>
<li>Instantiates a new <code class="language-plaintext highlighter-rouge">StaggeredGridLayoutManager</code> (this comes in the same package as the <code class="language-plaintext highlighter-rouge">RecyclerView</code>), tells it that it will have two columns, and that it’s scrolling vertically (not horizontally).</li>
<li>Attaches the <code class="language-plaintext highlighter-rouge">StaggeredGridLayoutManager</code> and the <code class="language-plaintext highlighter-rouge">MyRecyclerAdapter</code> to the <code class="language-plaintext highlighter-rouge">_recyclerView</code></li>
</ul>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">void</span> <span class="nf">SetupUiElements</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_mySimpleItemLoader</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MySimpleItemLoader</span><span class="p">();</span>
<span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="nf">LoadMoreItems</span><span class="p">(</span><span class="n">ItemsPerPage</span><span class="p">);</span>
<span class="n">_myRecyclerAdapter</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MyRecyclerAdapter</span><span class="p">(</span><span class="n">_mySimpleItemLoader</span><span class="p">);</span>
<span class="n">_recyclerView</span> <span class="p">=</span> <span class="n">FindViewById</span><span class="p"><</span><span class="n">RecyclerView</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">recyclerGridView</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">sglm</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StaggeredGridLayoutManager</span><span class="p">(</span><span class="m">2</span><span class="p">,</span> <span class="n">StaggeredGridLayoutManager</span><span class="p">.</span><span class="n">Vertical</span><span class="p">);</span>
<span class="n">_recyclerView</span><span class="p">.</span><span class="nf">SetLayoutManager</span><span class="p">(</span><span class="n">sglm</span><span class="p">);</span>
<span class="n">_recyclerView</span><span class="p">.</span><span class="nf">SetAdapter</span><span class="p">(</span><span class="n">_myRecyclerAdapter</span><span class="p">);</span>
<span class="c1">// --- snip --- we'll come back to the infinite scrolling in a sec...</span>
<span class="p">}</span></code></pre></figure>
<p>And that’s it.</p>
<p>This may look like a lot of code to implement, but in comparison to how it was done previously, it’s actually saved about 10 percent of the effort. And even more importantly, it’s actually more reusable than the previous version!</p>
<p>The last thing we have to do is implement a scroll listener for the RecyclerView. This is different enough from the previous scroll listener that you’ll need to create a new class inheriting from <code class="language-plaintext highlighter-rouge">RecyclerView.OnScrollListener</code>. It’s not a big deal, and as you’ll see, and it’s actually reusable too.</p>
<h3 id="adding-a-scroll-listener">Adding A Scroll Listener</h3>
<p>Create a new <code class="language-plaintext highlighter-rouge">InfiniteScrollListener</code>, which inherits from <code class="language-plaintext highlighter-rouge">RecyclerView.OnScrollListener</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">InfiniteScrollListener</span> <span class="p">:</span> <span class="n">RecyclerView</span><span class="p">.</span><span class="n">OnScrollListener</span>
<span class="p">{</span>
<span class="c1">// snip private variables which are all initialised from constructor</span>
<span class="c1">/// <summary></span>
<span class="c1">/// How many items away from the end of the list before we need to</span>
<span class="c1">/// trigger a load of the next page of items</span>
<span class="c1">/// </summary></span>
<span class="k">private</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">LoadNextItemsThreshold</span> <span class="p">=</span> <span class="m">8</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">InfiniteScrollListener</span><span class="p">(</span><span class="n">MySimpleItemLoader</span> <span class="n">mySimpleItemLoader</span><span class="p">,</span>
<span class="n">MyRecyclerAdapter</span> <span class="n">myRecyclerAdapter</span><span class="p">,</span>
<span class="n">StaggeredGridLayoutManager</span> <span class="n">staggeredGridLayoutManager</span><span class="p">,</span>
<span class="kt">int</span> <span class="n">itemsPerPage</span><span class="p">,</span>
<span class="n">Action</span> <span class="n">moreItemsLoadedCallback</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_mySimpleItemLoader</span> <span class="p">=</span> <span class="n">mySimpleItemLoader</span><span class="p">;</span>
<span class="n">_myRecyclerAdapter</span> <span class="p">=</span> <span class="n">myRecyclerAdapter</span><span class="p">;</span>
<span class="n">_staggeredGridLayoutManager</span> <span class="p">=</span> <span class="n">staggeredGridLayoutManager</span><span class="p">;</span>
<span class="n">_itemsPerPage</span> <span class="p">=</span> <span class="n">itemsPerPage</span><span class="p">;</span>
<span class="n">_moreItemsLoadedCallback</span> <span class="p">=</span> <span class="n">moreItemsLoadedCallback</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span> </code></pre></figure>
<p>I’ve added a constructor which brings in the objects we need. All of them are ones you’ve seen before, from the <code class="language-plaintext highlighter-rouge">MainActivity</code>, except for the last one, the <code class="language-plaintext highlighter-rouge">moreItemsLoadedCallback</code>. This is simply a method within the <code class="language-plaintext highlighter-rouge">MainActivity</code> which we will use to trigger a ‘data set has changed’ notification whenever the <code class="language-plaintext highlighter-rouge">MySimpleItemLoader</code> loads more items.</p>
<p>This will in turn tell the <code class="language-plaintext highlighter-rouge">MyRecyclerAdapter</code> to alert the <code class="language-plaintext highlighter-rouge">RecyclerView</code> it’s bound to, that it has more items to display, and it needs to update itself.</p>
<p><em>Yes, I know, we <strong>should</strong> also provide a callback which this scroll listener would invoke whenever more data has to be loaded, to separate scroll-listening from data-loading, but this is just an example.</em></p>
<p>Now override the <code class="language-plaintext highlighter-rouge">OnScrolled</code> method of the <code class="language-plaintext highlighter-rouge">InfiniteScrollListener</code></p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnScrolled</span><span class="p">(</span><span class="n">RecyclerView</span> <span class="n">recyclerView</span><span class="p">,</span> <span class="kt">int</span> <span class="n">dx</span><span class="p">,</span> <span class="kt">int</span> <span class="n">dy</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">base</span><span class="p">.</span><span class="nf">OnScrolled</span><span class="p">(</span><span class="n">recyclerView</span><span class="p">,</span> <span class="n">dx</span><span class="p">,</span> <span class="n">dy</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">visibleItemCount</span> <span class="p">=</span> <span class="n">recyclerView</span><span class="p">.</span><span class="n">ChildCount</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">totalItemCount</span> <span class="p">=</span> <span class="n">_myRecyclerAdapter</span><span class="p">.</span><span class="n">ItemCount</span><span class="p">;</span>
<span class="c1">// size of array must be >= the number of items you may have in view at </span>
<span class="c1">// any one time. Should be set to at least the same value as the 'span'</span>
<span class="c1">// parameter in StaggerGridLayoutManager ctor: i.e. 2 or 3 for phone </span>
<span class="c1">// in portrait, 4 or 5 for phone in landscape, assume more for a tablet, etc.</span>
<span class="kt">var</span> <span class="n">positions</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">int</span><span class="p">[</span><span class="m">6</span><span class="p">]</span> <span class="p">{-</span><span class="m">1</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">,};</span>
<span class="kt">var</span> <span class="n">lastVisibleItems</span> <span class="p">=</span> <span class="n">_staggeredGridLayoutManager</span><span class="p">.</span><span class="nf">FindLastCompletelyVisibleItemPositions</span><span class="p">(</span><span class="n">positions</span><span class="p">);</span>
<span class="c1">// remember you'll need to handle re-scrolling to last viewed item, </span>
<span class="c1">// if user flips between landscape/portrait.</span>
<span class="kt">int</span> <span class="n">currentPosition</span> <span class="p">=</span> <span class="n">lastVisibleItems</span><span class="p">.</span><span class="nf">LastOrDefault</span><span class="p">(</span><span class="n">item</span> <span class="p">=></span> <span class="n">item</span> <span class="p">></span> <span class="p">-</span><span class="m">1</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">currentPosition</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">totalItemCount</span> <span class="p">-</span> <span class="n">currentPosition</span> <span class="p"><=</span> <span class="n">LoadNextItemsThreshold</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">lock</span> <span class="p">(</span><span class="n">scrollLockObject</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">CanLoadMoreItems</span> <span class="p">&&</span> <span class="p">!</span><span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">IsBusy</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">IsBusy</span> <span class="p">=</span> <span class="k">true</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">"InfiniteScrollListener"</span><span class="p">,</span> <span class="s">"Load more items requested"</span><span class="p">);</span>
<span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="nf">LoadMoreItems</span><span class="p">(</span><span class="n">_itemsPerPage</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_moreItemsLoadedCallback</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="nf">_moreItemsLoadedCallback</span><span class="p">();</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>There’s nothing special in here, except maybe for the code which determines which items are currently visible, where they are within the entire data set, and whether it’s time to load more items. You can see the <code class="language-plaintext highlighter-rouge">_moreItemsLoadedCallback()</code> being invoked if more items are loaded, and we’ll hook this up in the <code class="language-plaintext highlighter-rouge">MainActivity</code> in a moment.</p>
<p>Yes, the entire block of code within</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"> <span class="k">if</span> <span class="p">(</span><span class="n">totalItemCount</span> <span class="p">-</span> <span class="n">currentPosition</span> <span class="p"><=</span> <span class="n">LoadNextItemsThreshold</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// blah</span>
<span class="p">}</span></code></pre></figure>
<p>at the end there should probably be in a callback into the object depending on this instance of the <code class="language-plaintext highlighter-rouge">InfiniteScrollListener</code>, but as I said, this is just an example.</p>
<p>Just let it go…</p>
<h3 id="attach-the-infinitescrolllistener">Attach the <code class="language-plaintext highlighter-rouge">InfiniteScrollListener</code></h3>
<p>Back in the <code class="language-plaintext highlighter-rouge">MainActivity</code>, we just need to create an instance of this <code class="language-plaintext highlighter-rouge">InfiniteScrollListener</code> and attach it to our <code class="language-plaintext highlighter-rouge">_recyclerView</code>. We do this in the <code class="language-plaintext highlighter-rouge">SetupUiElements</code> method, so now it looks like:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">void</span> <span class="nf">SetupUiElements</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// snip</span>
<span class="n">_recyclerView</span><span class="p">.</span><span class="nf">SetAdapter</span><span class="p">(</span><span class="n">_myRecyclerAdapter</span><span class="p">);</span>
<span class="n">_infiniteScrollListener</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">InfiniteScrollListener</span><span class="p">(</span><span class="n">_mySimpleItemLoader</span><span class="p">,</span>
<span class="n">_myRecyclerAdapter</span><span class="p">,</span>
<span class="n">sglm</span><span class="p">,</span>
<span class="n">ItemsPerPage</span><span class="p">,</span>
<span class="k">this</span><span class="p">.</span><span class="n">UpdateDataAdapter</span><span class="p">);</span>
<span class="n">_recyclerView</span><span class="p">.</span><span class="nf">SetOnScrollListener</span><span class="p">(</span><span class="n">_infiniteScrollListener</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>…and lastly, add the callback which will tell the <code class="language-plaintext highlighter-rouge">_myRecyclerAdapter</code> whenever the <code class="language-plaintext highlighter-rouge">_infiniteScrollListener</code> has loaded more items:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">void</span> <span class="nf">UpdateDataAdapter</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">count</span> <span class="p">=</span> <span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span>
<span class="n">Toast</span><span class="p">.</span><span class="nf">MakeText</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"{0} items"</span><span class="p">,</span> <span class="n">count</span><span class="p">),</span> <span class="n">ToastLength</span><span class="p">.</span><span class="n">Short</span><span class="p">).</span><span class="nf">Show</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">count</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_myRecyclerAdapter</span><span class="p">.</span><span class="nf">NotifyDataSetChanged</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>And that’s it. A staggered grid layout, with infinite scrolling. Hooray! :)</p>
<p><em>Oh yeah. Last thing. I mentioned that I’d made some changes to the <code class="language-plaintext highlighter-rouge">MySimpleItemLoader</code> implementation to make the items different sizes, this forcing the grid to lay out in a staggered fashion.</em></p>
<p><em>All I did was add some random-length lorem ipsum text to each item’s <code class="language-plaintext highlighter-rouge">DisplayName</code> property, along with the item number.</em></p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">readonly</span> <span class="kt">string</span><span class="p">[]</span> <span class="n">_lorem</span> <span class="p">=</span> <span class="s">"Lorem ipsum dolor sit amet, consectetur adipiscing elit."</span><span class="p">.</span><span class="nf">Split</span><span class="p">(</span><span class="sc">' '</span><span class="p">);</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">Random</span> <span class="n">_rand</span><span class="p">;</span>
<span class="c1">// constructor now looks like</span>
<span class="k">public</span> <span class="nf">MySimpleItemLoader</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">MySimpleItems</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">MySimpleItem</span><span class="p">>();</span>
<span class="n">_rand</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Random</span><span class="p">();</span>
<span class="p">}</span>
<span class="c1">// and LoadMoreItems now looks like </span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">LoadMoreItems</span><span class="p">(</span><span class="kt">int</span> <span class="n">itemsPerPage</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="n">CurrentPageValue</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">CurrentPageValue</span> <span class="p">+</span> <span class="n">itemsPerPage</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">string</span> <span class="n">randomLorem</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="s">" "</span><span class="p">,</span> <span class="n">_lorem</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">_rand</span><span class="p">.</span><span class="nf">NextInt</span><span class="p">(</span><span class="n">_lorem</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="m">1</span><span class="p">));</span>
<span class="n">MySimpleItems</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">MySimpleItem</span><span class="p">(){</span><span class="n">DisplayName</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"This is item {0:0000} ({1})"</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">randomLorem</span><span class="p">)});</span>
<span class="p">}</span>
<span class="c1">// snip</span>
<span class="p">}</span></code></pre></figure>
<h3 id="so-what-does-it-look-like">So what does it look like?</h3>
<p>It’s a little clunky, because we’ve not decorated it with cool images, and it could do with a bit of margin and padding love, but here it is, in the Xamarin player emulator:</p>
<p><img class="img-responsive img-thumbnail inline" src="/images/gridviewinfinitescroll/staggered-grid-demo.jpg" alt="staggered grid end result" title="How it looks" /></p>
<h3 id="source-code">Source Code</h3>
<p>The source code for the app in this article is <a href="https://github.com/wislon/xam-gridview-infinite-scroll/tree/staggered-grid">over on GitHub</a>. Note that it’s in a <code class="language-plaintext highlighter-rouge">staggered-grid</code> branch of the orginal, next to the <code class="language-plaintext highlighter-rouge">add-viewholder</code>, branch from <a href="http://blog.wislon.io/posts/2015/02/21/xamarin-android-gridview-improve-performance">the previous post</a>. I did it this way so that you can refer to all three, and compare the differences between them.</p>
<h3 id="references">References</h3>
<p>Some references you may find useful:</p>
<ul>
<li><a href="http://blog.wislon.io/posts/2015/02/21/xamarin-android-gridview-improve-performance">Previous post on implementing on enhancing load/scroll performance </a></li>
<li><a href="http://pinterest.com">Pinterest, because staggered grid layout</a></li>
<li><a href="https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html">RecyclerView</a></li>
<li><a href="https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html">RecyclerView.Adapter</a></li>
<li><a href="https://developer.android.com/reference/android/support/v7/widget/RecyclerView.LayoutManager.html">RecyclerView.LayoutManager</a></li>
</ul>
<p><em>As always, all my source code for these articles is released as OSS under the MIT license, so feel free to use it any way you like. I hope it helps you!</em></p>
Xamarin Android - Improving GridView Recycling Performance2015-02-21T00:00:00+00:00http://blog.wislon.io/posts/2015/02/21/xamarin-android-gridview-improve-performance<p>This is a quick performance-improvement follow-up to my <a href="http://blog.wislon.io/posts/2015/01/27/xamarin-android-gridview-with-infinite-scrolling">previous blog post</a> about implementing infinite scrolling in a Xamarin Android GridView</p>
<h3 id="a-quick-recap">A Quick Recap</h3>
<p>Previously, we were inflating a new instance of a view for every single item, as it was passed to us by the data adapter.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="n">View</span> <span class="nf">GetView</span><span class="p">(</span><span class="kt">int</span> <span class="n">position</span><span class="p">,</span> <span class="n">View</span> <span class="n">convertView</span><span class="p">,</span> <span class="n">ViewGroup</span> <span class="n">parent</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">dataItem</span> <span class="p">=</span> <span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">[</span><span class="n">position</span><span class="p">];</span>
<span class="n">View</span> <span class="n">itemView</span> <span class="p">=</span> <span class="n">convertView</span> <span class="p">??</span> <span class="n">LayoutInflater</span>
<span class="p">.</span><span class="nf">From</span><span class="p">(</span><span class="n">_context</span><span class="p">)</span>
<span class="p">.</span><span class="nf">Inflate</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">MyGridViewCell</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">tvDisplayName</span> <span class="p">=</span> <span class="n">itemView</span><span class="p">.</span><span class="n">FindViewById</span><span class="p"><</span><span class="n">TextView</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">tvDisplayName</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">imgThumbail</span> <span class="p">=</span> <span class="n">itemView</span><span class="p">.</span><span class="n">FindViewById</span><span class="p"><</span><span class="n">ImageView</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">imgThumbnail</span><span class="p">);</span>
<span class="n">imgThumbail</span><span class="p">.</span><span class="nf">SetScaleType</span><span class="p">(</span><span class="n">ImageView</span><span class="p">.</span><span class="n">ScaleType</span><span class="p">.</span><span class="n">CenterCrop</span><span class="p">);</span>
<span class="n">imgThumbail</span><span class="p">.</span><span class="nf">SetPadding</span><span class="p">(</span><span class="m">8</span><span class="p">,</span> <span class="m">8</span><span class="p">,</span> <span class="m">8</span><span class="p">,</span> <span class="m">8</span><span class="p">);</span>
<span class="n">tvDisplayName</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="n">dataItem</span><span class="p">.</span><span class="n">DisplayName</span><span class="p">;</span>
<span class="n">imgThumbail</span><span class="p">.</span><span class="nf">SetImageResource</span><span class="p">(</span><span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">Icon</span><span class="p">);</span>
<span class="k">return</span> <span class="n">itemView</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>This obviously works, and serves to demonstrate how to load an item’s details into a view so that it can be displayed on-screen. The overridden <code class="language-plaintext highlighter-rouge">GetView</code> method kindly takes care of recycling the views and the memory management for us.</p>
<p><img class="img-responsive img-thumbnail inline" src="/images/gridviewinfinitescroll/griditems.png" alt="screenshot" title="screenshot of demo app" /></p>
<p>The silly thing about doing it this way for Every Single Item is that we know exactly what each view will look like; there’s really no need to have to locate each <code class="language-plaintext highlighter-rouge">TextView</code> or <code class="language-plaintext highlighter-rouge">ImageView</code> control, every single time. It would be ideal if we only had to look for it once, and then just reference it every time we need it.</p>
<p>This post will show you how to do just that, using what’s called ‘the view-holder pattern’. In a nutshell, it allows us to capture the <em>instances</em> of those controls on each <code class="language-plaintext highlighter-rouge">MyGridViewCell</code> view, and then just set their text and image source properties directly, the next time we need them.</p>
<p><em>Using the view holder method will increase the performance of your GridView (or any other view you use it with) by between 20% and 40%. When you have to keep your UI fluidly updated at 60fps, this is a massive saving! And it gives you an idea of just how much it costs your app in terms of processing time! When you only have 16 milliseconds to get your stuff ready for the next redraw pass, those precious few milliseconds you just saved can be put to better use elsewhere!</em></p>
<p>We need a view holder for each grid cell item which is (or is about to be) displayed. <em>Not</em> one for every item in the list. Remember, the GridView doesn’t show <strong>all</strong> the items, only the number of items which will fit on the screen. So we won’t need that many of them.</p>
<p>Just how many do we need? Well, it actually doesn’t matter. Or not to us, anyway. We’ll get the <code class="language-plaintext highlighter-rouge">GetView</code> view-generation code to make them as we need them, and then get it to just recycle the ones it’s created.</p>
<p>Some of you may be thinking: this seems a little complex. It’s really not. The <code class="language-plaintext highlighter-rouge">GetView</code> method does <em>all</em> of the heavy lifting for us. It’s less than 10 lines of code.</p>
<p>So what does a view holder look like? Well, it’s a pretty simple object, all it contains is a couple of properties corresponding to the controls we need to keep references to. In this case, we need to just keep track of a <code class="language-plaintext highlighter-rouge">TextView</code> and an <code class="language-plaintext highlighter-rouge">ImageView</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">MySimpleItemViewHolder</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="k">public</span> <span class="n">TextView</span> <span class="n">DisplayName</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="n">ImageView</span> <span class="n">Thumbnail</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>
<p>They don’t come much simpler. Note that it inherits from <strong><code class="language-plaintext highlighter-rouge">Java.Lang.Object</code></strong>. This is important, and you’ll see why in a second.</p>
<h4 id="managing-the-mysimpleitemviewholder-instances">Managing the <code class="language-plaintext highlighter-rouge">MySimpleItemViewHolder</code> instances</h4>
<p>The <code class="language-plaintext highlighter-rouge">GetView</code> method already recycles the views for us. We only need to make a couple of minor adjustments to it, in order for it to be able to make and recycle the view holders for us, too.</p>
<p>Briefly, this breaks down into the following operations:</p>
<ul>
<li>If we don’t have a current view, then make one, and make a <code class="language-plaintext highlighter-rouge">MySimpleItemViewHolder</code> instance to go with it.</li>
<li>Locate the controls we need on the view (in our case <code class="language-plaintext highlighter-rouge">TextView</code> and <code class="language-plaintext highlighter-rouge">ImageView</code>).</li>
<li>Assign those controls to the corresponding properties in the <code class="language-plaintext highlighter-rouge">MySimpleItemViewHolder</code> instance.</li>
<li>Set the values in those properties, based on our incoming item’s DisplayName and Image source.
<ul>
<li>This will immediately have the effect of setting them on the controls (since they’re now pointed to by those properties).</li>
</ul>
</li>
<li>Attach the <code class="language-plaintext highlighter-rouge">MySimpleItemViewHolder</code> instance <em>to the current view</em> by putting it in the <code class="language-plaintext highlighter-rouge">Tag</code> property of the current view.
<ul>
<li><strong>This is the key step.</strong></li>
</ul>
</li>
</ul>
<p>And if we do have a current view (i.e. <code class="language-plaintext highlighter-rouge">currentView != null</code>)?</p>
<ul>
<li>Extract the <code class="language-plaintext highlighter-rouge">MySimpleItemViewHolder</code> instance from the <code class="language-plaintext highlighter-rouge">currentView.Tag</code> property.</li>
<li>Set the values of that instance’s <code class="language-plaintext highlighter-rouge">DisplayName</code> and <code class="language-plaintext highlighter-rouge">Thumbnail</code> image properties to the data provided by the incoming data item.</li>
<li>We’re done.</li>
</ul>
<p>Rinse. Repeat.</p>
<p><em>Being able to place the <code class="language-plaintext highlighter-rouge">MySimpleItemViewHolder</code> into the <code class="language-plaintext highlighter-rouge">Tag</code> property is why we inherited it from <code class="language-plaintext highlighter-rouge">Java.Lang.Object</code>. If we’d inherited from <code class="language-plaintext highlighter-rouge">System.Object</code>, we’d have got an assignment error. And no, it’s not possible to cast a <code class="language-plaintext highlighter-rouge">System.Object</code> to (or from) a <code class="language-plaintext highlighter-rouge">Java.Lang.Object</code>.</em></p>
<p>So what does it look like?</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="n">View</span> <span class="nf">GetView</span><span class="p">(</span><span class="kt">int</span> <span class="n">position</span><span class="p">,</span> <span class="n">View</span> <span class="n">convertView</span><span class="p">,</span> <span class="n">ViewGroup</span> <span class="n">parent</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">dataItem</span> <span class="p">=</span> <span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">[</span><span class="n">position</span><span class="p">];</span>
<span class="n">View</span> <span class="n">itemView</span> <span class="p">=</span> <span class="n">convertView</span><span class="p">;</span>
<span class="n">MySimpleItemViewHolder</span> <span class="n">viewHolder</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">itemView</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// just use the one we made before, it came back from the recycler</span>
<span class="c1">// still attached to the convertView object</span>
<span class="n">viewHolder</span> <span class="p">=</span> <span class="p">(</span><span class="n">MySimpleItemViewHolder</span><span class="p">)</span><span class="n">itemView</span><span class="p">.</span><span class="n">Tag</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">viewHolder</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MySimpleItemViewHolder</span><span class="p">();</span>
<span class="n">itemView</span> <span class="p">=</span> <span class="n">LayoutInflater</span><span class="p">.</span><span class="nf">From</span><span class="p">(</span><span class="n">_context</span><span class="p">).</span><span class="nf">Inflate</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">MyGridViewCell</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
<span class="c1">// locate and init the ImageView control on the layout.</span>
<span class="kt">var</span> <span class="n">imgThumbail</span> <span class="p">=</span> <span class="n">itemView</span><span class="p">.</span><span class="n">FindViewById</span><span class="p"><</span><span class="n">ImageView</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">imgThumbnail</span><span class="p">);</span>
<span class="n">imgThumbail</span><span class="p">.</span><span class="nf">SetScaleType</span><span class="p">(</span><span class="n">ImageView</span><span class="p">.</span><span class="n">ScaleType</span><span class="p">.</span><span class="n">CenterCrop</span><span class="p">);</span>
<span class="n">imgThumbail</span><span class="p">.</span><span class="nf">SetPadding</span><span class="p">(</span><span class="m">8</span><span class="p">,</span> <span class="m">8</span><span class="p">,</span> <span class="m">8</span><span class="p">,</span> <span class="m">8</span><span class="p">);</span>
<span class="c1">// assign it to the viewHolder object's ImageView property</span>
<span class="n">viewHolder</span><span class="p">.</span><span class="n">Thumbnail</span> <span class="p">=</span> <span class="n">imgThumbail</span><span class="p">;</span>
<span class="c1">// assign the layout's TextView to viewHolder object's TextView property</span>
<span class="n">viewHolder</span><span class="p">.</span><span class="n">DisplayName</span> <span class="p">=</span> <span class="n">itemView</span><span class="p">.</span><span class="n">FindViewById</span><span class="p"><</span><span class="n">TextView</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">tvDisplayName</span><span class="p">);</span>
<span class="c1">// the crucial step: attach the viewHolder to the view, so we can reuse it.</span>
<span class="n">itemView</span><span class="p">.</span><span class="n">Tag</span> <span class="p">=</span> <span class="n">viewHolder</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// set the values of the viewHolder properties to those </span>
<span class="c1">// provided by the dataItem</span>
<span class="n">viewHolder</span><span class="p">.</span><span class="n">DisplayName</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="n">dataItem</span><span class="p">.</span><span class="n">DisplayName</span><span class="p">;</span>
<span class="n">viewHolder</span><span class="p">.</span><span class="n">Thumbnail</span><span class="p">.</span><span class="nf">SetImageResource</span><span class="p">(</span><span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">Icon</span><span class="p">);</span>
<span class="k">return</span> <span class="n">itemView</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<h3 id="are-there-any-changes-to-the-infinite-scrolling-aspect">Are there any changes to the infinite scrolling aspect?</h3>
<p>No. All the heavy lifting is being done by our view holders. Nothing else changes. Hooray! :)</p>
<h3 id="conclusion">Conclusion</h3>
<p>We are able to use the <code class="language-plaintext highlighter-rouge">GetView</code> method’s ability to recycle the view objects to our advantage. When it recycles every view, it doesn’t (and will not) clear out the <code class="language-plaintext highlighter-rouge">Tag</code> object attached to each one. This gives us a safe place to store our references to the controls on each view, so we can reuse them.</p>
<p>Finally, the View Holder Pattern is one of Google’s best practice methods of improving scrolling-view-recycling performance. So they’re not going to clobber your <code class="language-plaintext highlighter-rouge">Tag</code> contents. They put it there for you to use for stuff just like this. Watch <a href="https://www.youtube.com/watch?v=N6YdwzAvwOA">Romain Guy’s Google I/O 2009 talk on improving performance</a> for more about view recycling and other layout and performance tips. Yes, it’s a little dated, but still relevant, and worth an hour of your time.</p>
<p>The full source is available in <a href="https://github.com/wislon/xam-gridview-infinite-scroll/tree/add-viewholder">this GitHub repo</a>. Please note: Rather than create a brand new repo for such a minor change, I’ve made these changes in the <em><code class="language-plaintext highlighter-rouge">add-viewholder</code></em> branch.</p>
<p><em>In my next post, I plan on demonstrating how to implement a staggered grid (like they do over at <a href="http://pinterest.com">pinterest.com</a> ) using the new Android <code class="language-plaintext highlighter-rouge">RecyclerView</code> and <code class="language-plaintext highlighter-rouge">StaggeredGridLayout</code>. Yes it’ll do infinite scrolling. And no, you won’t have to do any Java bindings.</em></p>
<h3 id="references">References</h3>
<p>Some references you may find useful:</p>
<ul>
<li><a href="http://blog.wislon.io/posts/2015/01/27/xamarin-android-gridview-with-infinite-scrolling">Previous post on implementing an infinite scrolling GridView</a></li>
<li><a href="https://www.youtube.com/watch?v=N6YdwzAvwOA">Romain Guy’s talk on making your Android App UI fast at Google I/O</a></li>
<li><a href="http://developer.xamarin.com/guides/android/user_interface/grid_view/">Xamarin Grid View sample</a></li>
</ul>
<p><em>Everything in here is released as OSS under the MIT license, so feel free to use it any way you like.</em></p>
Xamarin Android - Build a Gridview With Infinite Scrolling2015-01-27T00:00:00+00:00http://blog.wislon.io/posts/2015/01/27/xamarin-android-gridview-with-infinite-scrolling<p>I needed to implement a GridView display, which had two columns of items. Each item consisted of <code class="language-plaintext highlighter-rouge">ImageView</code> for a small thumbnail image (160x160), above a small <code class="language-plaintext highlighter-rouge">TextView</code> with a basic description. Easy enough, and there’s a ton of examples out there on how to do it.</p>
<p>However, my list of items is extremely large (it runs into the thousands). There’s way too many items to load in one go, and even if I could, the data set is dynamic, and there’s more of them being added all the time.</p>
<p>This is a problem that’s already been solved by ‘infinite scrolling’ or ‘endless scrolling’. If you’ve ever used any form of mobile app or website which has a large number of items in a list, you’ll already be familiar with the concept: Basically you’re shown an initial list of items, but as you scroll down, more items are loaded automatically, and displayed. All you have to do is just keep on scrolling…</p>
<p>How to get this working in a GridView though?</p>
<p>It was surprisingly difficult to find information on this. There were tons of examples showing how to do it in a ListView with ListAdapters and ArrayAdapters, but all the ones I was looking at had a static list of items. They’d start out with a list of items, and not add any more to it. Great to demonstrate how a GridView works, but infinite scrolling… not so much.</p>
<h3 id="the-basic-principles">The basic principles:</h3>
<ul>
<li>Load the first set of items</li>
<li>Capture something from the scrolling event which said “we’re getting close to the end, go and get more items!”</li>
<li>Load the items, hopefully quickly enough so the user doesn’t bump up against the bottom of the current list.</li>
<li>Tell the DataAdapter to notify everything attached to it that its data set has changed (coz it’s got new items in it).</li>
<li>Tell the GridView to invalidate its views (i.e. cells) which causes it to repaint everything (and update the scrollbar proportions, list count indicator, etc.)</li>
</ul>
<h3 id="getting-started">Getting Started</h3>
<p>To get started in this demo, we first need a backing data store which has the ability to load more items on demand. This could be a web service, an API call, a database query, an Observable collection of some sort, pretty much anything.</p>
<p>For the purposes of this example, I’m just going to use a generic list of <code class="language-plaintext highlighter-rouge">MySimpleItem</code> (as you may be able to tell from the name, it’s just a toy object).</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">MySimpleItem</span>
<span class="p">{</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">DisplayName</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>
<p>I’m going to keep a list of these in an object which I’ve created called <code class="language-plaintext highlighter-rouge">MySimpleItemLoader</code>, another toy object which has a method I can call whenever I like, to get more more <code class="language-plaintext highlighter-rouge">MySimpleItems</code>. Easy:</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">MySimpleItemLoader</span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">List</span><span class="p"><</span><span class="n">MySimpleItem</span><span class="p">></span> <span class="n">MySimpleItems</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="kt">bool</span> <span class="n">IsBusy</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">CurrentPageValue</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">bool</span> <span class="n">CanLoadMoreItems</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="nf">MySimpleItemLoader</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">MySimpleItems</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">MySimpleItem</span><span class="p">>();</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">LoadMoreItems</span><span class="p">(</span><span class="kt">int</span> <span class="n">itemsPerPage</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="n">CurrentPageValue</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">CurrentPageValue</span> <span class="p">+</span> <span class="n">itemsPerPage</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="n">MySimpleItems</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">MySimpleItem</span><span class="p">(){</span><span class="n">DisplayName</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"This is item {0:0000}"</span><span class="p">,</span> <span class="n">i</span><span class="p">)});</span>
<span class="p">}</span>
<span class="c1">// normally you'd check to see if the number of items returned is less than </span>
<span class="c1">// the number requested, i.e. you've run out, and then set this accordinly.</span>
<span class="n">CanLoadMoreItems</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="n">CurrentPageValue</span> <span class="p">=</span> <span class="n">MySimpleItems</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span>
<span class="n">IsBusy</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Let’s just unpack this quickly:</p>
<ul>
<li>The <code class="language-plaintext highlighter-rouge">List<MySimpleItem> MySimpleItems</code> is obviously just my data store.</li>
<li>The <code class="language-plaintext highlighter-rouge">CurrentPageValue</code> is just a grouping counter we can use to determine how far into the dataset we are, to get the next set (‘page’) of items.</li>
<li>The <code class="language-plaintext highlighter-rouge">CanLoadMoreItems</code> property is a flag to indicate whether there are more items than the ones that were just loaded.</li>
</ul>
<p><em>The <code class="language-plaintext highlighter-rouge">CanLoadMoreItems</code> variable is pretty dimwitted. For the purposes of this demo it’s always going to be true. But normally you could do a quick check to see if the last number of items returned was less than the number you asked for; if it was, then there’s obviously no more items.</em></p>
<ul>
<li>As for the <code class="language-plaintext highlighter-rouge">LoadMoreItems</code> method… I’m sure you’ll figure it out :)</li>
</ul>
<p>So far so good.</p>
<p>Now we need something to act as a middle-man between the GridView and the list of MySimpleItems. Enter the DataAdapter class of object. This acts as a kind of bridge between the list of data on the one side, and the display of it, on the other. It also handles things like recycling of views to save and reuse system resources, a topic I’ll go into in a future post where I’ll show how to display more than one kind of item in the grid, speed up the display, and so on.</p>
<h3 id="setting-up-the-data-adapter">Setting Up The Data Adapter</h3>
<p>There are many different kinds of DataAdapters, but we’re going to use a derivative of the standard <code class="language-plaintext highlighter-rouge">BaseAdapter</code>, which I called (imaginatively) <code class="language-plaintext highlighter-rouge">MyGridViewAdapter</code>. And here it is:</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">MyGridViewAdapter</span> <span class="p">:</span> <span class="n">BaseAdapter</span><span class="p"><</span><span class="n">MySimpleItem</span><span class="p">></span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">MySimpleItemLoader</span> <span class="n">_mySimpleItemLoader</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">Context</span> <span class="n">_context</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">MyGridViewAdapter</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="n">MySimpleItemLoader</span> <span class="n">mySimpleItemLoader</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_context</span> <span class="p">=</span> <span class="n">context</span><span class="p">;</span>
<span class="n">_mySimpleItemLoader</span> <span class="p">=</span> <span class="n">mySimpleItemLoader</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">override</span> <span class="n">View</span> <span class="nf">GetView</span><span class="p">(</span><span class="kt">int</span> <span class="n">position</span><span class="p">,</span> <span class="n">View</span> <span class="n">convertView</span><span class="p">,</span> <span class="n">ViewGroup</span> <span class="n">parent</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// we'll come back here in a sec...</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">override</span> <span class="kt">long</span> <span class="nf">GetItemId</span><span class="p">(</span><span class="kt">int</span> <span class="n">position</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">position</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="n">Count</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">override</span> <span class="n">MySimpleItem</span> <span class="k">this</span><span class="p">[</span><span class="kt">int</span> <span class="n">position</span><span class="p">]</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">[</span><span class="n">position</span><span class="p">];</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>First I inherit from <code class="language-plaintext highlighter-rouge">BaseAdapter<MySimpleItem></code>. This just makes it a little bit easier for the data adapter, means less object casting for us, and lets the parent of the <code class="language-plaintext highlighter-rouge">BaseAdapter</code> know what kind of objects it’s going to have to juggle internally so it can do all the things we shouldn’t have to care about.</p>
<p>In the constructor we pass in an instance of the <code class="language-plaintext highlighter-rouge">MySimpleItemLoader</code> (which will have the first set of items already in it).</p>
<p>We also pass in the the Android application <code class="language-plaintext highlighter-rouge">Context</code>. This is only so we can use it in overridden <code class="language-plaintext highlighter-rouge">GetView</code> method to help us inflate the correct type of grid view item display resource for each item in the grid, as it scrolls into view. More on this in a bit.</p>
<p>And then we just have to override a couple of abstract methods (which the <code class="language-plaintext highlighter-rouge">BaseAdapter</code> will force us to do), because it’s stuff that it needs us to implement.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="kt">long</span> <span class="nf">GetItemId</span><span class="p">(</span><span class="kt">int</span> <span class="n">position</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">position</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="n">Count</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">override</span> <span class="n">MySimpleItem</span> <span class="k">this</span><span class="p">[</span><span class="kt">int</span> <span class="n">position</span><span class="p">]</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">[</span><span class="n">position</span><span class="p">];</span> <span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Pretty standard. These methods just provide you with places you can do additional work, if you need to.</p>
<p>In our case the only ones we’re really interested in are the <code class="language-plaintext highlighter-rouge">Count</code> property, which tells the adapter how many items there are currently in the list. I guess it helps the adapter allocate memory, set the correct proportions on the scroll bars, stuff like that. This guy gets invoked every time we fire a <code class="language-plaintext highlighter-rouge">NotifyDataSetChanged</code> at the data adapter, to say ‘hey, you’ve got more items in your data thingy!’.</p>
<p>The <code class="language-plaintext highlighter-rouge">MySimpleItem this</code> is just a self-referencing property which returns the <code class="language-plaintext highlighter-rouge">MySimpleItem</code> at <code class="language-plaintext highlighter-rouge">position</code> in the list.</p>
<p>Now, coming back to the <code class="language-plaintext highlighter-rouge">GetView</code> method we’ve overridden. This is the core of the adapter and handles the linking of items in the <code class="language-plaintext highlighter-rouge">MySimpleItemLoader.MySimpleItems</code> with the actual grid view display items. For the sake of brevity (and clarity), I’ve left out some of the performance-enhancing bits. I’ll cover those in another post. But for what we need, this will suffice:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="n">View</span> <span class="nf">GetView</span><span class="p">(</span><span class="kt">int</span> <span class="n">position</span><span class="p">,</span> <span class="n">View</span> <span class="n">convertView</span><span class="p">,</span> <span class="n">ViewGroup</span> <span class="n">parent</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">item</span> <span class="p">=</span> <span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">MySimpleItems</span><span class="p">[</span><span class="n">position</span><span class="p">];</span>
<span class="n">View</span> <span class="n">itemView</span> <span class="p">=</span> <span class="n">convertView</span> <span class="p">??</span> <span class="n">LayoutInflater</span><span class="p">.</span><span class="nf">From</span><span class="p">(</span><span class="n">_context</span><span class="p">).</span><span class="nf">Inflate</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">MyGridViewCell</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">tvDisplayName</span> <span class="p">=</span> <span class="n">itemView</span><span class="p">.</span><span class="n">FindViewById</span><span class="p"><</span><span class="n">TextView</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">tvDisplayName</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">imgThumbail</span> <span class="p">=</span> <span class="n">itemView</span><span class="p">.</span><span class="n">FindViewById</span><span class="p"><</span><span class="n">ImageView</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">imgThumbnail</span><span class="p">);</span>
<span class="n">imgThumbail</span><span class="p">.</span><span class="nf">SetScaleType</span><span class="p">(</span><span class="n">ImageView</span><span class="p">.</span><span class="n">ScaleType</span><span class="p">.</span><span class="n">CenterCrop</span><span class="p">);</span>
<span class="n">imgThumbail</span><span class="p">.</span><span class="nf">SetPadding</span><span class="p">(</span><span class="m">8</span><span class="p">,</span> <span class="m">8</span><span class="p">,</span> <span class="m">8</span><span class="p">,</span> <span class="m">8</span><span class="p">);</span>
<span class="n">tvDisplayName</span><span class="p">.</span><span class="n">Text</span> <span class="p">=</span> <span class="n">item</span><span class="p">.</span><span class="n">DisplayName</span><span class="p">;</span>
<span class="n">imgThumbail</span><span class="p">.</span><span class="nf">SetImageResource</span><span class="p">(</span><span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">Icon</span><span class="p">);</span>
<span class="k">return</span> <span class="n">itemView</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>It’s pretty straightforward:</p>
<ul>
<li>It gets the current <code class="language-plaintext highlighter-rouge">MySimpleItem</code> at <code class="language-plaintext highlighter-rouge">position</code>.</li>
<li>It checks to see if the <code class="language-plaintext highlighter-rouge">currentView</code> is null. <code class="language-plaintext highlighter-rouge">currentView</code> is an instance of a grid view item which may have been used before, and been recycled. If it hasn’t been recycled, it’ll be null, and we need to make a new one.</li>
<li>If we need to make a new one, then here’s where we use our application <code class="language-plaintext highlighter-rouge">Context</code> to inflate the simple xml which describes our individual grid view item ‘cell’. In our case, it’s just an <code class="language-plaintext highlighter-rouge">ImageView</code> for a thumbnail, and a <code class="language-plaintext highlighter-rouge">TextView</code> for the <code class="language-plaintext highlighter-rouge">MySimpleItem.DisplayName</code> text.</li>
<li>If the <code class="language-plaintext highlighter-rouge">currentView</code> is <strong>not</strong> null, it means it’s come out of the recycling bucket, and we can just reuse it.</li>
</ul>
<p>For more on the ‘Recycling’, check out the <strong>References</strong> section at the end of this post.</p>
<p>Either way, we now have a grid view item. The next step is just to locate the image and text view controls on it (using <code class="language-plaintext highlighter-rouge">FindViewById</code>), and set the relevant property on each to the value that’s come from the <code class="language-plaintext highlighter-rouge">item</code> variable.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"> tvDisplayName.Text = item.DisplayName;
imgThumbail.SetImageResource(Resource.Drawable.Icon);</code></pre></figure>
<p><em>As I mentioned before, there’s ways to speed this operation up. I’ve omitted them for now.</em></p>
<p>At the time of writing I didn’t have a list of images, or image urls, so I’m simply setting the <code class="language-plaintext highlighter-rouge">ImageView</code> to the application’s icon.</p>
<p>With some of the attributes removed for brevity, the <code class="language-plaintext highlighter-rouge">MyGridViewCell.xml</code> file looks something like:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><LinearLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">xmlns:tools=</span><span class="s">"http://schemas.android.com/tools"</span>
<span class="na">android:layout_width=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="nt">></span>
<span class="nt"><ImageView</span>
<span class="na">android:id=</span><span class="s">"@+id/imgThumbnail"</span>
<span class="na">android:layout_width=</span><span class="s">"160dp"</span>
<span class="na">android:layout_height=</span><span class="s">"160dp"</span>
<span class="na">android:padding=</span><span class="s">"5dp"</span>
<span class="nt">></span>
<span class="nt"></ImageView></span>
<span class="nt"><TextView</span>
<span class="na">android:id=</span><span class="s">"@+id/tvDisplayName"</span>
<span class="na">android:layout_width=</span><span class="s">"fill_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:gravity=</span><span class="s">"center"</span>
<span class="na">android:textSize=</span><span class="s">"18sp"</span>
<span class="nt">></span>
<span class="nt"></TextView></span>
<span class="nt"></LinearLayout></span></code></pre></figure>
<p><em>(the full source is available in <a href="https://github.com/wislon/xam-gridview-infinite-scroll">this GitHub repo</a>)</em></p>
<p>And we’re done with <code class="language-plaintext highlighter-rouge">MyGridViewAdapter</code>.</p>
<h3 id="displaying-the-grid-items">Displaying the Grid Items</h3>
<p>Finally we need to make a <code class="language-plaintext highlighter-rouge">GridView</code>, hook it up to the <code class="language-plaintext highlighter-rouge">MyGridViewAdapter</code>, and monitor the <code class="language-plaintext highlighter-rouge">GridView</code> scrolling events to tell our <code class="language-plaintext highlighter-rouge">MySimpleDataLoader</code> to load more stuff.</p>
<p>We can do all of this in the standard MainActivity with just a few lines of code:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="p">[</span><span class="nf">Activity</span><span class="p">(</span><span class="n">Label</span> <span class="p">=</span> <span class="s">"GridViewInfiniteScroll"</span><span class="p">,</span> <span class="n">MainLauncher</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">Icon</span> <span class="p">=</span> <span class="s">"@drawable/icon"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="n">Activity</span>
<span class="p">{</span>
<span class="k">private</span> <span class="n">GridView</span> <span class="n">_gridView</span><span class="p">;</span>
<span class="k">private</span> <span class="n">MySimpleItemLoader</span> <span class="n">_mySimpleItemLoader</span><span class="p">;</span>
<span class="k">private</span> <span class="n">MyGridViewAdapter</span> <span class="n">_gridviewAdapter</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="kt">object</span> <span class="n">_scrollLockObject</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">object</span><span class="p">();</span>
<span class="k">private</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">ItemsPerPage</span> <span class="p">=</span> <span class="m">24</span><span class="p">;</span>
<span class="k">private</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">LoadNextItemsThreshold</span> <span class="p">=</span> <span class="m">6</span><span class="p">;</span>
<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">Main</span><span class="p">);</span>
<span class="n">_mySimpleItemLoader</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MySimpleItemLoader</span><span class="p">();</span>
<span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="nf">LoadMoreItems</span><span class="p">(</span><span class="n">ItemsPerPage</span><span class="p">);</span>
<span class="n">_gridView</span> <span class="p">=</span> <span class="n">FindViewById</span><span class="p"><</span><span class="n">GridView</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">gridView</span><span class="p">);</span>
<span class="n">_gridviewAdapter</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MyGridViewAdapter</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">_mySimpleItemLoader</span><span class="p">);</span>
<span class="n">_gridView</span><span class="p">.</span><span class="n">Adapter</span> <span class="p">=</span> <span class="n">_gridviewAdapter</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Nothing spectacular. It simply instantiates a new <code class="language-plaintext highlighter-rouge">MySimpleItemLoader</code> and tells it to load the first set of items, ready for immediate display. Then it hooks up a new instance of the <code class="language-plaintext highlighter-rouge">MyGridViewAdapter</code> with the application <code class="language-plaintext highlighter-rouge">Context</code> and our loader instance to the <code class="language-plaintext highlighter-rouge">GridView</code>.</p>
<p>And if you load it up at this point, you’ll get a grid view with 24 items in it.</p>
<p><img class="img-responsive img-thumbnail inline" src="/images/gridviewinfinitescroll/griditems.png" alt="screenshot" title="screenshot of demo app" /></p>
<p>It’ll scroll, but when you get to the bottom, it’ll just stop. That’s fine, if you only have 24 items. But we have an infinite list of items to display!</p>
<h3 id="adding-the-infinite-scroll">Adding The Infinite Scroll</h3>
<p>We need to add one more method to this activity to get it to do this, and attach it to the <code class="language-plaintext highlighter-rouge">GridView.Scroll</code> event. Easily done.</p>
<p>Add an event handler somewhere after you’ve instantiated the <code class="language-plaintext highlighter-rouge">GridView</code>:</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">Main</span><span class="p">);</span>
<span class="n">_mySimpleItemLoader</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">MySimpleItemLoader</span><span class="p">();</span>
<span class="c1">// snip...</span>
<span class="n">_gridView</span><span class="p">.</span><span class="n">Scroll</span> <span class="p">+=</span> <span class="n">KeepScrollingInfinitely</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">KeepScrollingInfinitely</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">AbsListView</span><span class="p">.</span><span class="n">ScrollEventArgs</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">lock</span> <span class="p">(</span><span class="n">_scrollLockObject</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">mustLoadMore</span> <span class="p">=</span> <span class="n">args</span><span class="p">.</span><span class="n">FirstVisibleItem</span> <span class="p">+</span> <span class="n">args</span><span class="p">.</span><span class="n">VisibleItemCount</span> <span class="p">>=</span> <span class="n">args</span><span class="p">.</span><span class="n">TotalItemCount</span> <span class="p">-</span> <span class="n">LoadNextItemsThreshold</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">mustLoadMore</span> <span class="p">&&</span> <span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">CanLoadMoreItems</span> <span class="p">&&</span> <span class="p">!</span><span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">IsBusy</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="n">IsBusy</span> <span class="p">=</span> <span class="k">true</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="n">TAG</span><span class="p">,</span> <span class="s">"Requested to load more items"</span><span class="p">);</span>
<span class="n">_mySimpleItemLoader</span><span class="p">.</span><span class="nf">LoadMoreItems</span><span class="p">(</span><span class="n">ItemsPerPage</span><span class="p">);</span>
<span class="n">_gridviewAdapter</span><span class="p">.</span><span class="nf">NotifyDataSetChanged</span><span class="p">();</span>
<span class="n">_gridView</span><span class="p">.</span><span class="nf">InvalidateViews</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>What does this do?</p>
<ul>
<li>The scroll event fires <strong>MANY</strong> times per second, so we need a way to ensure that the first event-on-a-thread that comes along has exclusive access to this method. Otherwise we’ll have a massive re-entrancy problem and maybe even a stack overflow (when was the last time you saw one of those?!). So we lock it across all threads for the duration of that method.</li>
<li>There’s a quick check to see if we’ve reached the point at which we need to load more stuff. I’ve set an arbitrary threshold of 6 items from the end. We use a combination of <code class="language-plaintext highlighter-rouge">args.FirstVisibleItem</code>, <code class="language-plaintext highlighter-rouge">args.VisibleItemCount</code>, <code class="language-plaintext highlighter-rouge">args.TotalItemCount</code> and <code class="language-plaintext highlighter-rouge">LoadNextItemsThreshold</code> to figure out where we are.</li>
<li>If we <em>have</em> reached the point at which we <em>do</em> need to load more items, we go and get them.</li>
<li>Then we tell the <code class="language-plaintext highlighter-rouge">MyGridViewAdapter</code> that its backing data set has changed, so it can do any background maintenance work it needs to do,</li>
<li>Finally we tell the <code class="language-plaintext highlighter-rouge">GridView</code> to update whatever it’s currently displaying (which will also make sure the scrollbar is set to the correct proportions and position).</li>
</ul>
<p>And if you load your items fast enough, the user won’t even notice. Obviously if you’re on a slow data connection or something else is bottlenecking your retrieval, you may need to set a higher reload threshold, so it goes earlier. Or temporarily display some sort of loading indicator so that your users don’t think they’ve reached the end of the list, or that your app has locked up.</p>
<p><strong>And get your next set of items asynchronously!</strong></p>
<p>That’s it. We’re done.</p>
<p>The source code for this example is sitting over in <a href="https://github.com/wislon/xam-gridview-infinite-scroll">this GitHub repo</a></p>
<p><em>I initially started out with using an adapter based on the <code class="language-plaintext highlighter-rouge">ArrayAdapter</code>. I’m dealing with a list of items, and an array is pretty close to a list, right? Man, that was a poor choice. Nothing I did could get the ArrayAdapter’s NotifyDataSetChanged method call to actually do anything. And without that, the GridView’s call to invalidate its views did nothing either. More items would load, but none would display. I found a workaround, where I’d recreate a brand new ArrayAdapter with all the new items in it, every time, and reattach it to the GridView. That works too, but it’s really clunky, and I felt dirty doing it.</em></p>
<h3 id="references">References</h3>
<p>Some references you may find useful:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=N6YdwzAvwOA">Romain Guy’s talk on making your Android App UI fast at Google I/O</a></li>
<li><a href="http://developer.xamarin.com/guides/android/user_interface/grid_view/">Xamarin Grid View sample</a></li>
<li><a href="http://motzcod.es/post/107620279512/load-more-items-at-end-of-listview-in">James Montemagno Loading More Items</a></li>
</ul>
<p><em>Everything in here is released as OSS under the MIT license, so feel free to use it any way you like.</em></p>
Xamarin and Android - KitKat and Your External SD Card2014-11-20T00:00:00+00:00http://blog.wislon.io/posts/2014/11/20/xamarin-and-android-kitkat-and-your-external-sd-card<p>This is an update on my <a href="http://blog.wislon.io/posts/2014/09/28/xamarin-and-android-how-to-use-your-external-removable-sd-card">previous blog post</a> about locating and using your external, removable SD card on versions of Android based on Jellybean (and below).</p>
<p>Android KitKat (version 4.4) introduced some extra security around writing to your external SD card, and in a nutshell, it really just comes down to not being able to place files where you like any more. You can still write to it, but there is a rather nasty caveat which I’ll get to later on.</p>
<p><img class="img-responsive img-thumbnail inline" src="/images/externalsdcard/permissionseverywhere.jpg" alt="permissions... permissions everywhere..." title="permissions... permissions everywhere..." /></p>
<p>Pre-KitKat, you could still create directories and files at the “root” of the mount point of the SD card.</p>
<p>But not any more. You can still do this on the INTERNAL disk. I don’t know if this will change in future, but I am going to assume it will.</p>
<p>So:</p>
<ul>
<li>You can still write to the external, removable SD card, just not in the root.</li>
<li>On the external SD card, you have to place your files in a folder based off:</li>
</ul>
<p><code class="language-plaintext highlighter-rouge">/storage/(external_sd_mount)/Android/data/(your.package.name)/</code></p>
<p><em>(you have full access rights in here, you can do pretty much anything you need to)</em></p>
<ul>
<li>You can write to the internal disk exactly as you’ve always been able to (as far as I’ve been able to test it). This part hasn’t changed.</li>
<li>You don’t appear to need the <code class="language-plaintext highlighter-rouge">WRITE_EXTERNAL_STORAGE</code> permission any more either, if you’re targeting only KitKat and above. Best to leave it in though, if targeting devices lower on the food chain.</li>
</ul>
<p>So in my spare time, I’ve done some fiddling, some research, built and broke a bunch of things, and coerced a couple of people (<a href="https://twitter.com/dfrencham">@dfrencham</a> and <a href="https://twitter.com/meligy">@meligy</a>) who have KitKat devices into helping me out. Thanks guys! :)</p>
<p>If you’ve been struggling with this, yourself, then I’ll save you some time, and show you how to go about it.</p>
<p>The key to accessing the external, removable SD card was there all along (or at least since the SDK version 19 for KitKat was released). Why did I have so much trouble getting it to work? Well, I was overthinking it, and trying to force the solution down the <code class="language-plaintext highlighter-rouge">proc/mounts</code> path I’d had to choose before. A one-size-fits-all approach struck me as more elegant, but it was not to be…</p>
<p>Things have actually become easier though…</p>
<p>Google introduced a new API method call in KitKat called <code class="language-plaintext highlighter-rouge">GetExternalFilesDirs</code>. This returns an array of all ‘permanently’ mounted ‘external’ paths to where you can write data. I use the term ‘external’ in quotes because I think it’s a bit of a misnomer (and I had a bit of a grumble about it in the earlier post).</p>
<p>You’ll have noticed the similarity of <code class="language-plaintext highlighter-rouge">GetExternalFilesDirs</code> to <code class="language-plaintext highlighter-rouge">GetExternalFilesDir</code> (added in level 8). One letter difference, and probably why I missed it.</p>
<p>You’ll be pleased to know that <code class="language-plaintext highlighter-rouge">GetExternalFilesDirs</code> does pretty much the same thing. The very first item in the list of paths it returns is exactly the same path as would have been returned by <code class="language-plaintext highlighter-rouge">GetExternalFilesDir</code>.</p>
<p>However, any paths in the list <em>after</em> that will be paths for externally mounted media, like your external, removable SD card.</p>
<p>If I make this call on an LG G3 running KitKat, I get:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">File[] externalFilesDirs = Android.App.Application.Context.GetExternalFilesDirs(null);
// Array.ForEach(externalFilesDirs, efd => Log.Debug("ESH", "Path: {0} - Mount State: {1}", efd.AbsolutePath, Android.OS.Environment.GetStorageState(efd)));
// D/ESH(31949): Path: /storage/emulated/0/Android/data/TestExternalSSD.TestExternalSSD/files - Mount State: mounted
// D/ESH(31949): Path: /storage/external_SD/Android/data/TestExternalSSD.TestExternalSSD/files - Mount State: mounted <-- YES!!! :)</code></pre></figure>
<p>Hooray!</p>
<p><em>Note that drives which are mounted “transiently”, such as those which might be connected via a USB cable (or something else) are not listed. It appears that the disk needs to be physically attached to the device, for instance in an SD card slot, inside the case.</em></p>
<p>Does this mean you can simply switch out <code class="language-plaintext highlighter-rouge">GetExternalFilesDir</code> for <code class="language-plaintext highlighter-rouge">GetExternalFilesDirs</code>, and just use the first item for your internal disk, as a one-call-to-rule-them-all method? Not quite. Because the API was only introduced with KitKat, you have to do an Android version check, to see which version of Android you’re running, or it’ll crash.</p>
<p>Viva fragmentation.</p>
<p>The check is easily done tho. All we have to do is something along the lines of:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="c1">// for jellybean and below</span>
<span class="k">if</span> <span class="p">(</span><span class="n">Android</span><span class="p">.</span><span class="n">OS</span><span class="p">.</span><span class="n">Build</span><span class="p">.</span><span class="n">VERSION</span><span class="p">.</span><span class="n">SdkInt</span> <span class="p"><=</span> <span class="n">BuildVersionCodes</span><span class="p">.</span><span class="n">JellyBeanMr2</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// and call exactly what we did before</span>
<span class="n">_path</span> <span class="p">=</span> <span class="n">ExternalSdStorageHelper</span><span class="p">.</span><span class="nf">GetExternalSdCardPath</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">else</span> <span class="c1">// for kitkat and above</span>
<span class="p">{</span>
<span class="c1">// I made a new method for legibility's sake </span>
<span class="n">_path</span> <span class="p">=</span> <span class="n">ExternalSdStorageHelper</span><span class="p">.</span><span class="nf">GetExternalSdCardPathEx</span><span class="p">();</span>
<span class="p">}</span></code></pre></figure>
<p>And what does <code class="language-plaintext highlighter-rouge">ExternalSdStorageHelper.GetExternalSdCardPathEx()</code> do?</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">static</span> <span class="kt">string</span> <span class="nf">GetExternalSdCardPathEx</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">File</span><span class="p">[]</span> <span class="n">externalFilesDirs</span> <span class="p">=</span> <span class="n">Android</span><span class="p">.</span><span class="n">App</span><span class="p">.</span><span class="n">Application</span><span class="p">.</span><span class="n">Context</span><span class="p">.</span><span class="nf">GetExternalFilesDirs</span><span class="p">(</span><span class="k">null</span><span class="p">);</span>
<span class="c1">// Array.ForEach(externalFilesDirs, efd => Log.Debug("ExternalSDStorageHelper", "Path: {0} - Mount State: {1}", efd.AbsolutePath, Android.OS.Environment.GetStorageState(efd)));</span>
<span class="c1">// Path: /storage/emulated/0/Android/data/TestExternalSSD.TestExternalSSD/files - Mount State: mounted</span>
<span class="c1">// Path: /storage/external_SD/Android/data/TestExternalSSD.TestExternalSSD/files - Mount State: mounted <-- this is the one we want!</span>
<span class="k">if</span> <span class="p">(</span><span class="n">externalFilesDirs</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
<span class="p">{</span>
<span class="kt">string</span> <span class="n">externalSdPath</span> <span class="p">=</span> <span class="n">externalFilesDirs</span><span class="p">.</span><span class="n">Length</span> <span class="p">></span> <span class="m">1</span> <span class="p">?</span> <span class="n">externalFilesDirs</span><span class="p">[</span><span class="m">1</span><span class="p">].</span><span class="n">AbsolutePath</span> <span class="p">:</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span> <span class="c1">// we only want the external drive, otherwise nothing!</span>
<span class="c1">// note that in the case of an SD card, ONLY the path it returns is writeable. You can </span>
<span class="c1">// drop back to the "root" as we did with the internal one above, but that's readonly.</span>
<span class="k">return</span> <span class="n">externalSdPath</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p><em>There’s more info and comments in the code file in the <a href="https://github.com/wislon/xamarin-android-use-removable-sd-card">repo</a>, I took some of it out brevity’s sake. You can also take a look at the <a href="http://developer.android.com/reference/android/support/v4/content/ContextCompat.html">ContextCompat</a> shim which Google provides for some backward compatibility tweaking, but this adds a hefty chunk to your final APK, which may be an issue for you.</em></p>
<p>And that’s pretty much all there is to it.</p>
<p>Note that there is also another API method call introduced in Android Lollipop (level 21), named <code class="language-plaintext highlighter-rouge">GetExternalMediaDirs</code>, but I haven’t got around to upgrading any of my devices to give this a try (yet!). And no, I don’t trust the emulators for anything hardware related.</p>
<h3 id="warning-data-loss">Warning! Data loss!</h3>
<p>Back to the caveat I mentioned at the start of this post. If you decide to store your information on the external, removable SD card in KitKat (or above), the only location you can write to will be removed if the application is uninstalled! There will be no warning, that data will simply be clobbered. So you may want to give your users the option of either backing up their files or moving them to somewhere else (e.g. on the internal) before uninstalling. Application <strong>updates</strong> are fine, everything is left as is, but if your user uninstalls your app, their data is history!</p>
<p>If you are a developer, and using this SD card location, then every time you relaunch your app using the debugger will also wipe everything out, since the debugger uninstalls the old version and installs the new one. Ask me how I know :)</p>
<p>The newest version of the testing app I built before is now up on <a href="https://github.com/wislon/xamarin-android-use-removable-sd-card">GitHub</a>, with the additional functionality built in. About the only other tweak I made to it was to explicitly call the <code class="language-plaintext highlighter-rouge">IsWritable</code> method after getting the path, instead of making calling it inside the path discovery routine.</p>
<h3 id="references">References</h3>
<p>Some references I found useful:</p>
<p><a href="http://developer.android.com/reference/android/content/Context.html#getExternalFilesDirs%28java.lang.String%29">GetExternalFilesDirs</a></p>
<p><a href="http://developer.android.com/reference/android/content/Context.html#getExternalFilesDir%28java.lang.String%29">GetExternalFilesDir</a></p>
<p><a href="http://developer.android.com/reference/android/content/Context.html#getExternalMediaDirs%28%29">GetExternalMediaDirs</a></p>
<p><a href="http://developer.android.com/reference/android/support/v4/content/ContextCompat.html">ContextCompat</a></p>
<p><em>Everything in here is released as OSS under the MIT license, so feel free to use it any way you like.</em></p>
Xamarin and Android - How to use your external removable SD card2014-09-28T00:00:00+00:00http://blog.wislon.io/posts/2014/09/28/xamarin-and-android-how-to-use-your-external-removable-sd-card<p>This quick post demonstrates how to how to access the external, <strong>REMOVABLE</strong> SD card plugged into your Android device.</p>
<p>The code example is done in Xamarin/C#, but exactly the same principles apply in Java, so it should be extremely easy for an Android Java dev to understand exactly what’s going on, and apply the same methods.</p>
<h3 id="background">Background</h3>
<p>There’s been a lot of questions on StackOverflow (and other places) about how to access the ‘External Storage’ on an Android device. Most people seem to want to know how to read from (and write to) <strong>the SD card</strong> the’ve plugged into the SD card slot in their device.</p>
<p>This sample project shows you how to do just that.</p>
<p><em>Sadly, due to the gamification and ‘first post!’ mentality of some SO posts, a lot of developers go charging off, half-cocked, and answer the question they think is being asked, instead of the one actually being asked. That’s why there’s so many duplicates of this type of question. And so most of the answers appear to get the concept of ‘External’ and ‘Removable’ mixed up, and then start preaching about how the ‘External Storage’ isn’t actually EXTERNAL storage, it’s just storage separate from the inaccessible, INTERNAL storage of the device. And sometimes that maybe the questioner is being a moron for getting the two concepts confused, and this may be a duplicate of that question over there. So most of those answers are essentially identical in implementation, and completely have the wrong end of the stick. Or the wrong stick.</em></p>
<p>Just about all developers who build for Android understand that the ‘External’ storage does NOT refer to the removable SD card, even tho the file system sometimes refers to it as such. For the purposes of this post, I am going to refer to this as the ‘removable’ storage, and it will refer to a removable SD card, which a person has physically plugged into the device.</p>
<h3 id="android-is-linux-based">Android is Linux-based</h3>
<p>The operating system underneath the Dalvik VM is a Linux based one. I hope that didn’t come as a shock to anyone.</p>
<p>That means that even if there’s not a direct Xamarin or Android call to do what you need to do, there may sometimes be a more Linux-y way of doing it.</p>
<p>The first question we’ve got is: how do we determine whether we even have an removable SD card plugged into the device?
This is both simultaneously easy to answer, but harder to solve. Easy, because we can make a single call to see what file systems we have mounted. And hard, because those damned device manufacturers haven’t seen fit to settle on a standardised naming convention for a <em>removable</em> SD card which is plugged into the device.</p>
<p>To make matters worse, some manufacturers have hijacked the name of the <em>internal</em> storage, and often called it something stupid like <code class="language-plaintext highlighter-rouge">sdcard0</code>.</p>
<p>On most Linux systems, there’s a ‘file’ in the <code class="language-plaintext highlighter-rouge">/proc/</code> directory called <code class="language-plaintext highlighter-rouge">mounts</code>, which contains a (sometimes very long) list of all the file systems mounted on the device.</p>
<p>It can be treated like a a text file. And it doesn’t require any special user permissions or device rooting to read it (though writing to it is another matter!).</p>
<p>Here’s an example, from my ye anciente Samsung Galaxy S2, which has an SD card slot in it, and a micro-SD card plugged into it (the list is shortened substantially):</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">rootfs / rootfs ro,relatime 0 0
tmpfs /dev tmpfs rw,nosuid,relatime,mode=755 0 0
devpts /dev/pts devpts rw,relatime,mode=600 0 0
blah blah more mounted stuff blah
:
/dev/block/mmcblk0p10 /data ext4 rw,nosuid,nodev,noatime,barrier=1,
/dev/block/mmcblk0p4 /mnt/.lfs j4fs rw,relatime 0 0
/dev/block/vold/259:3 /storage/sdcard0 vfat rw,dirsync,nosuid,nodev ...
/dev/block/vold/179:9 /storage/extSdCard vfat rw,dirsync,nosuid,nodev ...
:</code></pre></figure>
<p>Notice the two last lines there. Both of those are tagged as ‘sdcard’, and we know one of them has to be our removable one. My money is on <code class="language-plaintext highlighter-rouge">extSdCard</code>…</p>
<p>Can you see where we’re going with this? Good.</p>
<h3 id="reading-the-file-system">Reading the File System</h3>
<p>Remember to turn on <code class="language-plaintext highlighter-rouge">READ_EXTERNAL_STORAGE</code> and <code class="language-plaintext highlighter-rouge">WRITE_EXTERNAL_STORAGE</code> permissions in your <code class="language-plaintext highlighter-rouge">Manifest.xml</code>, or you won’t get the results you expect.</p>
<p>Now we can do a simple call to <code class="language-plaintext highlighter-rouge">System.IO.File.ReadAllText("/proc/mounts")</code> to read the text out of this file, and store it in a string. We can then parse the string to look for things like <code class="language-plaintext highlighter-rouge">storage</code>, <code class="language-plaintext highlighter-rouge">sdcard</code>, <code class="language-plaintext highlighter-rouge">vfat</code>, <code class="language-plaintext highlighter-rouge">ext</code> and anything else we think would be a good indicator. And if we can find <strong>all of that on one file-system line</strong>, then the file system mounted there is very likely a good candidate for our <em>removable</em> SD card.</p>
<p>So, after some hacky string parsing (because quick-and-dirty-hacks are how we roll) from the example <a href="https://github.com/wislon/xamarin-android-use-removable-sd-card">I’ve put together at this repo</a>:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="kt">var</span> <span class="n">candidateProcMountEntries</span> <span class="p">=</span> <span class="n">procMounts</span><span class="p">.</span><span class="nf">Split</span><span class="p">(</span><span class="sc">'\n'</span><span class="p">,</span> <span class="sc">'\r'</span><span class="p">).</span><span class="nf">ToList</span><span class="p">();</span>
<span class="n">candidateProcMountEntries</span><span class="p">.</span><span class="nf">RemoveAll</span><span class="p">(</span><span class="n">s</span> <span class="p">=></span> <span class="n">s</span><span class="p">.</span><span class="nf">IndexOf</span><span class="p">(</span><span class="s">"storage"</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">)</span> <span class="p"><</span> <span class="m">0</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">bestCandidate</span> <span class="p">=</span> <span class="n">candidateProcMountEntries</span>
<span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">s</span> <span class="p">=></span> <span class="n">s</span><span class="p">.</span><span class="nf">IndexOf</span><span class="p">(</span><span class="s">"ext"</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">)</span> <span class="p">>=</span> <span class="m">0</span>
<span class="p">&&</span> <span class="n">s</span><span class="p">.</span><span class="nf">IndexOf</span><span class="p">(</span><span class="s">"sd"</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">)</span> <span class="p">>=</span> <span class="m">0</span>
<span class="p">&&</span> <span class="n">s</span><span class="p">.</span><span class="nf">IndexOf</span><span class="p">(</span><span class="s">"vfat"</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">)</span> <span class="p">>=</span> <span class="m">0</span><span class="p">);</span>
<span class="c1">// e.g. /dev/block/vold/179:9 /storage/extSdCard vfat rw,dirsync,nosuid, blah</span>
<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">bestCandidate</span><span class="p">))</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">sdCardEntries</span> <span class="p">=</span> <span class="n">bestCandidate</span><span class="p">.</span><span class="nf">Split</span><span class="p">(</span><span class="sc">' '</span><span class="p">);</span>
<span class="n">sdCardEntry</span> <span class="p">=</span> <span class="n">sdCardEntries</span><span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">s</span> <span class="p">=></span> <span class="n">s</span><span class="p">.</span><span class="nf">IndexOf</span><span class="p">(</span><span class="s">"/storage/"</span><span class="p">,</span> <span class="n">System</span><span class="p">.</span><span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">)</span> <span class="p">>=</span> <span class="m">0</span><span class="p">);</span>
<span class="k">return</span> <span class="p">!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">sdCardEntry</span><span class="p">)</span> <span class="p">?</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"{0}"</span><span class="p">,</span> <span class="n">sdCardEntry</span><span class="p">)</span> <span class="p">:</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>…we end up with <code class="language-plaintext highlighter-rouge">/storage/extSdCard</code>.</p>
<p>It’s quite likely that this may not be quite good enough, because the manufacturers of an HTC or Xiaomi phone might have called it something different (for example if they’d mounted it at <code class="language-plaintext highlighter-rouge">/storage/bob</code> we’d be pretty much screwed). But that being said, they can’t be too free-spirited with it, or the Android OS wouldn’t be able to identify it either, and you wouldn’t be able to copy your music or movies to it, and then play them.</p>
<p>OK, so now we’ve established how to locate the SD card (assuming we have one plugged in!).</p>
<h3 id="can-we-write-to-it">Can We Write To It?</h3>
<p>How do we know if it’s writeable? Well, let’s just try and write to it…</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">static</span> <span class="kt">bool</span> <span class="nf">IsWriteable</span><span class="p">(</span><span class="kt">string</span> <span class="n">pathToTest</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">bool</span> <span class="n">result</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">string</span> <span class="n">someTestText</span> <span class="p">=</span> <span class="s">"some test text"</span><span class="p">;</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="kt">string</span> <span class="n">testFile</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"{0}/{1}.txt"</span><span class="p">,</span> <span class="n">pathToTest</span><span class="p">,</span> <span class="n">Guid</span><span class="p">.</span><span class="nf">NewGuid</span><span class="p">());</span>
<span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">File</span><span class="p">.</span><span class="nf">WriteAllText</span><span class="p">(</span><span class="n">testFile</span><span class="p">,</span> <span class="n">someTestText</span><span class="p">);</span>
<span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">File</span><span class="p">.</span><span class="nf">Delete</span><span class="p">(</span><span class="n">testFile</span><span class="p">);</span>
<span class="n">result</span> <span class="p">=</span> <span class="k">true</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="c1">// argh! did we do something stupid? no? then it's not writeable</span>
<span class="p">{</span>
<span class="n">Log</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="s">"ExternalSDStorageHelper"</span><span class="p">,</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"Exception: {0}\r\nMessage: {1}\r\nStack Trace: {2}"</span><span class="p">,</span> <span class="n">ex</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="n">ex</span><span class="p">.</span><span class="n">StackTrace</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p><em>You can create directories off this path to the SD card now too, now that you have it. Exactly the same way you programmatically create them everywhere else</em></p>
<p>OK, that works. But this is on an Android 4.1.2 device (not a KitKat one). And it works all the way up to, and including, Android 4.3 (level 18) We’ll get to the KitKat part later. For now, it’s OK, we have a removable SD card, and we can write to it. Hooray!</p>
<h3 id="how-much-space-have-we-got">How Much Space Have We Got?</h3>
<p>We’re not quite done yet though. The standard Xamarin/Android calls for determining how much total/available/usable/free space is available are limited to only those mount points that Android would let you access. Like the inappropriately and stupidly named <code class="language-plaintext highlighter-rouge">ExternalStorageDirectory</code>, which isn’t what we thought it was, and isn’t where we want.</p>
<p>Back to Linux we go. There’s a <code class="language-plaintext highlighter-rouge">statvfs()</code> call we can make to get some basic info about the mounted file system, and luckily for us, there’s a handy wrapper object called <code class="language-plaintext highlighter-rouge">Android.OS.StatFS</code>, which does all that heavy lifting for us. Makes it <em>really</em> easy to get to!</p>
<p>I’m interested in total space, available space and free space (the last two aren’t always interchangeable), so I made an object called <code class="language-plaintext highlighter-rouge">FileSystemBlockInfo</code>, which I’ll be using to store these bits of information</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">FileSystemBlockInfo</span>
<span class="p">{</span>
<span class="c1">/// <summary></span>
<span class="c1">/// The path you asked to check file allocation blocks for</span>
<span class="c1">/// </summary></span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Path</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="c1">/// <summary></span>
<span class="c1">/// The file system block size, in bytes, for the given path</span>
<span class="c1">/// </summary></span>
<span class="k">public</span> <span class="kt">double</span> <span class="n">BlockSizeBytes</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="c1">/// <summary></span>
<span class="c1">/// Total size of the file system at the given path</span>
<span class="c1">/// </summary></span>
<span class="k">public</span> <span class="kt">double</span> <span class="n">TotalSizeBytes</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="c1">/// <summary></span>
<span class="c1">/// Available size of the file system at the given path</span>
<span class="c1">/// </summary></span>
<span class="k">public</span> <span class="kt">double</span> <span class="n">AvailableSizeBytes</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="c1">/// <summary></span>
<span class="c1">/// Total free size of the file system at the given path</span>
<span class="c1">/// </summary></span>
<span class="k">public</span> <span class="kt">double</span> <span class="n">FreeSizeBytes</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>
<p>Nothing special. Moving on…</p>
<p>Using the StatFS object in <code class="language-plaintext highlighter-rouge">ExternalSdStorageHelper.GetFileSystemBlockInfo()</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="n">FileSystemBlockInfo</span> <span class="nf">GetFileSystemBlockInfo</span><span class="p">(</span><span class="kt">string</span> <span class="n">path</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">statFs</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StatFs</span><span class="p">(</span><span class="n">path</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">fsbi</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">FileSystemBlockInfo</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">Build</span><span class="p">.</span><span class="n">VERSION</span><span class="p">.</span><span class="n">SdkInt</span> <span class="p">>=</span> <span class="n">Android</span><span class="p">.</span><span class="n">OS</span><span class="p">.</span><span class="n">BuildVersionCodes</span><span class="p">.</span><span class="n">JellyBeanMr2</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">fsbi</span><span class="p">.</span><span class="n">Path</span> <span class="p">=</span> <span class="n">path</span><span class="p">;</span>
<span class="n">fsbi</span><span class="p">.</span><span class="n">BlockSizeBytes</span> <span class="p">=</span> <span class="n">statFs</span><span class="p">.</span><span class="n">BlockSizeLong</span><span class="p">;</span>
<span class="n">fsbi</span><span class="p">.</span><span class="n">TotalSizeBytes</span> <span class="p">=</span> <span class="n">statFs</span><span class="p">.</span><span class="n">BlockCountLong</span><span class="p">*</span><span class="n">statFs</span><span class="p">.</span><span class="n">BlockSizeLong</span><span class="p">;</span>
<span class="n">fsbi</span><span class="p">.</span><span class="n">AvailableSizeBytes</span> <span class="p">=</span> <span class="n">statFs</span><span class="p">.</span><span class="n">AvailableBlocksLong</span><span class="p">*</span><span class="n">statFs</span><span class="p">.</span><span class="n">BlockSizeLong</span><span class="p">;</span>
<span class="n">fsbi</span><span class="p">.</span><span class="n">FreeSizeBytes</span> <span class="p">=</span> <span class="n">statFs</span><span class="p">.</span><span class="n">FreeBlocksLong</span><span class="p">*</span><span class="n">statFs</span><span class="p">.</span><span class="n">BlockSizeLong</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span> <span class="c1">// this was deprecated in API level 18 (Android 4.3), so if your device is below level 18, this is what will be used instead.</span>
<span class="p">{</span>
<span class="n">fsbi</span><span class="p">.</span><span class="n">Path</span> <span class="p">=</span> <span class="n">path</span><span class="p">;</span>
<span class="c1">// you may want to disable warning about obsoletes, earlier versions of Android are using the deprecated versions</span>
<span class="n">fsbi</span><span class="p">.</span><span class="n">BlockSizeBytes</span> <span class="p">=</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">statFs</span><span class="p">.</span><span class="n">BlockSize</span><span class="p">;</span>
<span class="n">fsbi</span><span class="p">.</span><span class="n">TotalSizeBytes</span> <span class="p">=</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">statFs</span><span class="p">.</span><span class="n">BlockCount</span> <span class="p">*</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">statFs</span><span class="p">.</span><span class="n">BlockSize</span><span class="p">;</span>
<span class="n">fsbi</span><span class="p">.</span><span class="n">FreeSizeBytes</span> <span class="p">=</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">statFs</span><span class="p">.</span><span class="n">FreeBlocks</span> <span class="p">*</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">statFs</span><span class="p">.</span><span class="n">BlockSize</span><span class="p">;</span>
<span class="n">fsbi</span><span class="p">.</span><span class="n">AvailableSizeBytes</span> <span class="p">=</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span> <span class="n">statFs</span><span class="p">.</span><span class="n">AvailableBlocks</span> <span class="p">*</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">statFs</span><span class="p">.</span><span class="n">BlockSize</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">fsbi</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p><em>We have to do the check for the Android version, because the OS calls being made have been deprecated for older versions. The ‘old style’, pre Android level 18 didn’t use the <code class="language-plaintext highlighter-rouge">Long</code> suffixes, so if you try and call use those on anything below Android 4.3, it’ll crash on you, telling you that that those methods are unavailable. Viva fragmentation!</em></p>
<p>And that gives us the info we need to be able to track how much space we have, and have available to play with.</p>
<p>The example solution in this repo ties this all together, with a simple UI to show the mount point of a removable SD card (as pertains to my SGS I, SGS II and a couple of other devices I’ve tried).</p>
<p><img class="img-responsive img-thumbnail inline" src="/images/externalsdcard/screenshot.png" alt="screenshot" title="screenshot of demo app" /></p>
<p>I hope it’s of use to someone. And if you find any funky-named removable SD card mounts which the hacky parsing we did above wouldn’t have caught, please add it, and send through pull request! :)</p>
<p><img class="img-responsive img-thumbnail inline" src="/images/externalsdcard/google_y_u_no.jpg" alt="Google: y u no allow access to sd card?!" title="Google: y u no allow access to sd card?!" /></p>
<h3 id="google-y-u-no-allow-access-to-sd-cards-in-kitkat">Google Y U No Allow Access To SD Cards in KitKat?!</h3>
<p>One caveat: <strong>Android KitKat (aka 4.4, aka level 19, and presumably higher)</strong> won’t let you <strong>write</strong> to wherever you like on this removable disk. Ironically, earlier, more primitive versions of Android do.</p>
<p>I’ve written a new <a href="http://wislon.io/posts/2014/11/20/xamarin-and-android-kitkat-and-your-external-sd-card">blog post about this</a> and how to get to the SD card, so take a look if you’re interested.</p>
<p>The general consensus seems to be that this was done for security reasons. Basically to stop rogue apps from writing to places they don’t own. There are some patches and apps which can fix this for you, but they all appear to require rooting your device. KitKat doesn’t block you completely from writing to the SD card, but it means you have to make sure your data is stored somewhere “private” to the app. This will be someplace like <code class="language-plaintext highlighter-rouge">[externalSdpath]/Android/data/your.package.name/files/yourFolderName</code>.</p>
<p>There’s some more information on <a href="https://www.google.com/search?q=android+kitkat+SD+card">Google</a>, some apps <a href="https://play.google.com/store/apps/details?id=com.geeksoft.extcardpatch&hl=en">here</a> and <a href="https://play.google.com/store/apps/details?id=nextapp.sdfix&hl=en">here</a> which may help you figure out what you’ll need to do to make it work. And there’s some explanations of what other app developers have done to ‘fix’ their apps over here at <a href="http://www.beyondpod.com/Android/help/FAQKitKatSDReadOnly.htm">BeyondPod</a> and <a href="https://www.doubletwist.com/help/question/what-are-the-changes-to-sd-card-support-in-android-44-kitkat/">doubleTwist</a>.</p>
<p><em>Note: I’m not affiliated with any of these devs or apps</em></p>
<p>Unfortunately, it looks like your users will now also lose whatever data your app has generated, if you put your data in these sandboxed folders, and then they uninstall the app, the data gets removed as well. Though apparently app upgrades are fine.</p>
<p>Opinions differ as well, since it seems that you may be able to create directories elsewhere too, with the proviso that your app is the only one that can write to those directories… I guess I need to do a little more testing…</p>
<p><em>Everything in here is released as OSS under the MIT license, so feel free to use it any way you like.</em></p>
Bent iPhones, Broken HealthKits and Eating Your Own Dog Food2014-09-25T00:00:00+00:00http://blog.wislon.io/posts/2014/09/25/bent-iphones-broken-healthkits-and-eating-your-own-dog-food<p>Unless you’ve been living under a rock the past couple of weeks, you’ve probably noticed that there’s been a disturbing amount of fail coming out of Apple recently.</p>
<p>We’ve had the iCloud celebrity selfie hack. We’ve had New! Improved! iPhones that structurally don’t survive even being placed in your pocket. And just within the last couple of days, an iOS 8.0.1 operating system upgrade that was supposed to sort out some HealthKit issues, but just borked the phones it was installed on.</p>
<p>They’ve sold over 10 million units over the last week or so.</p>
<p><em>10 million!</em></p>
<p>That’s 10 million beautiful little slices of abject failure.</p>
<p><img class="img-responsive img-thumbnail inline" src="/images/bent/mouldiphone6.png" alt="mouldiphone" /></p>
<p>And, you may even be one of the unfortunate few whose selfies ended up on 4chan, then bent their brand spanking new iPhone just by putting it in a pants pocket, and then upgraded the software that obliterated the phone’s ability to perform its primary function.</p>
<p>At this point you must be starting to wonder just what the fuck they’re doing over there at Infinite Loop?! Are they even paying attention?</p>
<p>Twitter surely is, judging by the pics that have cropped up just in the last day or two (under the hashtags <a href="https://twitter.com/hashtag/bendgate?src=hash">#bendgate</a>, <a href="https://twitter.com/hashtag/bendghazi?src=hash">#bendghazi</a>, <a href="https://twitter.com/hashtag/willitbend?src=hash">#willitbend</a>, the list goes on…) :</p>
<ul class="list-unstyled list-inline">
<li style="padding-top: 10px; padding-bottom: 10px;"><img class="img-responsive img-thumbnail inline" src="/images/bent/bent01.jpg" alt="bent1" /></li>
<li style="padding-top: 10px; padding-bottom: 10px;"><img class="img-responsive img-thumbnail inline" src="/images/bent/bent02.png" alt="bent2" /></li>
<li style="padding-top: 10px; padding-bottom: 10px;"><img class="img-responsive img-thumbnail inline" src="/images/bent/bent03.png" alt="bent3" /></li>
</ul>
<p>…and some people have had some fun with it…</p>
<ul class="list-unstyled list-inline">
<li style="padding-top: 10px; padding-bottom: 10px;"><img class="img-responsive img-thumbnail inline" src="/images/bent/iphone6repairkit.jpg" alt="iphone 6 repair kit" title="iPhone 6 repair kit" /> iPhone 6 repair kit</li>
<li style="padding-top: 10px; padding-bottom: 10px;"><img class="img-responsive img-thumbnail inline" src="/images/bent/lgflexphone.jpg" alt="LG flex phone" title="LG Flex phone. It flexes, but doesn't bend" /> LG Flex: it flexes, but doesn't bend.</li>
<li style="padding-top: 10px; padding-bottom: 10px;"><img class="img-responsive img-thumbnail inline" src="/images/bent/mbp.png" alt="macbook pro" title="don't put an mbp in your pocket" /> Don't put your MBP in your pocket!</li>
</ul>
<p>Lols aside, it’s my opinion that all of these things could have been sorted out long before they delivered any of these failur…I mean features, if they’d just done a little more testing. That’s testing by people who actually use the iPhones. Real people. You know, the kind of folk who will hold a mobile phone like it’s a mobile phone, or put it in their pocket like, well, like a regular person. Focus groups would probably have picked up the ‘it bends irreparably when I sit down with it in my pocket’ problem. Long before you started manufacturing a squillion of them.</p>
<p>Why did you skip that testing? Were you just that good, and your materials engineers so awesome, that it never occurred to you that making a case out of something that thin might make it fragile? Did you have a clueless development or product manager who threatened your very livelihood if you wasted money on focus groups, or <a href="http://en.wikipedia.org/wiki/A/B_testing">A/B testing</a>?</p>
<p>Did you just have some autocratic, self-serving dickhead exec who has no idea what you do, slam his fist on the table and tell you to ‘just fucking make that delivery date or you’re all fucked!’? Or did you have some software development rock-star cowboy who assured you that the new version of the operating system was the most awesome thing he ever built, and even he can’t believe how quickly he was able make those fixes?</p>
<p>Who made the decisions? You know, those ones saying it’s OK to deliver several, successive failures in a row. Where are those people, right now?</p>
<p>It’s OK, we actually <em>do</em> understand. Most of us have worked with folk like that in the past. We don’t like it. But we’ve been there.</p>
<p>Some smaller development shops (like Microsoft and Google, maybe you’ve heard of them) do this thing that could be called ‘functional testing’ or ‘user testing’. They even coined a term for it. They call it “eating your own dog food”, or simply, “dog-fooding”. <a href="http://www.urbandictionary.com/define.php?term=dogfooding+%28to+dogfood%29">I’m not making this up</a>. Look, it’s even on <a href="http://en.wikipedia.org/wiki/Eating_your_own_dog_food">Wikipedia</a>. Maybe you’ve heard of that, instead? No? It means take those products you’re building, and try and use them yourself. Does something about your product piss you off? Do you hate using what you’ve built? Or is it broken? Feed the experience back. Listen. Iterate on it until it’s better! It’s the essence of agile development. It’s the whole bloody point of it, in fact!</p>
<p>And that way, instead of delivering to ‘them’ (the users), you’re delivering to ‘us’ (the people building the product are users too). Get into your customers’ shoes every now and again, and be on the receiving end of your own products and services for once. Realise how much carnage you cause when you decide that something is probably OK to ship, when you haven’t tested it. If you didn’t test it, then she will most definitely <strong>not</strong> be alright on the night.</p>
<p>It’s also one of the reasons that the concept of ‘DevOps’ was introduced. One of the useful side effects of it is that if the developers are involved in the deployment and shipping process, there’s nowhere for them to hide when the shortcut they took fucks the entire process up. Everything points back, accountably and auditably, to the person who made that decision or checked in that source code.</p>
<p>It’s not a mistake you make twice. Trust me. Ask me how I know.</p>
<p>I’ve always told my dev teams: I’d rather have it a day or two late, but working and stable, than a day or two early, and broken and fucked. I’ll be the one to take the flak from the guys up in the ivory tower, because we’re late. That’s not your problem. I’ll wear the embarassment of a late, working delivery, over the embarassment of premature, broken failure, any day.</p>
<p>People don’t remember the successful deliveries as well as they remember your failures. And you don’t get to have too many failures.</p>
<p><em>But I digress…</em></p>
<p>I’m pretty sure I know what Steve Jobs’ response would have been to this one, had he still been around. “You’re putting it in your pocket wrong”, or “you’re putting it in the wrong pocket”. Or maybe just, “why didn’t you just get a bumper case for it?”</p>
<p>Internally he’d have been apopletic, and no doubt someone (or several someones) would have been fired, but that wouldn’t solve this problem. The problem is basically arrogance, and a lack of attention. Apple are supposed to be all about attention to detail. But these ‘little’ details, when multiplied out by tens or hundreds of millions, aren’t ‘little’ details any more!</p>
<p>Are they even testing the details, these days? With real people? On real devices?</p>
<p>Think about it: one of the reasons people used to buy an iPhone is because it’s so sleek, and so well machined, that you just want to hold it. <strong>Without</strong> a case. And because it’s so smooth and sleek, people drop them all the time. And they shatter and smash just like every other smart phone. People know that’s their fault, and they’ll acknowledge it (most of the time). But acknowledge that it’s their fault it got bent, because they put it in their pocket?</p>
<p>Come on! Pull the other one, Apple, it’s got bells on it!</p>
<p>And in this case, good luck with that bumper case. It’ll need to be so bulky and solid to prevent the new iPhones from bending that it won’t fit neatly into your pocket any more (unless you’re wearing cargo pants). It’s not an untested poorly-engineered-antenna-circuit problem this time. And even back then, if you’d been less worried about the device leaking, and let it out of a fake casing once in a while, perhaps given it to, oh, I dunno, <strong>another</strong> focus group, maybe you’d have realised that the antenna was a problem before even going to manufacture?</p>
<p>Yes, you still sold untold millions of them, all of them with a fucked antenna. But it doesn’t appear you learned anything from the experience, because here we are again!</p>
<p>The iCloud guys could have put out a bug bounty (or even a social engineering one) to see if anyone could break or exploit their way into iCloud. Hey Apple! Some people will actually do that for you if you pay them. I even know a few.
And if you don’t pay them, they will come anyway, they won’t tell you how they got in, and then they will Clean. You. Out.</p>
<p>Ask those guys at Target and Home Depot how their lack of proper penetration testing worked out for them. Ask them soon, yeah?</p>
<p>As for the HealthKit ‘upgrade’: you won’t even really tell people what’s wrong with it, except that it borked the mobile connectivity functionality on their phones (amongs other things). People can’t make phone calls or (worse!) stalk their friends on social media. It can be fixed via iTunes. But don’t you think that if you’d just taken the time to install it on a few <strong>real</strong> devices and given it to a few <strong>real</strong> internal people to see if it worked, that they’d have noticed that the phones they installed it on <em>lost all the ability to communicate with the cellular networks</em>?!</p>
<p>Really?!</p>
<p>The <a href="http://www.theverge.com/2014/9/25/6842487/apple-directing-people-to-reinstall-ios-8-while-it-figures-out-a-fix">iOS up-down-grade fix</a> is probably an easy one for Gen-Ys, most Gen-Xs, and those with some basic IT skills. But the reason people buy these Apple products is because they’re supposed to be idiot proof. So even my grandparents (if they were still around) should be able to use it. That was supposed to be one of the biggest selling points! Seamless. Foolproof. Intuitive, slick, simple.</p>
<p>The fools got in somewhere, though.</p>
<p>The arrogance and shortcuts you’re taking are killing you guys. People don’t trust your products like they used to. And you’re only <strong>now</strong> introducing stuff that Android phones had, over two years ago. Back then you told everyone that those ideas were stupid. I mean, who wants a screen that big, right? NFC? Meh, who’d want to bump their phone against stuff? Being able to have more than one home screen, or put my own picture on it? Ludicrous!</p>
<p>Steve Jobs and Tim Cook know what I want more than I do, right? So I’ll just wait for them to tell me.</p>
<p>And don’t even get me started on fucking iTunes…</p>
<p>I think people must be wondering if somewhere along the line, your wheels have come off. You set the bar in the beginning, with the iPhone 3-series devices. No one can dispute that. Now you’re playing catch-up. You’re slowly losing market share. And your share price is showing a steady decline (with the occasional bump whenever you release a new iPhail, which is what this last one is).</p>
<p>Finally, I think the only way you could have made people angrier about that free U2 album you inflicted on them is if you’d only given it to half the people, randomly selected. I’m not sure who would have been more pissed off. Those who got it, or those who didn’t. I don’t mind U2 myself, but I’m not everyone.</p>
<p>And no, I don’t even own an iPhone (or any Apple device for that matter).</p>
<p>So why am I even having this rant?</p>
<p>Because it gives all of us in the product and software development world a bad name! It makes us look incompetent, lax, lazy, autocratic, and arrogant! I have a hard enough time trying to get the devops message across to the more junior members of my software development teams. And it makes it so much harder for us to get IT management to trust that we’re actually competent, reliable, detailed and just really good at what we do. They look at behemoths like you, and they think, “Mother of god! If even Apple can’t get it right, then what chance do our guys have?!”</p>
<p>This shit you’re doing? It’s <em>not</em> helping.</p>
Make a Xamarin.Android widget to enable/disable Bluetooth2014-08-23T00:00:00+00:00http://blog.wislon.io/posts/2014/08/23/make-a-xamarin-android-widget-to-enable-disable-bluetooth<p>In <a href="/posts/2014/08/01/building-a-bluetooth-status-widget-with-xamarin">the previous post</a>, I showed how to build a simple Xamarin.Android widget for monitoring the device’s Bluetooth adapter status. It didn’t do much, simply displaying whether or not the Bluetooth adapter was turned on. It covers a number of other aspects of widget development though, so if there’s something you don’t understand in this post, go back and have a read of that one first. No rush. I’ll wait :).</p>
<p>This post develops onwards from that one. It shows you how to monitor the connectivity status and, as promised, how to use the widget to enable and disable Bluetooth on your device (which actually makes it a little more useful).</p>
<p>The full <a href="https://github.com/wislon/xamarin-bluetooth-toggle-widget">source of this project is on GitHub</a>, so pop that open and have a look, if you want to use it to follow along…</p>
<p>I’ve made some minor changes to the naming of things (for example the main class is now called <code class="language-plaintext highlighter-rouge">BTToggleWidget</code>), but the principles are exactly the same.</p>
<h3 id="some-new-status-images">Some New Status Images</h3>
<p>I won’t rehash the details of how to set up the images and drawables, but I did add a couple of new ones for ‘connected’ and ‘connecting’/’disconnecting’.</p>
<ul class="list-unstyled list-inline">
<li><img class="img-responsive img-thumbnail inline" src="/images/btwidget/bluetooth_connected.png" alt="bluetooth connected" /></li>
<li><img class="img-responsive img-thumbnail inline" src="/images/btwidget/bluetooth_connecting.png" alt="bluetooth connecting/disconnecting" /></li>
</ul>
<p>On faster devices, you won’t really get to see the transition from ‘Connecting’ to ‘Connected’ to ‘Disconnecting’, but I don’t think that’s a problem.</p>
<h3 id="adding-the-connectivity-state-intent">Adding the Connectivity State Intent</h3>
<p>Monitoring the connectivity status changes of the Bluetooth adapter is done exactly the same way as it was for monitoring the Bluetooth adapter power state. All you have to do to catch notifications of changes which occur when a device connects and disconnects is to add a new intent type to the filter, and a handler for it.</p>
<p>The new intent is called <code class="language-plaintext highlighter-rouge">BluetoothAdapter.ActionConnectionStateChanged</code>, and you can just add it to the current list of intents which are filtered by the widget:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="p">[</span><span class="nf">BroadcastReceiver</span><span class="p">(</span><span class="n">Label</span> <span class="p">=</span> <span class="s">"Bluetooth Toggle Widget"</span><span class="p">)]</span>
<span class="p">[</span><span class="nf">IntentFilter</span><span class="p">(</span><span class="k">new</span> <span class="kt">string</span><span class="p">[]</span>
<span class="p">{</span> <span class="s">"android.appwidget.action.APPWIDGET_UPDATE"</span><span class="p">,</span>
<span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ActionStateChanged</span><span class="p">,</span>
<span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ActionConnectionStateChanged</span>
<span class="p">})]</span>
<span class="p">[</span><span class="nf">MetaData</span><span class="p">(</span><span class="s">"android.appwidget.provider"</span><span class="p">,</span> <span class="n">Resource</span> <span class="p">=</span> <span class="s">"@xml/bt_widget"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">BTToggleWidget</span> <span class="p">:</span> <span class="n">AppWidgetProvider</span></code></pre></figure>
<p>Then add an additional filter handler for when one of these <code class="language-plaintext highlighter-rouge">BluetoothAdapter.ActionConnectionStateChanged</code> intents arrives, to the <code class="language-plaintext highlighter-rouge">OnReceive</code> method:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="c1">/// <summary>
</span>
<span class="c1">/// This event fires for every intent you're filtering for. There can be lots of them,
</span>
<span class="c1">/// and they can arrive very quickly, so spend as little time as possible processing them
</span>
<span class="c1">/// on the UI thread.
</span>
<span class="c1">/// </summary>
</span>
<span class="c1">/// <param name="context">The Context in which the receiver is running.</param>
</span>
<span class="c1">/// <param name="intent">The Intent being received.</param>
</span>
<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnReceive</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="n">Intent</span> <span class="n">intent</span><span class="p">)</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="n">Constants</span><span class="p">.</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="s">"OnReceive received intent: {0}"</span><span class="p">,</span> <span class="n">intent</span><span class="p">.</span><span class="n">Action</span><span class="p">);</span>
<span class="c1">// ...some code omitted for brevity...
</span>
<span class="k">if</span><span class="p">(</span><span class="n">intent</span><span class="p">.</span><span class="n">Action</span> <span class="p">==</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ActionConnectionStateChanged</span><span class="p">)</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="n">Constants</span><span class="p">.</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="s">"Received BT Connection State change message"</span><span class="p">);</span>
<span class="nf">ProcessBTConnectionStateChangeMessage</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">intent</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>…and add a new method (I called mine <code class="language-plaintext highlighter-rouge">ProcessBTConnectionStateChangeMessage</code>) for processing the connection state change <code class="language-plaintext highlighter-rouge">Extra</code> payload:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">void</span> <span class="nf">ProcessBTConnectionStateChangeMessage</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="n">Intent</span> <span class="n">intent</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">prevState</span> <span class="p">=</span> <span class="n">intent</span><span class="p">.</span><span class="nf">GetIntExtra</span><span class="p">(</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ExtraPreviousConnectionState</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">newState</span> <span class="p">=</span> <span class="n">intent</span><span class="p">.</span><span class="nf">GetIntExtra</span><span class="p">(</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ExtraConnectionState</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">);</span>
<span class="kt">string</span> <span class="n">message</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"Bluetooth Connection state change from {0} to {1}"</span><span class="p">,</span> <span class="p">(</span><span class="n">State</span><span class="p">)</span><span class="n">prevState</span><span class="p">,</span> <span class="p">(</span><span class="n">State</span><span class="p">)</span><span class="n">newState</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="n">Constants</span><span class="p">.</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="n">message</span><span class="p">);</span>
<span class="nf">UpdateWidgetDisplay</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">newState</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>As you can see, this functions in exactly the same way as the <code class="language-plaintext highlighter-rouge">ProcessBTStateChangeMessage</code> mentioned in the previous article. It checks the previous and new states, and then sends the new connection state on to the <code class="language-plaintext highlighter-rouge">UpdateWidgetDisplay</code> method.</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">void</span> <span class="nf">UpdateWidgetDisplay</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="kt">int</span> <span class="n">newState</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">appWidgetManager</span> <span class="p">=</span> <span class="n">AppWidgetManager</span><span class="p">.</span><span class="nf">GetInstance</span><span class="p">(</span><span class="n">context</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">remoteViews</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">RemoteViews</span><span class="p">(</span><span class="n">context</span><span class="p">.</span><span class="n">PackageName</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">initial_layout</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">thisWidget</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ComponentName</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">Class</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_off</span><span class="p">;</span>
<span class="n">State</span> <span class="n">currentState</span> <span class="p">=</span> <span class="p">(</span><span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">)</span><span class="n">newState</span><span class="p">;</span>
<span class="k">switch</span><span class="p">(</span><span class="n">currentState</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">Off</span><span class="p">:</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">TurningOn</span><span class="p">:</span>
<span class="p">{</span>
<span class="c1">// ... snip ...
</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">Connecting</span><span class="p">:</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">Disconnecting</span><span class="p">:</span>
<span class="p">{</span>
<span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_connecting</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">Connected</span><span class="p">:</span>
<span class="p">{</span>
<span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_connected</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">Disconnected</span><span class="p">:</span>
<span class="p">{</span>
<span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_on</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// ... snip ...
</span>
<span class="p">}</span>
<span class="n">remoteViews</span><span class="p">.</span><span class="nf">SetImageViewResource</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">imgBluetooth</span><span class="p">,</span> <span class="n">imgResource</span><span class="p">);</span>
<span class="c1">// ... snip ...</span></code></pre></figure>
<p>This leaves us with a widget that can track and display the connection-state changes of the Bluetooth adapter, as other devices connect and disconnect. However, it also leaves us with the same problem as in the previous article: it only responds to changes in status, but doesn’t check to see what the connection state is <strong>when it starts up</strong>.</p>
<p>Easily fixed, yes? As before, all we have to make a call to ask the adapter whether it’s connected when we initially place the widget on the home screen.</p>
<p>Except it turns out it’s not <em>quite</em> that simple. It’s not a big deal though. It’s just that there’s no genericky ‘adapter.IsConnected’ or ‘adapter.ConnectionStatus’ property or function we can call directly. We have to query each Bluetooth connection profile type individually, to see if it’s connected to something (and if we care, what it’s connected to).</p>
<p>So I slapped together a quick method to do just that:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"> <span class="c1">/// <summary>
</span>
<span class="c1">/// Clunky implementation of getting connection state for the different profiles.
</span>
<span class="c1">/// Sufficient for our purposes though. Could expand it to include what we're connected
</span>
<span class="c1">/// to as well.
</span>
<span class="c1">/// </summary>
</span>
<span class="c1">/// <returns>The Bluetooth connection state.</returns>
</span>
<span class="k">private</span> <span class="n">ProfileState</span> <span class="nf">GetBluetoothConnectionState</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">profileTypes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ProfileType</span><span class="p">[]</span> <span class="p">{</span> <span class="n">ProfileType</span><span class="p">.</span><span class="n">A2dp</span><span class="p">,</span> <span class="n">ProfileType</span><span class="p">.</span><span class="n">Gatt</span><span class="p">,</span> <span class="n">ProfileType</span><span class="p">.</span><span class="n">GattServer</span><span class="p">,</span> <span class="n">ProfileType</span><span class="p">.</span><span class="n">Headset</span><span class="p">,</span> <span class="n">ProfileType</span><span class="p">.</span><span class="n">Health</span> <span class="p">};</span>
<span class="kt">bool</span> <span class="n">connected</span> <span class="p">=</span> <span class="n">profileTypes</span><span class="p">.</span><span class="nf">Any</span><span class="p">(</span><span class="n">pt</span> <span class="p">=></span> <span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">DefaultAdapter</span><span class="p">.</span><span class="nf">GetProfileConnectionState</span><span class="p">(</span><span class="n">pt</span><span class="p">)</span> <span class="p">==</span> <span class="n">ProfileState</span><span class="p">.</span><span class="n">Connected</span><span class="p">);</span>
<span class="k">return</span> <span class="n">connected</span> <span class="p">?</span> <span class="n">ProfileState</span><span class="p">.</span><span class="n">Connected</span> <span class="p">:</span> <span class="n">ProfileState</span><span class="p">.</span><span class="n">Disconnected</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>This just iterates over the list of available profiles to see which (if any) are connected. At this point, I’m only interested in whether it’s connected to at least one device. I’m not interested in what it’s actually connected to. That’s an exercise for another day.</p>
<p>Now that I can quickly determine whether I am connected to something, I just need to invoke that function from where the widget is initialised. This is up where the widget update/initialisation <code class="language-plaintext highlighter-rouge">android.appwidget.action.APPWIDGET_UPDATE</code> intent is caught (i.e. up at the top of <code class="language-plaintext highlighter-rouge">OnReceive</code>):</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnReceive</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="n">Intent</span> <span class="n">intent</span><span class="p">)</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="n">Constants</span><span class="p">.</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="s">"OnReceive received intent: {0}"</span><span class="p">,</span> <span class="n">intent</span><span class="p">.</span><span class="n">Action</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">intent</span><span class="p">.</span><span class="n">Action</span> <span class="p">==</span> <span class="s">"android.appwidget.action.APPWIDGET_UPDATE"</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ... snip ...
</span>
<span class="n">Log</span><span class="p">.</span><span class="nf">Info</span><span class="p">(</span><span class="n">Constants</span><span class="p">.</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="s">"BT adapter state currently {0}"</span><span class="p">,</span> <span class="n">currentState</span><span class="p">);</span>
<span class="nf">UpdateWidgetDisplay</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">currentState</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">currentState</span> <span class="p">==</span> <span class="n">State</span><span class="p">.</span><span class="n">On</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Log</span><span class="p">.</span><span class="nf">Debug</span><span class="p">(</span><span class="n">Constants</span><span class="p">.</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="s">"Checking Bluetooth connection state..."</span><span class="p">);</span>
<span class="n">ProfileState</span> <span class="n">currentConnectedState</span> <span class="p">=</span> <span class="nf">GetBluetoothConnectionState</span><span class="p">();</span>
<span class="nf">UpdateWidgetDisplay</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">currentConnectedState</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// ... snip ...</span></code></pre></figure>
<p><em>It makes sense to only bother checking the connectivity state if the Bluetooth adapter state is already set to ‘On’.</em></p>
<p>So now we can update the display to show not only whether your Bluetooth adapter is turned on, but also whether it’s connected to something.</p>
<p>So what? I can see the same information in the phone’s notification/status bar.</p>
<p><a class="fancybox" rel="group" href="/images/btwidget/widget_vs_notification_status.png" title="Widget and notification/status bar"><img class="img-responsive img-thumbnail inline" src="/images/btwidget/widget_vs_notification_status.png" alt="Widget and notification/status bar" /></a></p>
<p>The original goal was to build a widget that could enable and disable the Bluetooth adapter <em>without</em> the Nexus 5’s flipper-draggingly stupid process of ‘using-two-fingers-swipe-down-and-tap-bluetooth/wifi/whatever-then-wait-then-tap-‘on’/’off’-to-enable/disable-then-tap-‘back’-twice’.</p>
<p>I want to be able to do it with a single tap. Once for on. Once for off.</p>
<h3 id="making-the-widget-do-stuff">Making The Widget Do Stuff</h3>
<p>We need the widget to react like a button, when tapped. And it’s one of the reasons I picked an <code class="language-plaintext highlighter-rouge">ImageButton</code> to start off with. We need something with a click (or click-like) event. Unfortunately, due to the way widgets are designed to work, you can’t just add code into an <code class="language-plaintext highlighter-rouge">OnClick</code> event, and go, as you would with a normal button.</p>
<p>There’s a chance you may have more than one widget of the same type on your home screen (well, ok, probably not more than one of these specific widgets). And you need each widget to behave the same way <em>and do the same thing, regardless of which widget you’ve just tapped.</em> This means that even if we have five widgets on the screen, we want to be able to tap any one of them, and have the same operation occur. We’d need their <code class="language-plaintext highlighter-rouge">OnClick</code> handlers to be in sync, and run the same code, independently of which widget is reacting to an update or tap.</p>
<p>Fortunately, this is quite easy to do. The <code class="language-plaintext highlighter-rouge">RemoteViews</code> object can bind certain events and properties to other objects, for precisely this purpose. We’re already doing it with setting the status image into the ImageView Resource on the ImageButton, with the <code class="language-plaintext highlighter-rouge">remoteViews.SetImageViewResource(Resource.Id.imgBluetooth, imgResource)</code> call.</p>
<p>Unfortunately, it doesn’t allow us to assign actual code to those events. Annoying on the face of it, but if you think about it, it’s the smart thing to do. Depending on what your requirements, you’d have to set up some sort of closure, with an action or function which would potentially be long-running, maybe with multiple threads, perhaps needing a callback attached, and probably some exception handling. And it would need to be locked for possible thread safety issues. And/or re-entrancy. Oh, and then you’d probably have to serialise data into and out of it somehow, so you could pass it around using Android’s intent messaging system. And if you break something, it may cause your widget to ANR, which is A Bad Thing. And because widgets can’t be attached/debugged, you could end up completely screwed if it doesn’t work, because you’ll never know what broke…</p>
<p>Yuck.</p>
<p>So, they force you to push the code for the operations you need to perform to somewhere a little more sane. And they do this by getting you to set up a <code class="language-plaintext highlighter-rouge">PendingIntent</code>, which points to the process that you would <em>like</em> to occur when you click the widget. This then turns the widget into a kind of launcher, instead. Things suddenly become more loosely coupled, and they’re isolated, attachable, and swappable.</p>
<p>Like I said: a little more sane.</p>
<p>It’s a lot easier for the <code class="language-plaintext highlighter-rouge">RemoteViews</code> instance to hook up a PendingIntent to your <code class="language-plaintext highlighter-rouge">OnClick</code>, let you tack on whatever data you need, and then pass that little bundle of joy to all the other widgets of this type. Then they become launchers too. Then you build the actual process you want to run into a <code class="language-plaintext highlighter-rouge">Service</code> or <code class="language-plaintext highlighter-rouge">Activity</code>. It’ll then be that object’s responsibility to manage all that stuff. This is as it should be.</p>
<p>So let’s make some PendingIntents. We’ll need two. One to enable the Bluetooth adapter, and one to disable it. We’ll also need a tiny bit of coding logic to turn them into a toggle, depending on the current state of the Bluetooth adapter.</p>
<h3 id="building-the-enable-pendingintent">Building the ‘Enable’ PendingIntent</h3>
<p>The first one we’ll make is to request the Bluetooth adapter to become enabled. Best practices say you shouldn’t just enable the adapter without the user’s permission anyway. Fair enough, it’s all about privacy these days, so no problem there. We’ll do it the way they suggest, and as you’ll see, it takes care of asking the user for us, too.</p>
<p>Set up an <code class="language-plaintext highlighter-rouge">Intent</code> for the request (it’s called <code class="language-plaintext highlighter-rouge">BluetoothAdapter.ActionRequestEnable</code>), wrap it in a <code class="language-plaintext highlighter-rouge">PendingIntent</code> and ask the <code class="language-plaintext highlighter-rouge">remoteViews</code> instance to bind it to the ‘click’ method of the Image button. Then tell all the other widgets of this type to do the same thing:</p>
<p>Inside the <code class="language-plaintext highlighter-rouge">UpdateWidgetDisplay</code> method, after you update the ImageButton’s display with the current status image, place the following code:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">void</span> <span class="nf">UpdateWidgetDisplay</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="kt">int</span> <span class="n">newState</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ... snip ...
</span>
<span class="n">remoteViews</span><span class="p">.</span><span class="nf">SetImageViewResource</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">imgBluetooth</span><span class="p">,</span> <span class="n">imgResource</span><span class="p">);</span>
<span class="k">switch</span><span class="p">(</span><span class="n">currentState</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">State</span><span class="p">.</span><span class="n">Off</span><span class="p">:</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="n">Constants</span><span class="p">.</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="s">"Adapter state is 'off', adding click delegate to turn on Bluetooth "</span><span class="p">);</span>
<span class="n">Intent</span> <span class="n">enableBluetoothIntent</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Intent</span><span class="p">(</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ActionRequestEnable</span><span class="p">);</span>
<span class="n">PendingIntent</span> <span class="n">pendingIntent</span> <span class="p">=</span> <span class="n">PendingIntent</span><span class="p">.</span><span class="nf">GetActivity</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">enableBluetoothIntent</span><span class="p">,</span> <span class="n">PendingIntentFlags</span><span class="p">.</span><span class="n">UpdateCurrent</span><span class="p">);</span>
<span class="n">remoteViews</span><span class="p">.</span><span class="nf">SetOnClickPendingIntent</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">imgBluetooth</span><span class="p">,</span> <span class="n">pendingIntent</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">default</span><span class="p">:</span>
<span class="p">{</span>
<span class="c1">// we'll be back here in a moment, to turn it off again
</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">appWidgetManager</span><span class="p">.</span><span class="nf">UpdateAppWidget</span><span class="p">(</span><span class="n">thisWidget</span><span class="p">,</span> <span class="n">remoteViews</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p><em>We use <code class="language-plaintext highlighter-rouge">PendingIntentFlags.UpdateCurrent</code> to ensure that if there’s already a pending intent attached to the other widgets, then they’re updated with this one. If there aren’t any, then this one will simply become the pending intent attached to each widget’s OnClick event. This isn’t a real issue with this particular widget because we’re not attaching anything extra to it, but if you had additional data to pass along with the intent, this would swap out any existing pending intent for this new one.</em></p>
<p>The intent to enable the Bluetooth is then wrapped in a <code class="language-plaintext highlighter-rouge">PendingIntent</code> (think of it as a kind of closure function pointer in this case), and attached to the <code class="language-plaintext highlighter-rouge">imgBluetooth</code> ImageButton via <code class="language-plaintext highlighter-rouge">remoteViews.SetOnClickPendingIntent(Resource.Id.imgBluetooth, pendingIntent)</code>.</p>
<p>If you build this project at this point, upload the widget, and then tap it when Bluetooth is off, you’ll be presented with the following ‘permission request’ dialog (it may differ slightly from device to device):</p>
<p><a class="fancybox" rel="group" href="/images/btwidget/request_permission_to_enable.png" title="Request permission to enable Bluetooth"><img class="img-responsive img-thumbnail inline" src="/images/btwidget/request_permission_to_enable.png" alt="Request permission to enable Bluetooth" /></a></p>
<p>If you click ‘Allow’, the Bluetooth adapter will become enabled (this may take second or two). If you click ‘Deny’, then nothing will happen.</p>
<p>Now let’s get it to turn it off again.</p>
<h3 id="building-the-disable-pendingintent">Building the ‘Disable’ PendingIntent</h3>
<p>So, there’s no <code class="language-plaintext highlighter-rouge">BluetoothAdapter.ActionRequestDisable</code> intent. Or similar. Bet you saw that one coming. Turns out that if we want to turn the adapter off, we whave to actually call the <code class="language-plaintext highlighter-rouge">BluetoothAdapter.DefaultAdapter.Disable()</code> method.</p>
<p>Why? Why couldn’t they just give us an ActionRequestDisable? With a corresponding permission dialog? How hard would it be?!</p>
<p><em>*Sigh*</em></p>
<p>OK then. We know we can’t put code into the OnClick event directly, so we’ll need to put that Single Line Of Code into its own <code class="language-plaintext highlighter-rouge">Activity</code> or <code class="language-plaintext highlighter-rouge">Service</code>, and launch it as described above. I actually picked an <code class="language-plaintext highlighter-rouge">IntentService</code> for this because it’s simpler, runs on its own thread, and cleans up after itself. There’s no need to bind to it or call <code class="language-plaintext highlighter-rouge">StopSelf()</code> at the end.</p>
<p>So here’s my IntentService (the whole thing):</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">using</span> <span class="nn">Android.App</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Android.Appwidget</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Android.Content</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Android.Bluetooth</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Android.Util</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Android.Widget</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">BluetoothToggleWidget</span>
<span class="p">{</span>
<span class="p">[</span><span class="n">Service</span><span class="p">]</span>
<span class="k">class</span> <span class="nc">DisableBluetoothService</span> <span class="p">:</span> <span class="n">IntentService</span>
<span class="p">{</span>
<span class="k">public</span> <span class="nf">DisableBluetoothService</span><span class="p">()</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="s">"DisableBluetoothService"</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">OnHandleIntent</span><span class="p">(</span><span class="n">Intent</span> <span class="n">intent</span><span class="p">)</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="n">Constants</span><span class="p">.</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="s">"Received request to disable Bluetooth"</span><span class="p">);</span>
<span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">DefaultAdapter</span><span class="p">.</span><span class="nf">Disable</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Nothing fancy. We don’t even need to add an <code class="language-plaintext highlighter-rouge">IntentFilter</code> or <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> to it, because it only does one thing.</p>
<p>And then set up our widget to launch it, using a PendingIntent:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="n">remoteViews</span><span class="p">.</span><span class="nf">SetImageViewResource</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">imgBluetooth</span><span class="p">,</span> <span class="n">imgResource</span><span class="p">);</span>
<span class="k">switch</span><span class="p">(</span><span class="n">currentState</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">State</span><span class="p">.</span><span class="n">Off</span><span class="p">:</span>
<span class="p">{</span>
<span class="c1">// ...snip... we already did this bit
</span>
<span class="p">}</span>
<span class="k">default</span><span class="p">:</span> <span class="c1">// anything OTHER than 'Off'
</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="n">Constants</span><span class="p">.</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"Adapter state is {0}, adding click delegate to turn off BT"</span><span class="p">,</span> <span class="n">currentState</span><span class="p">.</span><span class="nf">ToString</span><span class="p">()));</span>
<span class="n">Intent</span> <span class="n">disableBluetoothIntent</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Intent</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="k">typeof</span><span class="p">(</span><span class="n">DisableBluetoothService</span><span class="p">));</span>
<span class="n">PendingIntent</span> <span class="n">pendingIntent</span> <span class="p">=</span> <span class="n">PendingIntent</span><span class="p">.</span><span class="nf">GetService</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">disableBluetoothIntent</span><span class="p">,</span> <span class="n">PendingIntentFlags</span><span class="p">.</span><span class="n">UpdateCurrent</span><span class="p">);</span>
<span class="n">remoteViews</span><span class="p">.</span><span class="nf">SetOnClickPendingIntent</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">imgBluetooth</span><span class="p">,</span> <span class="n">pendingIntent</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">appWidgetManager</span><span class="p">.</span><span class="nf">UpdateAppWidget</span><span class="p">(</span><span class="n">thisWidget</span><span class="p">,</span> <span class="n">remoteViews</span><span class="p">);</span></code></pre></figure>
<p>Basically the same thing as before: we create an Intent pointing at a <code class="language-plaintext highlighter-rouge">DisableBluetoothService</code> type, get a pointer to it via the <code class="language-plaintext highlighter-rouge">PendingIntent.GetService()</code> call (note that it’s <strong>GetService</strong> and <em>not</em> GetActivity), and finish up as before.</p>
<p>And we’re done.</p>
<p>Build and upload it to the device (with Bluetooth turned off), and tap once to turn it on. ‘Allow’ the Bluetooth to be enabled. And when it’s enabled, just tap the widget again to turn it off. Notice that it doesn’t ask you if you want to turn it off, it just clobbers it (or at least it does on the three devices I’ve tested it on). Best practice advises us to get the user to confirm the disabling of it too, but I’ll leave that up to you to implement.</p>
<h3 id="enabling-the-adapter-without-permission">Enabling the Adapter Without Permission</h3>
<p><strong>UPDATE</strong>: If you want to just enable the Bluetooth adapter <em>without</em> forcing the user to go through a permission dance, then you don’t need to use the intent-method explained above. All you need to do is use the same methodology yuou did for disabling it, i.e. create an <code class="language-plaintext highlighter-rouge">EnableBluetoothService</code>, with a corresponding <code class="language-plaintext highlighter-rouge">BluetoothAdapter.DefaultAdapter.Enable()</code> method call in it. That’ll have the desired effect.</p>
<h3 id="conclusion">Conclusion</h3>
<p>OK, so it’s not quite a single-tap toggle switch, but it’s good enough for me at this point. I can live with tap-tap for on, and tap-for-off. It does what I need it to do.</p>
<p>If you’ve been following along in the code, you’ll notice I’ve omitted a lot of the logging calls here. This was just for brevity. But if you hook your device up to your development environment, and run the Android monitor (in the SDK tools, probably under <code class="language-plaintext highlighter-rouge">C:\Users\userName\AppData\Local\Android\android-sdk\tools\lib\monitor-x86\monitor.exe</code>), you can see what it’s doing under the hood.</p>
<p>Below is a dump of the log as the widget undergoes a full life-cycle</p>
<ul>
<li>The widget being added to the home screen (Bluetooth off)</li>
<li>Tapping to enable Bluetooth</li>
<li>Bluetooth becoming enabled</li>
<li>Automatic connection to my Bluetooth headset</li>
<li>The headset disconnecting as I turn it off</li>
<li>Tapping to disable the Bluetooth.</li>
<li>Removing the widget from the home screen.</li>
</ul>
<p><em>(I added a ‘BTToggleWidget’ filter using a log tag of BTToggleWidget and set log level to ‘Verbose’, and then just copied and pasted)</em></p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">: I/BTToggleWidget(4669): OnReceive received intent: android.appwidget.action.APPWIDGET_UPDATE
: I/BTToggleWidget(4669): Received AppWidget Update
: I/BTToggleWidget(4669): BT adapter state currently Off
: I/BTToggleWidget(4669): Adapter state is 'off', adding click delegate to turn on BT
: I/BTToggleWidget(4669): OnReceive received intent: com.motorola.blur.home.ACTION_SET_WIDGET_SIZE
: I/BTToggleWidget(4669): OnReceive received intent: mobi.intuitit.android.hpp.ACTION_READY
: I/BTToggleWidget(4669): OnReceive received intent: android.appwidget.action.APPWIDGET_UPDATE_OPTIONS
: I/BTToggleWidget(4669): OnReceive received intent: android.bluetooth.adapter.action.STATE_CHANGED
: I/BTToggleWidget(4669): Received BT Action State change message
: I/BTToggleWidget(4669): Bluetooth Adapter state change from Off to TurningOn
: I/BTToggleWidget(4669): Adapter state is TurningOn, adding click delegate to turn off BT
: I/BTToggleWidget(4669): OnReceive received intent: android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED
: I/BTToggleWidget(4669): Received BT Connection State change message
: I/BTToggleWidget(4669): Bluetooth Connection state change from Disconnected to Connecting
: I/BTToggleWidget(4669): Adapter state is Connecting, adding click delegate to turn off BT
: I/BTToggleWidget(4669): OnReceive received intent: android.bluetooth.adapter.action.STATE_CHANGED
: I/BTToggleWidget(4669): Received BT Action State change message
: I/BTToggleWidget(4669): Bluetooth Adapter state change from TurningOn to On
: I/BTToggleWidget(4669): Adapter state is On, adding click delegate to turn off BT
: I/BTToggleWidget(4669): OnReceive received intent: android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED
: I/BTToggleWidget(4669): Received BT Connection State change message
: I/BTToggleWidget(4669): Bluetooth Connection state change from Disconnected to Connected
: I/BTToggleWidget(4669): Adapter state is Connected, adding click delegate to turn off BT
: I/BTToggleWidget(4669): OnReceive received intent: android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED
: I/BTToggleWidget(4669): Received BT Connection State change message
: I/BTToggleWidget(4669): Bluetooth Connection state change from Connected to Disconnected
: I/BTToggleWidget(4669): Adapter state is Disconnected, adding click delegate to turn off BT
: I/BTToggleWidget(4669): Received request to disable bluetooth
: I/BTToggleWidget(4669): OnReceive received intent: android.bluetooth.adapter.action.STATE_CHANGED
: I/BTToggleWidget(4669): Received BT Action State change message
: I/BTToggleWidget(4669): Bluetooth Adapter state change from On to TurningOff
: I/BTToggleWidget(4669): Adapter state is TurningOff, adding click delegate to turn off BT
: I/BTToggleWidget(4669): OnReceive received intent: android.bluetooth.adapter.action.STATE_CHANGED
: I/BTToggleWidget(4669): Received BT Action State change message
: I/BTToggleWidget(4669): Bluetooth Adapter state change from TurningOff to Off
: I/BTToggleWidget(4669): Adapter state is 'off', adding click delegate to turn on BT </code></pre></figure>
<p>There’s a lot of redundant setting of things as the intents arrive, and I guess there’s no real way around that short of introducing a bunch of flags for controlling program flow. And in my opinion, for what this widget needs to do, it’ll add a metric buttload of unnecessary complexity, with no real benefit.</p>
<h3 id="google-play">Google Play</h3>
<p>I’ve also just released it as an app on the <a href="https://play.google.com/store/apps/details?id=io.wislon.BluetoothToggleWidget">Google Play store</a>, because reasons, and why not?</p>
<h3 id="references">References</h3>
<p>Some additional references which you may find useful:</p>
<ul>
<li>PendingIntent reference: <a href="http://developer.android.com/reference/android/app/PendingIntent.html">http://developer.android.com/reference/android/app/PendingIntent.html</a></li>
<li>Services and IntentServices: [http://developer.xamarin.com/guides/android/application_fundamentals/services/part_1<em>-_started_services/](http://developer.xamarin.com/guides/android/application_fundamentals/services/part_1</em>-_started_services/)</li>
<li>Android Bluetooth Basics: <a href="http://developer.android.com/guide/topics/connectivity/bluetooth.html">http://developer.android.com/guide/topics/connectivity/bluetooth.html</a> (on/off states)</li>
<li>Android Bluetooth Adapter class: <a href="http://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html">http://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html</a></li>
<li>The code for this project: <a href="https://github.com/wislon/xamarin-bluetooth-toggle-widget">https://github.com/wislon/xamarin-bluetooth-toggle-widget</a></li>
</ul>
<h3 id="license">License</h3>
<p>The code for this project has been released under the MIT License, so you can do what you like with it. However one of the Bluetooth symbol images is provided by a third-party, and was distributed as ‘freeware, not for commercial use’. So if you plan on using the code in this repo as the basis for a commercial product (go right ahead!), you’ll need to source your Bluetooth symbol image(s) from elsewhere.</p>
Building a Bluetooth status widget with Xamarin2014-08-01T00:00:00+00:00http://blog.wislon.io/posts/2014/08/01/building-a-bluetooth-status-widget-with-xamarin<p>I have an LGE Nexus 5, and it’s a really great phone. But there’s some aspects of its usability that feel unfinished. For instance, the UI/UIX designers decided that enabling or disabling things like the wi-fi or bluetooth wasn’t difficult enough. So they turned it into a process: ‘using-two-fingers-swipe-down-and-tap-whatever-then-wait-then-tap-‘on’/’off’-to-enable/disable-then-tap-‘back’-twice’. Bluetooth, wi-fi, NFC, location… all the same.</p>
<p>It’s stupid. And bloody difficult if you’re trying to do it with just one hand. Didn’t they <a href="http://blog.codinghorror.com/the-ultimate-dogfooding-story/">dog-food</a> it before shipping?</p>
<p>Yeah, I know. First-world problems, right? Probably. But that’s not the point. An effective user interface gets <em>out</em> of the way, not <em>into</em> it. My point is that turning it on or off should be a simple toggle. If it’s off, a single tap should turn it on. And vice versa. Samsung (and others) have it right: their phones have a simple <em>single-digit</em> ‘swipe-down-and-tap’ operation. You can do it with just your thumb. And that’s way less stupid.</p>
<p>After dropping my phone for the umptieth time, I decided I’d make a widget to act as a switch to enable/disable the bluetooth, to prove the concept. One that I can use just a finger or thumb on. After a quick look in the app store (there’s about a squillion bluetooth widgets), I figured I’d just build my own. There’s a serious shortage of Xamarin widget examples too, as it turns out. The only half decent one I could find was that <a href="http://developer.xamarin.com/samples/SimpleWidget/">‘word of the day’</a> one, which just gave me HTTP ‘403’ errors…</p>
<p>So, I figured I would blog about making it, too, as a simple Xamarin widget tutorial, and maybe save someone else a bit of time and effort.</p>
<p>Building a simple Android widget is surprisingly easy to do. Though building all the functionality and documenting it in a single blog post turned out to be a little ambitious, especially as I prefer to go into a bit more detail about some of the less obvious bits.</p>
<p>So I’ve decided to break it up. To get the proof-of-concept up and running, I’ve started off with a ‘react-only’ widget, which simply responds to changes in Bluetooth state, and that’s what this article is about.</p>
<p><strong>UPDATE:</strong> <em>The second part of this tutorial, detailing how to manage and monitor the device’s connectivity <a href="/posts/2014/08/23/make-a-xamarin-android-widget-to-enable-disable-bluetooth">is over here</a>.</em></p>
<p><a class="fancybox" rel="group" href="/images/btwidget/btwidget/bluetooth_widget_installed.png" title="Widget on home screen"><img class="img-responsive img-thumbnail inline" src="/images/btwidget/bluetooth_widget_installed.png" alt="Widget on home screen" /></a></p>
<h2 id="creating-a-widget-basic-steps">Creating a Widget: Basic Steps</h2>
<p>I used both <strong>Xamarin Studio</strong> and <strong>Visual Studio with the Xamarin.Android add-in</strong> to build this widget, but at this level there’s no real reason to use instead of the other. This example will use Xamarin Studio, simply because everyone who’s using Xamarin has that installed.</p>
<p>The sample project for this article is <a href="https://github.com/wislon/xamarin-bluetooth-status-widget">up on GitHub</a>.</p>
<p>Create an empty Xamarin ‘Android application’ project. I called mine “BluetoothWidget”. <em>(Don’t create a Xamarin.Forms app, this is not the same thing.)</em></p>
<p><a class="fancybox" rel="group" href="/images/btwidget/new_android_application.png" title="Create a new android application"><img class="img-responsive img-thumbnail inline" src="/images/btwidget/new_android_application.png" alt="Create a new android application" /></a></p>
<p>Right click on the project to access the Android application properties, and make sure you select the permissions for <code class="language-plaintext highlighter-rouge">BLUETOOTH</code> and <code class="language-plaintext highlighter-rouge">BLUETOOTH_ADMIN</code>. If you don’t do this, nothing will happen when the bluetooth state changes. You won’t get any errors, but nothing will work, and it may leave you scratching your head. Ask me how I know…</p>
<p><a class="fancybox" rel="group" href="/images/btwidget/bluetooth_widget_project_settings.png" title="Bluetooth widget app manifest settings"><img class="img-responsive img-thumbnail inline" src="/images/btwidget/bluetooth_widget_project_settings.png" alt="Bluetooth widget app manifest settings" /></a></p>
<p>Delete the standard <code class="language-plaintext highlighter-rouge">MainActivity.cs</code> class file it creates, you won’t need it. Now add a new, empty class, to replace <code class="language-plaintext highlighter-rouge">MainActivity.cs</code>. I called mine <code class="language-plaintext highlighter-rouge">BTWidget</code>, because I’m not very imaginative, and naming things is hard.</p>
<p><a class="fancybox" rel="group" href="/images/btwidget/add_bt_widget_class.png" title="Add a new class file"><img class="img-responsive img-thumbnail inline" src="/images/btwidget/add_bt_widget_class.png" alt="Add a new class file" /></a></p>
<p>Make sure the new <code class="language-plaintext highlighter-rouge">BTWidget</code> class inherits from <code class="language-plaintext highlighter-rouge">Android.Appwidget.AppWidgetProvider</code>. You can also remove the default constructor, you won’t need that either.</p>
<p>You should end up with an empty class that looks like:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Android.Appwidget</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">BluetoothWidget</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">BTWidget</span> <span class="p">:</span> <span class="n">AppWidgetProvider</span>
<span class="p">{</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>When the OS installs the widget and sets it up for you, it needs to know some initial default sizes, layout info, have a preview icon, etc. You’ll need to provide it with some metadata to do this. In this case it’s easiest to use an XML file to hold this information. So, add a new <code class="language-plaintext highlighter-rouge">xml</code> folder off the <code class="language-plaintext highlighter-rouge">Resources</code> folder, and create a new XML file in it called <code class="language-plaintext highlighter-rouge">bt_widget.xml</code>.</p>
<p>This file contains the initial specs for the widget, and a pointer to the rest of the layout:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><appwidget-provider</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">android:minWidth=</span><span class="s">"36dip"</span>
<span class="na">android:minHeight=</span><span class="s">"36dip"</span>
<span class="na">android:maxWidth=</span><span class="s">"80dip"</span>
<span class="na">android:maxHeight=</span><span class="s">"80dip"</span>
<span class="na">android:updatePeriodMillis=</span><span class="s">"0"</span>
<span class="na">android:previewImage=</span><span class="s">"@drawable/icon"</span>
<span class="na">android:initialLayout=</span><span class="s">"@layout/initial_layout"</span><span class="nt">></span>
<span class="nt"></appwidget-provider></span></code></pre></figure>
<p>Let’s unpack that quickly:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">android:minWidth/Height</code> and <code class="language-plaintext highlighter-rouge">android:maxWidth/Height</code> give the host container for the Android widget a hint as to how small/large the widget should be allowed to shrink/grow. In most cases, these can be overridden by the home screen manager you’re placing them on anyway, but it gives it a predefined starting point.
<ul>
<li>We use <code class="language-plaintext highlighter-rouge">dip</code> or <a href="http://stackoverflow.com/questions/2025282/difference-between-px-dp-dip-and-sp-in-android">‘density independent pixels’</a> so that we’re not locking the widget size down to an absolute; it allows the device to pick the scaling, size and aspect ratio of the pixels used to render the widget. It just looks better.</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">android:updatePeriodMillis</code> tells the OS how long it should wait between sending notifications to the widget to tell it to update itself. Our widget is pretty dumb. It’s just reacting to events broadcast by the Bluetooth adapter’s state changes, so there’s no schedule. Hence <code class="language-plaintext highlighter-rouge">0</code>.</li>
<li><code class="language-plaintext highlighter-rouge">android:previewImage</code> is used to indicate to the user, when adding the widget, what the widget would normally look like. In this case we’ve just used the same icon as we’ve used for the widget application.</li>
<li><code class="language-plaintext highlighter-rouge">android:initialLayout</code> is just a pointer to the <code class="language-plaintext highlighter-rouge">initial_layout.axml</code> file which is off the <code class="language-plaintext highlighter-rouge">Resources\layout</code> folder. You can use a regular <code class="language-plaintext highlighter-rouge">.xml</code> file too, but some IDEs won’t give you the ‘intellisense’ completion you may be expecting. Using an <code class="language-plaintext highlighter-rouge">.axml</code> file also lets you load it up in the layout designer, if you’re that way inclined. We’ll come back to this guy in a moment…</li>
</ul>
<p>You can tell the compiler where to find the widget metadata by either putting a <code class="language-plaintext highlighter-rouge">[MetaData]</code> attribute on the class, or editing the <strong>AndroidManifest.xml</strong> file directly. Personally I prefer doing it through code; there’s less of a disconnect, and you spend less time jumping around between files to modify the same thing. It’s also, in my opinion, less prone to error, since the compiler can then build the XML for you.</p>
<p>Add the <code class="language-plaintext highlighter-rouge">[MetaData]</code> attribute, with a string property of <code class="language-plaintext highlighter-rouge">"android.appwidget.provider"</code>. This will create a key in the <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> file, which the OS (or widget host) can use to load the rest of the widget’s metadata. What metadata? Well, you need to point to it, and you do so by adding another property string called <code class="language-plaintext highlighter-rouge">Resource</code>, set to the URI of the <code class="language-plaintext highlighter-rouge">bt_widget.xml</code> file you created earlier. It should look like <code class="language-plaintext highlighter-rouge">Resource = "@xml/bt_widget"</code>. And since that file points to the rest of the widget’s initial layout, it can be bootstrapped from there.</p>
<p><em>Personally I think this is an awful lot of effort to go to, with way too many levels of indirection and abstraction, but the compiler will take care of most of this if you set that <code class="language-plaintext highlighter-rouge">[MetaData]</code> attribute up properly.</em></p>
<p>You should end up with this:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Android.Appwidget</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">BluetoothWidget</span>
<span class="p">{</span>
<span class="p">[</span><span class="nf">MetaData</span><span class="p">(</span><span class="s">"android.appwidget.provider"</span><span class="p">,</span> <span class="n">Resource</span> <span class="p">=</span> <span class="s">"@xml/bt_widget"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">BTWidget</span> <span class="p">:</span> <span class="n">AppWidgetProvider</span>
<span class="p">{</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="the-widget-layout">The widget layout</h3>
<p>I said we’d come back to the <code class="language-plaintext highlighter-rouge">android:initialLayout="@layout/initial_layout"</code> attribute. This just points at another xml file (actually an <code class="language-plaintext highlighter-rouge">.axml</code>) file in this case, which defines how the widget will be laid out on screen. It’s nothing fancy. As you saw in the screenshot at the beginning of this article, our widget just looks like a 1x1 Bluetooth icon, sitting on the desktop. Basically all we’ve done is stuck a picture of the standard Bluetooth symbol on an <code class="language-plaintext highlighter-rouge">ImageButton</code>. I picked an ImageButton because it’ll allow me to add a click event handler at some point, which will make it more useful. I wrapped it in a <code class="language-plaintext highlighter-rouge">LinearLayout</code> because we have to put it in some sort of layout to keep the compiler and renderer happy.</p>
<p>Create a new xml file called <code class="language-plaintext highlighter-rouge">initial_layout.axml</code> in the <code class="language-plaintext highlighter-rouge">Resources\layout</code> folder, and paste in the code below:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><LinearLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">android:id=</span><span class="s">"@+id/widget"</span>
<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:orientation=</span><span class="s">"vertical"</span><span class="nt">></span>
<span class="c"><!-- ImageButton creates a View which we can place an image on,
and gives us access to a click-handler too --></span>
<span class="nt"><ImageButton</span>
<span class="na">android:id=</span><span class="s">"@+id/imgBluetooth"</span>
<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_gravity=</span><span class="s">"center"</span>
<span class="na">android:src=</span><span class="s">"@drawable/bluetooth_off"</span>
<span class="na">style=</span><span class="s">"@style/WidgetBackground"</span>
<span class="nt">/></span>
<span class="nt"></LinearLayout></span></code></pre></figure>
<p>Like I said, nothing fancy.</p>
<p>The only part that might be of interest is the <code class="language-plaintext highlighter-rouge">style</code> attribute on the ImageButton. This points to a <code class="language-plaintext highlighter-rouge">styles.xml</code> file off the <code class="language-plaintext highlighter-rouge">Resources\values</code> folder (again, you’ll need to create this file yourself), and sets the background colour of the widget to transparent. If you don’t do this, it may look a little clunky because it doesn’t blend in properly.</p>
<p>Create a file called <code class="language-plaintext highlighter-rouge">styles.xml</code> in the <code class="language-plaintext highlighter-rouge">Resources\values</code> folder, and paste in the small chunk of XML below:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><resources></span>
<span class="nt"><style</span> <span class="na">name=</span><span class="s">"WidgetBackground"</span><span class="nt">></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"android:background"</span><span class="nt">></span>@android:color/transparent<span class="nt"></item></span>
<span class="nt"></style></span>
<span class="nt"></resources></span></code></pre></figure>
<p>At this point, your solution should look something like this (ignore those <code class="language-plaintext highlighter-rouge">drawable-</code> folders for the moment):</p>
<p><a class="fancybox" rel="group" href="/images/btwidget/bt_widget_solution_structure.png" title="Bluetooth Widget Solution"><img class="img-responsive img-thumbnail inline" src="/images/btwidget/bt_widget_solution_structure.png" alt="" /></a></p>
<h3 id="icons-and-drawables">Icons and ‘Drawables’</h3>
<p>OK, so you noticed those <code class="language-plaintext highlighter-rouge">drawable</code>-related folders. These aren’t created by default, I had to add them manually.</p>
<p>The <code class="language-plaintext highlighter-rouge">dpi</code> suffixes allow the compiler and renderer to build and select the correct resolution of icons and images to use for the best display, depending on the device they’re running on. The suffixes are convention based, so referencing <code class="language-plaintext highlighter-rouge">@drawable/bluetooth_off</code> on a high-DPI device would load the <code class="language-plaintext highlighter-rouge">bluetooth_off.png</code> file from the <code class="language-plaintext highlighter-rouge">drawable-hdpi</code> folder. Referencing that same resource on a low-DPI device would go looking for, and load, a file with the same name from the <code class="language-plaintext highlighter-rouge">drawable-ldpi</code> folder. And so on.</p>
<p>The Android subsystem knows the resolution level of the device it’s running on, so it’ll be able to automatically select the correct one (assuming you’ve provided it with one). It’ll fall back automatically if you didn’t, until it ends up in the <code class="language-plaintext highlighter-rouge">Resources\drawable</code> folder.</p>
<p>For the purposes of this widget, I used a freeware, non-commercial Bluetooth symbol image created by <a href="http://iconfactory.com">IconFactory</a>, which I downloaded from <a href="http://findicons.com/icon/131341/bluetooth_file_exchange?id=131341">FindIcons</a>. Because it’s ‘freeware, non-commercial’, you cannot use it in a commercial product (which this isn’t, so we should be fine). So do not copy these and use them for your own commercial purposes without paying for them.</p>
<p>This symbol is 128x128 pixels, which is bigger than most of the <code class="language-plaintext highlighter-rouge">-dpi</code> settings I needed, but that’s nothing that Paint.Net couldn’t fix. I also needed an ‘off’ or ‘disabled’ version of the image, so I copied the original png file, and cranked the saturation down to 0, which effectively turned the background from blue to grey.
I ended up with two images:</p>
<ul class="list-unstyled list-inline">
<li><img class="img-responsive img-thumbnail inline" src="/images/btwidget/bluetooth_on.png" alt="bluetooth on" /></li>
<li><img class="img-responsive img-thumbnail inline" src="/images/btwidget/bluetooth_off.png" alt="bluetooth off" /></li>
</ul>
<p>I resized the images from the original 128x128 pixels to</p>
<ul>
<li>36x36 (ldpi),</li>
<li>48x48 (mdpi),</li>
<li>72x72 (hdpi),</li>
<li>96x96 (xhdpi), and</li>
<li>144x144 (xxhdpi)</li>
</ul>
<p><em>(This last was obviously an up-size, but since the increase is negligible, it shouldn’t look too clunky on xxhdpi screens. This is just an example anyway.)</em></p>
<p>I then made <code class="language-plaintext highlighter-rouge">dpi</code>-related folders for each of them, and copied them in, adding them to the project as I went.</p>
<p>Finally I copied the <code class="language-plaintext highlighter-rouge">bluetooth_on.png</code> version of each one to an <code class="language-plaintext highlighter-rouge">icon.png</code>, in the same folder, so that there would be an appropriately named (and sized) <code class="language-plaintext highlighter-rouge">@drawable/icon</code> for each screen resolution.</p>
<h3 id="capturing-messages-about-bluetooth-state-changes">Capturing messages about bluetooth state changes</h3>
<p>Every time the bluetooth adapter state changes, the Android OS sends a notification message (‘intent’) to anyone who’s interested. Intents will often carry a payload, containing more information about what actually happened.</p>
<p>In order to let the OS know you’re interested in receiving these messages, you need to let it know you want to receive broadcasts of intents. You do this, by marking the class with a <code class="language-plaintext highlighter-rouge">[BroadcastReceiver]</code> attribute. This sets it up to receive <em>ALL</em> intents (there are thousands of different types), most of which you’ll neither want to know (nor care) about. We’ll see how to filter for just the ones we want, in a moment.</p>
<p>You should now have something like:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Android.Appwidget</span><span class="p">;</span>
<span class="k">namespace</span> <span class="nn">BluetoothWidget</span>
<span class="p">{</span>
<span class="p">[</span><span class="nf">BroadcastReceiver</span><span class="p">(</span><span class="n">Label</span> <span class="p">=</span> <span class="s">"Bluetooth Widget"</span><span class="p">)]</span>
<span class="p">[</span><span class="nf">MetaData</span><span class="p">(</span><span class="s">"android.appwidget.provider"</span><span class="p">,</span> <span class="n">Resource</span> <span class="p">=</span> <span class="s">"@xml/bt_widget"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">BTWidget</span> <span class="p">:</span> <span class="n">AppWidgetProvider</span>
<span class="p">{</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="hooking-all-the-bits-up">Hooking all the bits up</h3>
<p>Now that we’ve added all the bits related to displaying the widget (and gained some small appreciation for how much work must really go into an actual graphically intensive Android application), it’s time to hook them up to some code.</p>
<p>First, we need to add an <em>Intent Filter</em> to the class. This will allow it to register itself with the OS as a filter for catching and processing specific kinds of Android intents.</p>
<p>For the purposes of this example, we’re interested in intercepting two kinds of Android intents: <code class="language-plaintext highlighter-rouge">"android.appwidget.action.APPWIDGET_UPDATE"</code> (which the system would be sending to our widget to tell it to update itself, for example when it’s first installed, or on a schedule). The other is the Bluetooth state-change notification intent (<code class="language-plaintext highlighter-rouge">Bluetooth.Adapter.ActionStateChanged</code>), which carries two integer values as a payload: one to say what the previous state was, and what state it’s in now. These integer values will resolve to <code class="language-plaintext highlighter-rouge">Android.Bluetooth.State</code> enum values.</p>
<p>So add the <code class="language-plaintext highlighter-rouge">IntentFilter</code> attribute, with a string array containing the two intents as follows (the order in which you list them doesn’t matter):</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="p">[</span><span class="nf">BroadcastReceiver</span><span class="p">(</span><span class="n">Label</span> <span class="p">=</span> <span class="s">"Bluetooth Widget"</span><span class="p">)]</span>
<span class="p">[</span><span class="nf">MetaData</span><span class="p">(</span><span class="s">"android.appwidget.provider"</span><span class="p">,</span> <span class="n">Resource</span> <span class="p">=</span> <span class="s">"@xml/bt_widget"</span><span class="p">)]</span>
<span class="p">[</span><span class="nf">IntentFilter</span><span class="p">(</span><span class="k">new</span> <span class="kt">string</span><span class="p">[]</span> <span class="p">{</span> <span class="s">"android.appwidget.action.APPWIDGET_UPDATE"</span><span class="p">,</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ActionStateChanged</span> <span class="p">})]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">BTWidget</span> <span class="p">:</span> <span class="n">AppWidgetProvider</span></code></pre></figure>
<p>Now that Android knows it needs to send us these intents, we need to actually do something with them when they arrive. This is done by</p>
<ul>
<li>Overriding the <code class="language-plaintext highlighter-rouge">OnReceive</code> method. This will fire for every intent you’re filtering for, when it arrives (and they can arrive <strong>en masse and quickly</strong>!),</li>
<li>Grabbing the intent you’re interested in, and</li>
<li>Processing it and any payload it’s carrying, to do what you need to do.</li>
</ul>
<p>So, add an override for <code class="language-plaintext highlighter-rouge">OnReceive</code> (this was provided by the <code class="language-plaintext highlighter-rouge">AppWidgetProvider</code> you originally inherited the class from):</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnReceive</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="n">Intent</span> <span class="n">intent</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">intent</span><span class="p">.</span><span class="n">Action</span> <span class="p">==</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ActionStateChanged</span><span class="p">)</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="n">APP_NAME</span><span class="p">,</span> <span class="s">"Received BT Action State change message"</span><span class="p">);</span>
<span class="nf">ProcessBTStateChangeMessage</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">intent</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><code class="language-plaintext highlighter-rouge">context</code> is basically “this application”, which we’re not really worried about. But we do care about that <code class="language-plaintext highlighter-rouge">intent</code>, which could be one of either <code class="language-plaintext highlighter-rouge">APPWIDGET_UPDATE</code> or <code class="language-plaintext highlighter-rouge">ActionStateChanged</code>. So we grab it, see if it’s one that we want, and if so, we hand it off to a custom method to process it.</p>
<p>The custom method really only consists of:</p>
<ul>
<li>looking at the payload,</li>
<li>reading the previous state and new state (e.g. maybe it went from <code class="language-plaintext highlighter-rouge">Android.Bluetooth.State.Off</code> to <code class="language-plaintext highlighter-rouge">Android.Bluetooth.State.TurningOn</code>)
<ul>
<li>we don’t even really need the previous state, but I’ve included it here for demo purposes.</li>
</ul>
</li>
<li>changing the UI of the widget to indicate the new state.</li>
</ul>
<p><em>Android has some fairly strict protocols to kill applications that take too long to do things. So make sure you get in, get your stuff done, and get out again as quickly as you can. Otherwise you may end up with an “Application Not Responding” (ANR) message, which is one of the fastest ways to get your widget uninstalled, especially if it does it a lot. If something is going to take a while (> ~5 seconds), you’re better handing it off to something <code class="language-plaintext highlighter-rouge">async</code>, or <code class="language-plaintext highlighter-rouge">Task</code> or a <code class="language-plaintext highlighter-rouge">Service</code>, so it doesn’t lock everything up.</em></p>
<p>The custom method is below (you might want to put in some logging for debugging/info purposes, I’ve left it out for the sake of brevity, though I’ve added some comments for clarity):</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">void</span> <span class="nf">ProcessBTStateChangeMessage</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="n">Intent</span> <span class="n">intent</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// we don't use prevState, this is just for example purposes</span>
<span class="kt">int</span> <span class="n">prevState</span> <span class="p">=</span> <span class="n">intent</span><span class="p">.</span><span class="nf">GetIntExtra</span><span class="p">(</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ExtraPreviousState</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">newState</span> <span class="p">=</span> <span class="n">intent</span><span class="p">.</span><span class="nf">GetIntExtra</span><span class="p">(</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ExtraState</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">);</span>
<span class="c1">// get the running instance of the current app widget manager</span>
<span class="kt">var</span> <span class="n">appWidgetManager</span> <span class="p">=</span> <span class="n">AppWidgetManager</span><span class="p">.</span><span class="nf">GetInstance</span><span class="p">(</span><span class="n">context</span><span class="p">);</span>
<span class="c1">// get the list of all currently running Views based on the type of 'this' app, </span>
<span class="c1">// which are Views of type Resource.Layout.initial_layout. </span>
<span class="c1">// Remember, there may be more than one instance of your widget running. </span>
<span class="kt">var</span> <span class="n">remoteViews</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">RemoteViews</span><span class="p">(</span><span class="n">context</span><span class="p">.</span><span class="n">PackageName</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">initial_layout</span><span class="p">);</span>
<span class="c1">// get this _specific_ widget</span>
<span class="kt">var</span> <span class="n">thisWidget</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ComponentName</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">Class</span><span class="p">);</span>
<span class="c1">// allocate a variable to hold an image resource ID, and set it to the default 'off' image.</span>
<span class="kt">int</span> <span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_off</span><span class="p">;</span>
<span class="c1">// set that imgResource variable based on the new state. If the new state cannot be </span>
<span class="c1">// matched, it'll default to the 'off' version (as above)</span>
<span class="k">switch</span><span class="p">((</span><span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">)</span><span class="n">newState</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">Off</span><span class="p">:</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">TurningOn</span><span class="p">:</span>
<span class="p">{</span>
<span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_off</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">On</span><span class="p">:</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">TurningOff</span><span class="p">:</span>
<span class="p">{</span>
<span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_on</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">default</span><span class="p">:</span>
<span class="p">{</span>
<span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_off</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// set the image on the ImageButton to the chosen image resource ID</span>
<span class="n">remoteViews</span><span class="p">.</span><span class="nf">SetImageViewResource</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">imgBluetooth</span><span class="p">,</span> <span class="n">imgResource</span><span class="p">);</span>
<span class="c1">// force an update (which in our case is just a redraw)</span>
<span class="n">appWidgetManager</span><span class="p">.</span><span class="nf">UpdateAppWidget</span><span class="p">(</span><span class="n">thisWidget</span><span class="p">,</span> <span class="n">remoteViews</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>And that’s basically it. We’re done.</p>
<p>Or are we?</p>
<h3 id="widget-startup">Widget startup</h3>
<p>As it stands, this widget works quite well to report on bluetooth adapter state changes. With “changes” being the operative word. When you initially install the widget, it’s highly unlikely that the bluetooth state will be changing just as you do so. So it’s also likely that the initial “off” status that the widget is installed with will be wrong if your bluetooth is switched on at the time.</p>
<p>There’s one last thing we need to do: We need to react to the <code class="language-plaintext highlighter-rouge">android.appwidget.action.APPWIDGET_UPDATE</code> intent that is broadcast to our widget(s) when they’re initially installed, and use that as a trigger to check the bluetooth adapter state. We can then use the same code to set the widget’s initial display to match.</p>
<p>Modify the <code class="language-plaintext highlighter-rouge">OnReceive</code> method to also react to the <code class="language-plaintext highlighter-rouge">APPWIDGET_UPDATE</code> intent:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnReceive</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="n">Intent</span> <span class="n">intent</span><span class="p">)</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="n">APP_NAME</span><span class="p">,</span> <span class="s">"OnReceive received intent: {0}"</span><span class="p">,</span> <span class="n">intent</span><span class="p">.</span><span class="n">Action</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">intent</span><span class="p">.</span><span class="n">Action</span> <span class="p">==</span> <span class="s">"android.appwidget.action.APPWIDGET_UPDATE"</span><span class="p">)</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="n">APP_NAME</span><span class="p">,</span> <span class="s">"Received AppWidget Update"</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">currentState</span> <span class="p">=</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">DefaultAdapter</span><span class="p">.</span><span class="n">State</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="n">APP_NAME</span><span class="p">,</span> <span class="s">"BT adapter state currently {0}"</span><span class="p">,</span> <span class="n">currentState</span><span class="p">);</span>
<span class="nf">UpdateWidgetDisplay</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">currentState</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">intent</span><span class="p">.</span><span class="n">Action</span> <span class="p">==</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ActionStateChanged</span><span class="p">)</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="n">APP_NAME</span><span class="p">,</span> <span class="s">"Received BT Action State change message"</span><span class="p">);</span>
<span class="nf">ProcessBTStateChangeMessage</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">intent</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>…and we can refactor out the common code to to an <code class="language-plaintext highlighter-rouge">UpdateWidgetDisplay</code> method (and shorten <code class="language-plaintext highlighter-rouge">ProcessBTStateChangeMessage</code> accordingly):</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">void</span> <span class="nf">UpdateWidgetDisplay</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="kt">int</span> <span class="n">newState</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">appWidgetManager</span> <span class="p">=</span> <span class="n">AppWidgetManager</span><span class="p">.</span><span class="nf">GetInstance</span><span class="p">(</span><span class="n">context</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">remoteViews</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">RemoteViews</span><span class="p">(</span><span class="n">context</span><span class="p">.</span><span class="n">PackageName</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">initial_layout</span><span class="p">);</span>
<span class="n">Log</span><span class="p">.</span><span class="nf">Debug</span><span class="p">(</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="s">"this.GetType().ToString(): {0}"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nf">GetType</span><span class="p">().</span><span class="nf">ToString</span><span class="p">());</span>
<span class="kt">var</span> <span class="n">thisWidget</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ComponentName</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">Class</span><span class="p">);</span>
<span class="n">Log</span><span class="p">.</span><span class="nf">Debug</span><span class="p">(</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="n">thisWidget</span><span class="p">.</span><span class="nf">FlattenToString</span><span class="p">());</span>
<span class="n">Log</span><span class="p">.</span><span class="nf">Debug</span><span class="p">(</span><span class="n">APP_NAME</span><span class="p">,</span> <span class="s">"remoteViews: {0}"</span><span class="p">,</span> <span class="n">remoteViews</span><span class="p">.</span><span class="nf">ToString</span><span class="p">());</span>
<span class="kt">int</span> <span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_off</span><span class="p">;</span>
<span class="k">switch</span><span class="p">((</span><span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">)</span><span class="n">newState</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">Off</span><span class="p">:</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">TurningOn</span><span class="p">:</span>
<span class="p">{</span>
<span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_off</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">On</span><span class="p">:</span>
<span class="k">case</span> <span class="n">Android</span><span class="p">.</span><span class="n">Bluetooth</span><span class="p">.</span><span class="n">State</span><span class="p">.</span><span class="n">TurningOff</span><span class="p">:</span>
<span class="p">{</span>
<span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_on</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">default</span><span class="p">:</span>
<span class="p">{</span>
<span class="n">imgResource</span> <span class="p">=</span> <span class="n">Resource</span><span class="p">.</span><span class="n">Drawable</span><span class="p">.</span><span class="n">bluetooth_off</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">remoteViews</span><span class="p">.</span><span class="nf">SetImageViewResource</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">imgBluetooth</span><span class="p">,</span> <span class="n">imgResource</span><span class="p">);</span>
<span class="n">appWidgetManager</span><span class="p">.</span><span class="nf">UpdateAppWidget</span><span class="p">(</span><span class="n">thisWidget</span><span class="p">,</span> <span class="n">remoteViews</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>…and the new <code class="language-plaintext highlighter-rouge">ProcessBTStateChangeMessage</code> now looks like:</p>
<figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">private</span> <span class="k">void</span> <span class="nf">ProcessBTStateChangeMessage</span><span class="p">(</span><span class="n">Context</span> <span class="n">context</span><span class="p">,</span> <span class="n">Intent</span> <span class="n">intent</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">prevState</span> <span class="p">=</span> <span class="n">intent</span><span class="p">.</span><span class="nf">GetIntExtra</span><span class="p">(</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ExtraPreviousState</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">newState</span> <span class="p">=</span> <span class="n">intent</span><span class="p">.</span><span class="nf">GetIntExtra</span><span class="p">(</span><span class="n">BluetoothAdapter</span><span class="p">.</span><span class="n">ExtraState</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">);</span>
<span class="kt">string</span> <span class="n">message</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"Bluetooth State Change from {0} to {1}"</span><span class="p">,</span> <span class="n">prevState</span><span class="p">,</span> <span class="n">newState</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="n">APP_NAME</span><span class="p">,</span> <span class="n">message</span><span class="p">);</span>
<span class="nf">UpdateWidgetDisplay</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">newState</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p><strong>Now</strong> we’re done.</p>
<table>
<tbody>
<tr>
<td>You should now be able to build and deploy the widget to your device. In Xamarin Studio this is done by selecting “Run</td>
<td>Upload to device” from the main menu. In Visual Studio, it’s done by selecting “Build</td>
<td>Deploy Solution” or “Build</td>
<td>Deploy BluetoothWidget” from the main menu.</td>
</tr>
</tbody>
</table>
<p>Once the project is built and has been deployed successfully, you can then install it (usually) by long-pressing on a home screen, selecting the option to add a widget, and picking it from the list. You may have to manually remove it between upgrades and fixes as well, as the (re)deployment procedure doesn’t always remove it from the screen.</p>
<p><a class="fancybox" rel="group" href="/images/btwidget/installing_bt_widget.png" title="Installing Bluetooth widget on home screen"><img class="img-responsive img-thumbnail inline" src="/images/btwidget/installing_bt_widget.png" alt="Installing Bluetooth widget on home screen" /></a></p>
<p>Once it’s installed, turn your bluetooth on and off to see the state change from grey to blue (or vice versa). It’s not instantaneous; it may take a second or two for your bluetooth adapter to power up and down.</p>
<p><em>Note that you can’t “run” a widget, which makes it more difficult to debug. The best you can do with this particular one is to add some logging, if things are misbehaving, to try and figure out what’s wrong.</em></p>
<h3 id="some-notes">Some notes</h3>
<p>As I mentioned, there’s no easy way to actually debug widgets, short of adding lots of logging. So keep them dumb. That way there’s fewer places for things to break.</p>
<p>Remember that the widget may trigger an ANR if it takes too long to do something. In our case all it’s doing is swapping out an image that’s been embedded as a resource, so that’s not a big deal. But if it had to load that image off the net, or do some heavy calculations, that might be enough to break it. Any complex code should go into an external service (which you can also debug if necessary, by hooking it up to a separate application).</p>
<p>This widget, as it stands, is obviously a little contrived. But it does serve to provide an intro to building widgets with Xamarin. In a future blog post, I’ll add some more functionality to it, to get it to tell us if we’re connected to something (and maybe even what it’s connected to). And I’ll also solve the problem of allowing us to enable and disable the bluetooth by tapping on the widget.</p>
<p>In my <a href="/posts/2014/08/23/make-a-xamarin-android-widget-to-enable-disable-bluetooth">follow-up</a>, I’ll show you how to enable and disable the Bluetooth adapter by tapping on the widget, as well as monitoring its connectivity state.</p>
<h3 id="wheres-the-source-code">Where’s the source code?</h3>
<p>The source code is <a href="https://github.com/wislon/xamarin-bluetooth-status-widget">up on GitHub</a>. Go ahead and clone it, or fork it, or use the ‘Download ZIP’ button on the right hand side of that page top get your own copy. Have a look, load it up, and experiment with it. You’ll learn far more from actually fiddling with the code, breaking stuff, and deploying it than you ever will just scanning through the dead code. It’s open-source (of course) and I’ve published it under the MIT license, so you can do what you like with it (bearing in mind any restrictions on image copyrights).</p>
<h3 id="some-additional-resources">Some additional resources</h3>
<p>I found these pages and sites useful while I was researching this and other widgets I’ve built for personal use, and I am sure you will too. A number of them reference Java APIs and pure Android development, but the Xamarin guys have made sure to keep things (and the naming of things) close enough that you’ll easily be able to understand what’s going on. Feel free to ask questions (or offer corrections!) in the comments below. And, like I said, and this is a public OSS repo on GitHub so please feel free to fork it or improve it.</p>
<ul>
<li>Android Widget Basics: <a href="https://developer.android.com/guide/topics/appwidgets/index.html">https://developer.android.com/guide/topics/appwidgets/index.html</a></li>
<li>Android Bluetooth Basics: <a href="http://developer.android.com/guide/topics/connectivity/bluetooth.html">http://developer.android.com/guide/topics/connectivity/bluetooth.html</a> (on/off states)</li>
<li>Android Bluetooth Adapter class: <a href="http://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html">http://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html</a></li>
<li>Android Widget Tutorial: <a href="http://www.tutorialspoint.com/android/android_widgets.htm">http://www.tutorialspoint.com/android/android_widgets.htm</a></li>
<li>Xamarin ‘Word of the day’ widget example: <a href="http://developer.xamarin.com/samples/SimpleWidget/">http://developer.xamarin.com/samples/SimpleWidget/</a> (doesn’t work out of the box, gave me a ‘403 forbidden’ error)</li>
<li>Bluetooth Icon/Widget image: <a href="http://findicons.com/icon/131341/bluetooth_file_exchange?id=131341">http://findicons.com/icon/131341/bluetooth_file_exchange?id=131341</a> (via http://iconfactory.com, it’s freeware, for non-commercial use only)</li>
<li>StackOverflow - How do I identify and update ‘me’ (i.e. <em>this</em> widget)? <a href="http://stackoverflow.com/questions/4073907/update-android-widget-from-activity?rq=1">http://stackoverflow.com/questions/4073907/update-android-widget-from-activity?rq=1</a></li>
</ul>
GitHub Atom Editor numeric keypad bindings2014-07-14T00:00:00+00:00http://blog.wislon.io/posts/2014/07/14/atom-editor-bind-number-keypad<p>I downloaded and installed the ‘new’ <a href="https://atom.io/">Atom editor</a> from GitHub (the Windows version). It’s pretty clear they’ve got their initial inspiration from <a href="http://www.sublimetext.com/">Sublime Text</a> (a really great editor by the way, if you’re using the unregistered version, you really should buy it). Sublime has got a really good thing going for it, and they do say that imitation is the sincerest form of flattery. If I could just find a package for it that did proper CSS class name completion, I could ditch some of the other stuff I have to use for website development (Visual Studio, I am looking at you). But this isn’t a post about comparing editors, there’s too many of those already.</p>
<p>One thing the <strong>Atom</strong> guys and gals seem to have forgotten is some keyboard bindings for the secondary keys on the numeric keypad. I’m old school. I grew up with keyboards that didn’t have a specific set of arrow keys between the numeric keypad and the rest of the keys. You probably think I’m a dinosaur for saying so, but i never fully grokked those miniature arrow keys. I have big hands, so it really annoys me when I have to footle about with the tips of my fingers on inadequately-spaced tiny keys which are too close together, each one smaller than a postage stamp. So I use the numeric keypad, with the num-lock turned off.</p>
<p>It also means I got used to being able to use stuff like <code class="language-plaintext highlighter-rouge">ctrl+shift+end</code> to quickly select everything to the end of a document, without having to pick my hand up from the aforementioned dinky arrow keys, to move them to an equally dinky set of keys just above them, to do the same. Click-and-drool programmers, and some Gen-Y folk may scoff at me for doing so, but be that as it may, everything I need is right there on the numeric keypad.</p>
<p>So the first thing I noticed when I opened Atom was that I couldn’t navigate through the editor at all. No up, no down, nothing. Because I was using the keypad. Rinky-dinky arrow keys worked fine. I thought that was rather silly. So I did a bit of poking around, found the Atom settings, and noticed that nothing was mapped to the secondary keys on the numeric keypad. I’m lazy. And I thought that someone on the Internet had probably done this already, since it’s a big bathroom wall of code. Maybe there was somewhere I could copy and paste from? Nope.</p>
<p>Really?</p>
<p>Nope. Or at least Google doesn’t seem to think so. Nor does the Atom forum (though there’s a few people asking for them).</p>
<p>So I made my own, which map to the standard, expected Windows functionality. Here they are.</p>
<table>
<tbody>
<tr>
<td>*Simply copy and paste them into your <code class="language-plaintext highlighter-rouge">keymap.cson</code> file (accessible from **File</td>
<td>Settings</td>
<td>Keybindings<em>*, where it says <strong>“You can override these keybindings by copying and pasting them into your keymap file”</strong>)</em></td>
</tr>
</tbody>
</table>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>'.editor':
'num-left': 'core:move-left'
'num-right': 'core:move-right'
'num-up': 'core:move-up'
'num-down': 'core:move-down'
'num-end' : 'editor:move-to-end-of-screen-line'
'num-home' : 'editor:move-to-first-character-of-line'
'num-pageup': 'core:page-up'
'num-pagedown': 'core:page-down'
'num-delete': 'core:delete'
'ctrl-num-home': 'core:move-to-top'
'ctrl-num-end': 'core:move-to-bottom'
'shift-num-delete': 'core:cut'
'shift-num-insert': 'core:paste'
'ctrl-num-insert': 'core:copy'
'shift-num-left': 'core:select-left'
'shift-num-right': 'core:select-right'
'shift-num-up': 'core:select-up'
'shift-num-down': 'core:select-down'
'shift-num-end' : 'editor:select-to-end-of-line'
'shift-num-home' : 'editor:select-to-first-character-of-line'
'shift-num-pageup': 'core:select-page-up'
'shift-num-pagedown': 'core:select-page-down'
'ctrl-shift-num-left': 'editor:select-to-beginning-of-word'
'ctrl-shift-num-right': 'editor:select-to-end-of-word'
'ctrl-shift-num-end': 'core:select-to-bottom'
'ctrl-shift-num-home': 'core:select-to-top'
</code></pre></div></div>
<p>There’s also a handy <a href="https://gist.github.com/wislon/84ceb8cdf2f7c17e6efc">GitHub gist</a> for it, which I may or may not keep updated depending on my personal requirements.</p>
<p>I had to do a bit of fiddling to figure out what was ‘core’ and what was ‘editor’. But with a combination of <code class="language-plaintext highlighter-rouge">ctrl+.</code> (that’s holding down <code class="language-plaintext highlighter-rouge">ctrl</code> and pressing the <code class="language-plaintext highlighter-rouge">.</code> key) which shows you which keys Atom thinks you’re pressing, and digging through the other key bindings, I made it work.</p>
<p>They take effect immediately, once you save they <code class="language-plaintext highlighter-rouge">keybindings.cson</code> file with <code class="language-plaintext highlighter-rouge">ctrl+s</code>.</p>
<p>Atom is a good editor, but it’s not great. Not yet. It’s slow to start up. Really slow. But that’s not really a deal-breaker if you have it open all the time. There doesn’t appear to be a way to refresh the folder tree view without reopening the folder, and it does strange things to other windows when you <code class="language-plaintext highlighter-rouge">alt-tab</code> to them. But it has a really neat live markdown preview window which you can toggle on and off, and it can interact with your git repo, and GitHub out of the box, which is awesome.</p>
Games recruiters play2014-07-09T00:00:00+00:00http://blog.wislon.io/posts/2014/07/09/games-recruiters-play<p>So I was having a couple of beers the other night with a few other IT folk, swapping war stories, gossiping, the usual. A couple of them were unhappy at their current, permanent jobs, and thinking of trying contracting for a while. Maybe make a bit more money, or just to make a change. For someone who hasn’t done it before, leaving a stable, full-time job can be a little scary, especially if it’s in a foreign country (which for some of them this is). And they were asking all the usual questions: What’s the next step? How do I resign? Do I know of any available jobs? Can I get them a job? How long will it take to find a new job? What skills do I need? Do I need a recruiter? Which recruiters do you use? Or not? Why not?</p>
<p>That last one really opened a can of worms! Everyone had a pet horror story. We compared notes. There were some particularly disturbing tales about some recruitment agencies, some of their agents, and the steps they were taking to ensure that you got a job (at someone else’s expense), didn’t get a job, lost a job, or were sent on a wild-goose chase.</p>
<p>Then one of the guys piped up: “Hey wislon,” he said, (and he really does call me ‘wislon’), “you should write a blog post about it.”</p>
<p>So I did. And here it is. It has an IT aspect to it, but I think the behaviour is endemic, and can be found across all industries.</p>
<p>Job loss is inevitable. Being a contractor means that by definition you’re going to be out of work occasionally. When I started contracting, the idea of not having a “permanent” job used to bother me. But I’ve been doing it for a while now, and as long as you’re careful with your budget, and keep your skills sharp, you’ll be fine. And these days I don’t think there’s such a thing as a permanent job anyway. There’s a hundred reasons it could go away: your contract is up, the project is done, there’s no more money, there’s a change of government, or a particularly inept new manager decides they absolutely, positively want to buy-cheap-and-pay-dearly to outsource your job to a body-shop in Bangalore.</p>
<p>Or maybe you just got so sick of what you were doing that you threw in the towel and left, with nowhere to go.</p>
<p>You can become too expensive, obsolete, or just plain irrelevant in a heartbeat. And that’s actually OK, provided you’ve got a Plan B. You do have a Plan B, don’t you? Because the days of a job-for-life, with a gold watch and a handshake at the end, are over. You should always have an exit strategy or a backup plan. No matter how good you think you are, there’s always someone younger, smarter, faster and cheaper than you, and your experience only counts for so much.</p>
<p>And once you’re out of work, with a mortgage, bills and mouths to feed, there’s pressure to find something else. Hopefully you’ll be able to something you like doing, that pays really well. You ask your mates, previous clients, ping a few folks in your social network, and have a bit of a poke around on the job boards. If you’re lucky, you can segue into something else immediately. But sometimes it just doesn’t work out. Your network fails and you have to dust off that resume, and start whoring yourself out via recruitment agencies. There’s a reason we contractors call them “our pimps”.</p>
<p>Before you do that though, here’s a couple of things you’ll want to keep an eye out for. They may save you some aggravation.
I’ve had variations of all of these happen to me, and I’ve heard similar tales from other people. Some seem pretty common, and not limited to one agency in particular. But I’ve not seen any of these described anywhere else. I didn’t want this to read too much like a BuzzFeed listicle, but here’s a few of my most anti-favourites in the “Games Recruiters Play”. If you’ve ever dealt with a particular breed of recruiter or the agency they represent, what comes next will probably neither shock nor amaze you.</p>
<h3 id="the-cock-block">The Cock-Block</h3>
<p>You get a phone call from a recruiter saying that Initech is going to be hiring soon. They’ll be advertising privately, but they know someone on the inside, and if you can get your resume to them right away, they’ll make sure you’re the leading candidate. It’s a sure thing. Just please don’t send your resume directly to the company, as it’ll just confuse things, since the recruiter will be the one representing you. <em>In Australia, they usually have to ask for your permission, in writing, to represent you exclusively. The more ethical ones will, anyway. Once you’ve done that, it’s then best to stay with them for that application. But if they don’t ask you, then they may not be on the level</em>.</p>
<p>A few days later, Initech does indeed post the job, and there’s a <strong>“No Recruiters”</strong> statement in there somewhere. But that’s OK, right? That recruiter you spoke to knows someone, and they’re doing you a favour, right? Wrong. Suddenly you can’t get hold of the recruiter to find out if they submitted the resume. It turns out one of your mates knows someone who works there (and why didn’t you know this already?), and can get your resume in front of the right people… only now you can’t give them your resume because apparently you’re already represented and it’ll mess up your chances… right?</p>
<p><strong>Bullshit</strong>. If you can’t verify that your resume was ever sent, then send it again, directly this time. Any recruiter worth your attention will be in touch with you again as soon as that job posting appears, to let you know that the ball is rolling. If you can’t get hold of them, especially over the next few days, then there’s a good chance they’ve pulled a fast one. I honestly don’t even know why they do this. Maybe there’s a preferred candidate they’d rather not have you competing against? Or they’re trying to get back at someone for something? One international agency with a big, shiny office in Brisbane appears to do this fairly consistently, so I tell everyone to avoid them.</p>
<p><strong>Lesson learned</strong>: I’m often asked to be a referee for people I’ve worked with, and this means speaking to representatives for companies looking at hiring them. A lot of them simply refuse to use recruiters for precisely this kind of reason. And their advice to me has always been to resubmit if you’re not sure, because if there was a “no recruiters” clause, then there’s a good chance your resume went straight into File #13 without even getting a look-in.</p>
<p><em>And if you know someone at the company willing to bump your resume on for you, even better! In some cases it’s like having a partial reference check already attached. And once you’ve got your interview, then keep your mouth shut about it, or you may be lining yourself up for…</em></p>
<h3 id="a-sucker-punch-aussies-call-this-a-king-hit">…A Sucker Punch (Aussies call this a ‘king-hit’)</h3>
<p>It’s the one you didn’t see coming. My preferred recruitment agency constantly reminds me to keep any details of any job I’m a candidate for, completely confidential. I learned why, the hard way, a couple of years ago. Acme Recruiting lined me up for what appeared to be a really sweet gig with a large distribution company. The interviews were a decent grilling (as they should be), and everything was lined up. It was a done deal. I was due to start in a couple of weeks, but in the interim they were going to be sorting out a new PC for me, getting a login and email account sorted out, and so on. Everyone was happy.</p>
<p>A few days later, I got a call from another recruiter, about another job somewhere else, and in my naiive idiocy, I told him thanks, but no, I had another gig lined up. He asked where, and through whom I’d got it. I made the mistake of telling him. Stupid. Less than three hours later, I got a phone call from Acme Recruiting, saying they didn’t know what had happened, but the company had suddenly decided to go with someone else.</p>
<p>Job gone. Just like that.</p>
<p><strong>Lesson Learned</strong>: Until you have a signed contract, you’ve got nothing. If you have something, anything, going, even if it’s just an interview, no one else needs to know about it. Keep it between you and your agency. And ask your friends not to mention it either, especially if they’re talking to other recruiters. Because some of those recruiters are always on…</p>
<h3 id="a-fishing-trip">A Fishing Trip</h3>
<p>Who’s hiring at your place? This is a classic, and it’s one you’re already familiar with if you’re in a senior role. You got that new job at Initech, writing software for their new Green Widgets. And then you made the mistake of updating your profile on LinkedIn, because hey, you’re proud to be part of it, and Green Widgets are the new Black Widgets and everyone wants to be doing that.</p>
<p>And now you’re the most popular guy in town; recruiters you’ve never heard of are calling or emailing you to ask if they can come and meet you, or introduce you to the perfect developer for your new team, or to find out how many people you need. You have to keep fobbing them off, or you’ll never get anything done, and anyway, you’re the new guy, you don’t even have the proper authority yet. So pretty soon they’re calling your project manager, HR person, development manager, and the guy who cleans the toilets, and dropping your name, saying you recommended that they call. It pisses everyone off, just when you’re trying to build a decent reputation in a new company.</p>
<p><strong>Lesson Learned</strong>: No one needs to know you’ve got a new job, other than your mates. It’s not something you really need to share with the world. Don’t share the details of any of your hiring managers with anyone either. And update your LinkedIn profile sparingly and vaguely. Or, even better, update it months later, or only <em>after</em> you leave that particular gig.</p>
<p><em>And please don’t bother calling me to fish for leads, under the pretext of telling me about a job you’ll never submit me for. There’s one recruiter who does this to me every couple of months, especially when the hiring market tanks. The international agency he works for is pretty much the only one I’ve dealt with who’s _never</em> managed to get me an interview anywhere, for anything, ever. Something other recruiters never seem to have a problem with._</p>
<h3 id="the-empty-piñata">The Empty Piñata</h3>
<p>You get a call about a job that is an ideal match for your skill-set, and is paying over and above market rates. You have to go through a recruiter, but they really want to meet you first, and tell you more about the role. You fiddle your calendar, or take some time off work (if you’re employed), and have a really good nuts and bolts chat with the recruiter. They’re having to play cagey with the company name, so they can’t really tell you who it’s for yet, but they’re really positive, and you’re totally the best fit for it.</p>
<p>You spend some more time fixing up your resume to better match what the company is apparently looking for, send it over to the recruiter. And then… nothing. Nothing at all. No feedback, no progress report, nothing. You’ve just wasted several hours helping that recruiter pad their database with your details, and achieve some nebulous KPI. There never was a job. This happens more often than you think. So much so, that <a href="http://www.brisbanetimes.com.au/small-business/trends/how-to-spot-a-fake-job-ad-20140501-37jk2.html">other people write about it</a> all the time. Well, at least you met the recruiter. But you could have done that any time, and not had to make a special trip to do so.</p>
<p><strong>Lesson Learned</strong>: The recruiter should be able to give you at least the name of the company, and send you a position description (‘PD’). Or just tell you that there’s no actual job available right now, but they’d like to meet you anyway. Just for future reference. Or at least get an updated copy of your resume. Other recruiters are able to do this. So why the subterfuge?</p>
<p><em>Being a successful salesperson (because that’s what you are) is all about relationships. Honesty and trust are everything. So why risk it by trying to put one over on your candidates?</em></p>
<h3 id="the-road-to-nowhere">The Road To Nowhere</h3>
<p>This one involves sending you for a Real! Dinkum! job interview with an organisation that has no intention of actually hiring anyone. If you’re wondering why they would even be interviewing, well, there’s a couple of reasons: maybe they’re just having a look to see what’s out there. Or maybe there are incumbents who are having to re-interview to keep their positions. The latter is more likely the case, and is more common in government-based roles, especially around the end of the financial year.</p>
<p>Usually there’s a stipulation in their labour-hire processes that says the organisation has to “go to the market”, to give them a chance to swap out anyone who’s not pulling their weight. Makes sense in principle, but in practice maybe not so much. Would you rather keep the people with the domain knowledge where they are? Or hire newbies who have to learn everything from scratch? Newbies are sinister. They usually aren’t institutionalised yet. Sometimes they have Ideas, and want to Improve Things. And for those who have spent years empire-building in an attempt to make themselves indispensable, Ideas and Improvements are definitely a no-go area. Even if the incumbent is a useless nuffa and an oxygen thief, they’ll often get to keep their position, because some organisations believe it’s simply safer to keep the devil you know.</p>
<p>As an interviewee, what they often won’t tell you is that there’s people in the positions already, no one’s going anywhere, and that the interview is basically lip service to the farce that helps managers (and recruiters) meet their KPIs, and keep their jobs. This really is a massive waste of everyone’s time.</p>
<p><strong>Lesson Learned</strong>: A warning flag is when several positions suddenly become available literally overnight. And every recruiter is calling you about them. Who suddenly, <em>desperately</em> needs eight senior software developers, just like that? Everywhere else I’ve worked brings people on board in a slow trickle over a period of weeks or months, to avoid overwhelming the existing teams and processes.</p>
<p>Ask how many incumbents there are. Then ask how many are actually leaving. No one hoofs out a bunch of employees overnight, just so they can swap them out for fresh (and hopefully cheaper and smarter) new ones*. And a decent recruiter will know if there’s actually any <strong>real</strong> positions available. Finally, if the lead interviewer spends a lot of time sighing, yawning, and looking at their watch, that’s also a dead giveaway. I know I interview quite well, because I am passionate about what I do. I definitely won’t bore you. So if I encounter that, I’d rather terminate the interview at that point, because as I said, the whole thing is a massive waste of everyone’s time.</p>
<p>*<em>Well, OK, someone recently crash-dumped around 14,000-odd IT workers on the job market in Queensland, but that’s in league of its own.</em></p>
<h3 id="the-bait-and-switch">The Bait-and-Switch</h3>
<p>So a recruiter is offering you a chance to work at a shop which will let you flex your rockstar ninja code muscles building Awesome Disruptive Tech using New Shiny. It’s a permanent role, but the team is Agile and Dynamic, you can work flex-time, and there’s X-Box and ping-pong and free beer and pizza on Fridays. Sounds good. You get the interview, knock them cold with your mad koding skillz, and then start chatting to the interviewer about the job and what it <strong>actually</strong> entails. Turns out that you’re actually being hired to normalise the three-table 250GB customer payments database because the guy you’re replacing left suddenly. You know you’ll spend the rest of your waking life making stored procedures, views and indexing all the things. There won’t actually be any rockstar or ninja muscles involved. But if you’re bored, and you want to help figure out why the build server keeps locking up, you can go ahead and come in on your own time and fix that too.</p>
<p>You’re not a database guy, but you need the work. The rate looks ok, and we all have to be a bit mercenary now and again, so you think you can do it for a while, yeah? At least until something better comes along. You’re ready to sign up, and then it starts: “Well actually, the client has decided that the rate is a bit high, and they’ve changed their minds, would you take a 20% hit?”. “No, there’s no overtime compensation, but you’ll be expected to put in some extra hours to get the job done. They’re a loyal, hard-working, dedicated team, some even come in on weekends to work on stuff”. “No, you can’t remote in, because productivity is measured by bums in seats”. “No, you won’t get to work with (the advertised) New Shiny, that’s the other team’s job, and they have enough people on it already, but if you stick around for a few months, a spot may open up”.</p>
<p>No. No. No…</p>
<p>I’ve done the death-march sweat-shop thing. I’ve got no problem with extra hours and extra responsibilities. Up to a point. But I won’t burn up the remaining hours of my life for free, for you. Everyone gets the idea of “No work, no pay”, but the converse should also be true: “No pay, no work”. I’ll not be exploited like an intern or someone whose work visa you’re threatening to cancel.</p>
<p><strong>Lesson Learned</strong>: If you’re going to pay me 40 bucks an hour, but only for the first eight hours, and then I’ll be expected to work at least another 20 hours a week (for “free”), just to get this thing over the line for you, because your massive ego goaded you into making your boss an impossible promise… No wonder you can’t find anyone to come and work for you.
That being said, this kind of thing is not necessarily the recruiter’s fault. If they’ve been given a brief that’s inaccurate or just plain wrong, there’s only so much they can do. Some companies want to use the buzzwords, even when they don’t know what they mean (a friend of mine told me of a guy who tried to sell the idea that his systems were “above the cloud”, because that had to be better than “<em>in</em> the cloud”, right?)</p>
<p><br /></p>
<h3 id="in-conclusion">In conclusion…</h3>
<p>If you, as a recruiter, have done any of these things, <a href="https://www.linkedin.com/today/post/article/20140611184904-68766496-my-history-of-abusive-co-depenadant-relationships-with-recruiters">you’re doing it wrong</a> <em>(see some of the comments on that one)</em>. And word does get around. It may explain why you’re having difficulty filling a role at Company X. No one seems interested, even though it sounds awesome. Or maybe your “candidates” aren’t returning your calls (perhaps it’s because you never return theirs, if you think you even owe them any feedback?). Why poison the well for yourself?</p>
<p>People don’t always remember the good things, and they don’t always remember the bad, but they always remember how you made them feel. And if you treat them like “resources” (a term they hate), they’ll just go elsewhere. And they’ll be spreading the word about you too. That’s you, personally. Not just your agency. If you’re a recruiter, you’re probably reading this and grinning, and thinking “heh, we’ll see!”. But you’ll be earning less this year because of it, and you won’t know. And you’ll have no way to fix it.</p>
<p>Recruitment, at least in Brisbane, seems to be pretty cut-throat. I see a lot of churn in recruitment staff too, as recruiters move between agencies every few months. Whether that’s to get a salary bump, or for other reasons, I don’t know.</p>
<p>You probably think I hate recruiters. I actually don’t. Well, except for the jerks. I’ve worked with a couple of agencies that have provided excellent service, don’t make me feel like I’m being ripped off when they take their cut, and have been professional all the way down the line. They provide constant feedback (even if it’s to tell you there is no feedback). A single-line email or even a text message is all it takes. They know who they are, because I have told them. They’ll be the first ones I call when I do start looking around for something new. And they’re the ones I recommend when people ask me which agencies they should get in contact with.</p>
We can't be experts at everything2014-05-30T00:00:00+00:00http://blog.wislon.io/posts/2014/05/30/We-cant-be-experts-at-everything<p>The longer I do this software development thing, the more I realise that we can’t be experts at everything.</p>
<p>Generally an expert is defined as someone who has more than 10,000 man-hours of experience with something. That’s a lot of hours. It means that if you’ve been doing whatever it is you do for your day job, without too many breaks, it’ll take you just over four years to get there.</p>
<p>I started learning BASIC around thirty years ago, when my dad used to bring home his HP-85 each night for us to play with. Big suitcase-looking thing, with really clunky buttons. And a screen not much larger than an original iPhone’s. By today’s standards, it was <em>really</em> limited. Limited memory (something stupidly small like 16 or 32kB of RAM) and really, really slow. It had cassette-tapes for storing programs, which used to take ages to spin up, and the whole thing sounded like a power tool when you turned it on. And occasionally, randomly, capriciously, it would corrupt itself and lose everything.</p>
<p>I loved it. I started with the normal “hello, what is your name?” console programs, and graduated to writing my own primitive games; snake and Pac-man and top-down racing games mostly.</p>
<p>By the time I hit my teens, I’d grown a little bored with BASIC, but there had been a sudden explosion in languages and computing power. Over the years that followed, I became proficient in Assembly, then Pascal and C, and then C++. There was always something new and shiny and faster and more powerful. And always, in order to learn the new stuff, you always needed more RAM, more disk space, a maths co-processor, a faster CPU. Only a couple of my friends had the remotest idea what I was ever talking about, and while they were off discovering girls, I was usually parked in front of a keyboard and monitor, trying to make my crudely drawn games and graphics more presentable.</p>
<p>Quite the stereotypical computer nerd.</p>
<p>I stuck with Pascal through high-school, and it stood me in good stead at uni. While some of the kids were still having to learn Pascal and C/C++ (on top of the actual course matter itself), I already knew those, which was an advantage, and was the go-to guy for a bit. Relatively speaking, I was an expert. But I was no longer the <strong>the</strong> expert.</p>
<p><em>And yes, I learned COBOL. Not by choice. I sometimes think those lecturers forced us to learn a bit of it just so we’d know how hard they and their punch-cards used to have it.</em></p>
<p>Good times…</p>
<p>Things settled down for a bit as Microsoft and IBM did their little dance about who was going to own the desktop. And then after Microsoft Windows appeared, there was a sudden explosion in event-driven programming languages. Visual Basic meant that now you could write applications with proper-looking buttons and menus. You could run more than one program at a time. No more TSRs! Delphi. C++Builder. Microsoft Visual C++. PowerBuilder. Fifty fucking flavours of Java: Beans. Swing. AWT. And Eclipse…</p>
<p>I tried. God help me, I tried to learn them all.</p>
<p>By then I was working for a company who wrote software for hand-held terminals (barcode scanners, stock-taking, meter reading; pretty dumb creatures by today’s standards). Proprietary stuff, mostly based on C and BASIC. You built and wrote your own hardware interfaces, and everything spoke to everything else using serial ports. We were all experts in there, too.</p>
<p>And then, overnight, there was HTML. And ASP. And PHP. And Perl. And fifty more fucking flavours of Java. JavaScript. JScript. VBScript. VBA. I was spending most of my evenings poking around on the internet, trying to learn a bit of everything, and I just couldn’t keep up. No one could. You want to try all these new, shiny things, but there just aren’t enough hours in a day.</p>
<p>At work, we ended up settling on C# and .NET for most of our PC-based development at that point, which was a bit of a relief, because trying to stay on the bleeding edge of all The New Shiny was exhausting for all of us. So it was Windows forms ftw, with various databases (which we were experimenting with too), and varying measures of success.</p>
<p><em>I’m not even going to go into the database systems that were sprouting like mushrooms at that point. Though, to be fair, SQL was pretty much SQL, and once you’d got a handle on tables and indexes and keys and 1/2/3NF, the data manipulation language is just semantics and sugar anyway… One of my former colleagues used to say, “it’s nice and quiet down here in the database.”</em></p>
<p>Then we moved to Australia. And all of a sudden, there’s high speed, cheap internet. I know that phrase may give Australians pause. High speed and cheap? In Australia? And yes, relatively speaking, Australian Internet is light-years ahead of South African Internet.</p>
<p>_Back in the early-to-mid-2000s, only reasonably large companies could afford to host and run websites there. South African online shopping was a joke. Probably still is. No one did it. No one could afford to run the sites. And it was sooo sloooow… everyone was on dial-up, even those in metropolitan areas. Their idea of “broadband” was a 384kbps line, with a 3 gigabyte limit, costing the equivalent of about $30 per gig if you wanted a top-up. That was the cheapest option. And this was in a major metropolitan area, over their copper lines. Costs went up geometrically from there. Mobile connectivity… Well, there wasn’t any. GPRS and Edge don’t really count, and no one could afford the usurious rates anyway.</p>
<p>But I digress…</p>
<p>The point I was so slowly getting around to making, is that the internet connectivity was so slow, unreliable and expensive that no one could afford to build and host their own websites. So no one built them. Which meant that no one other than people working for Microsoft shops was using ASP.NET. Or any of the fifty fucking flavours of Java-based web servers. Sure, you play with them a bit during the initial buzz over their release (they’re New And Shiny, after all), but you can’t afford to run it, or host it, and no one will come and look at it unless it’s either donkey porn or pictures of cats. So you let it slide, and go back to building your precious windows forms and console apps._</p>
<p>So I get to Australia, and my very first gig is leading a team who are building an ASP.NET website. In VB.NET. A website. This is a joke, right? No one builds websites, the Internet’s too slow and expensive. Windows Forms, that’s the way of things. Isn’t it? Isn’t it?</p>
<p>It’s not. Just about everything Internet in this country is driven through a web browser. It completely blew my mind. Now it was ASP.NET. And JavaScript. And AJAX. Web Services. XML-RPC. DCOM. SOAP. WCF. JSON. The acronyms go marching on. And on. Another steep learning curve. Nose to the grindstone. Shoulder to the wheel. Fast-forward a year, and I’m pretty sure I’ve got a really good handle on it. I’m teaching other people how to do it properly. But things are still accelerating. And I don’t have time to learn it all. While I was grokking ASP.NET WebForms, someone resurrected ASP.NET MVC. And SignalR. And frameworks! I blinked and there were IoC frameworks and Dependency Injection and Entity Framework and NHibernate and Unit Testing Frameworks. All with their own idiosyncrasies and methodologies. And Windows Presentation Foundation. And Windows Communication Foundation. And Workflow Foundation. Identity Foundation. Fifty fucking flavours of ‘Foundation’, and I can’t learn them all!</p>
<p>And that’s only just part of the Microsoft technology stack! What about the others?</p>
<p>I. Just. Can’t.</p>
<p>But wait! There’s more! Some guy called Steve invented the iPhone, and a company called Google invented Android (and now there’s fifty-fucking-one flavours of Java because you have to be able to use it to program Android apps too). Now there’s Objective-C, and Xamarin and Windows Phone and XML and XAML and AXML. And the devices are all just different enough to give UI/UIX developers headaches because of all those funky screen sizes. Thank goodness HTML5 and CSS3 are so quick and easy to learn, right? No time taken to learn those. Why do you think we have UI/UIX geeks? Because UI stuff is <em>hard</em>, and it’s subjective too, so there’s always someone who’ll hate it.</p>
<p>And God help you if you built anything in Silverlight because that shit won’t run <em>anywhere</em> except on a PC.</p>
<p>Oh, and while you were sleeping, every man and his dog made their own JavaScript framework. Because JavaScript. And a bad case of ‘mine is bigger than yours’. Which one do you pick? Now there’s five <strong>hundred</strong> fucking flavours of JavaScript framework, and which is the one you should be learning right now? Angular. I think. And Node. Hang on. Or maybe it’s Knockout. And maybe ReactJS. Or Durandal? I’m not sure. Probably a good idea to get some Ember and Backbone in there too, if you want to keep your skills marketable. And what’s WinJS? Is that a JavaScript framework? Best you learn some before you find out the hard way. And don’t forget to learn TypeScript and/or CoffeeScript, while you’re at it. Just in case. Oh, and are you testing your stuff? What with? Jasmine? Ask me again this time next year and I’ll have a different answer. And a different one 6 months after that. And you know about ES6 and Google’s Dart, right?</p>
<p>And what are you going to build and deploy them with? Gulp? Grunt? Fart? Sneeze? Who knows?! I don’t.</p>
<p>So how do you solve this? As good little software development geeks, our job is to optimise for efficiency and reuse. Lean, mean, agile, and thinking 20 years ahead (because in some government departments, that’s how long it’ll take to rotate out an existing solution). We have to build solutions that are fast, cheap and reliable, on a shoestring budget, with very little time and at least one manager who doesn’t have a cooking clue what it is that we do. Somehow we’ve got to remain relevant, and if we want to succeed and still be The Expert, we need to be more relevant than that other guy.</p>
<p>And that’s the nature of the game we’re in. If you’re more than about a year behind in terms of the technologies, then you’re becoming obsolete. Your problem is actually trying to predict which is the Next Big Thing in software development. And depending on which echo chamber you’re living in, or which Kool Aid you’ve drunk, that’s just about impossible to do.</p>
<p>Doctors already solved this though, a long time ago. If all you ever do is try to become an Expert In Everything, and wonder why you’re always just running in place, then you’re doing it wrong. Essentially you’re just a general practitioner. A GP. They work bloody long hours too. But if you want to be The Expert, then you need to become a specialist. Like a neurosurgeon, or an anaesthetist. But they’re constantly having to learn about new techniques, new drugs, new diseases.</p>
<p>It never stops.</p>
<p>Basically you need to become The Mobile guy, or The Cloud guy, or the Big Data guy. And then the best you can hope to do is become familiar enough within that field to know what’s going on, what’s new, and sometimes (most importantly), what not to use. Everyone around you is in the same boat. If you’re lucky, the people in your team are halfway competent, and will complement your skill-set.</p>
<p>And then finally you get your head around that you, yourself, will eventually become obsolete.</p>
<p>Like it or not, there’s always someone younger, smarter, and faster than you. And they all want to be The Expert too. Some are convinced they already are.</p>
<p>Lucky you though. You have experience too. So you’ll move into management.</p>