Android Design Patternshttps://www.androiddesignpatterns.com/Alex Lockwood2018-12-12T21:27:52+00:00How to use Android Studio's SVG-to-VectorDrawable converter from the command linehttps://www.androiddesignpatterns.com/2018/11/android-studio-svg-to-vector-cli.html2018-11-13T00:00:00+00:002018-11-13T00:00:00+00:00<!--morestart-->
<!--morestart-->
<p>Since the very beginning, one of the most annoying aspects of using <code class="highlighter-rouge">VectorDrawable</code>s on Android has been the lack of a reliable SVG converter. Google has recently made huge strides towards improving Android Studio’s SVG-to-<code class="highlighter-rouge">VectorDrawable</code> tool in order to improve this experience.</p>
<p>However, there has always been one major issue for me: the tool doesn’t support batch conversion of SVGs and can’t be invoked from the command line. Working at Lyft, it’s not uncommon for me to need to convert hundreds of SVGs into <code class="highlighter-rouge">VectorDrawable</code>s at a time. Having to go through Android Studio’s GUI in order to convert each SVG one-by-one is simply not realistic.</p>
<!--more-->
<p>Well, this weekend my good buddy <a href="https://twitter.com/crafty">Nick Butcher</a> taught me how to build the tool as a binary that can be executed from the command line, and I’m so excited about it that I had to share it with the world! :)</p>
<h2 id="where-can-i-download-it">Where can I download it?</h2>
<p>I anticipate many won’t want to go through the trouble of building these binaries from scratch, so I built them myself and hosted them on <a href="https://j.mp/svg-to-vector-google-drive">Google Drive here</a>.</p>
<h2 id="how-do-i-run-it">How do I run it?</h2>
<p>The following command will convert all SVGs in a directory called <code class="highlighter-rouge">svgs/</code>, convert them all into <code class="highlighter-rouge">VectorDrawable</code>s, and write them to a directory called <code class="highlighter-rouge">vectors/</code>. Note that both directories must exist beforehand.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./vd-tool -c -in svgs/ -out vectors/
</code></pre></div></div>
<h2 id="how-do-i-build-it">How do I build it?</h2>
<p>In case you want to build these yourself, here’s how I did it.</p>
<p>First, follow the <a href="https://source.android.com/source/downloading.html">Downloading the Source</a> guide to install and set up the <code class="highlighter-rouge">repo</code> tool, but instead of running the listed <code class="highlighter-rouge">repo</code> commands to initialize the repository, run the folowing:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>repo init -u https://android.googlesource.com/platform/manifest -b <most-recent-android-studio-branch>
</code></pre></div></div>
<p>At the time of this writing, the most recent Android Studio branch was <code class="highlighter-rouge">studio-3.2.1</code>. This will obviously change over time as newer versions of Android Studio are released.</p>
<p>Now that your repository is set to pull only what you need for building and running the tool, download the code using the following command (you might want to grab a coffee or something too, as this command might take a while to complete):</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>repo sync -j8 -c
</code></pre></div></div>
<p>Finally, execute the following to build the binaries:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ./tools/base
../gradlew publishLocal
</code></pre></div></div>
<p>Once it completes, you should find the binaries in a <code class="highlighter-rouge"><root>/out/build/base/vector-drawable-tool/build/distributions/vd-tool.zip</code> file. Unzip it and it will extract a <code class="highlighter-rouge">/bin</code> directory that contains binaries compatible with Mac OS X, Linux, and Windows.</p>
<h2 id="how-do-i-report-bugs">How do I report bugs?</h2>
<p>Now for the most important part of this blog post…</p>
<p>Please, please, <strong>please</strong> file bugs if you discover SVGs that don’t convert properly! File the bugs in the <a href="https://issuetracker.google.com/issues?q=componentid:192708%20status:open">Android Studio public issue tracker</a>. Here’s <a href="https://issuetracker.google.com/issues/119372339">an example bug</a> I recently reported if you need a template to go by.</p>
<p>My goal is to make the Android Studio converter the most reliable SVG-to-<code class="highlighter-rouge">VectorDrawable</code> converter out there. So if and when you file a bug, feel free to <a href="https://twitter.com/alexjlockwood">hit me up on Twitter</a> with a link to the report and I’ll do my best to ensure the bug is fixed as quickly as possible!</p>
<p>Happy converting!</p>
Introducing Kyrie - An Alternative to Animated Vector Drawableshttps://www.androiddesignpatterns.com/2018/03/introducing-kyrie-animated-vector-drawables.html2018-03-06T00:00:00+00:002018-03-06T00:00:00+00:00<!--morestart-->
<!--morestart-->
<p>Today I am open sourcing the first alpha release of an animation library I’ve been writing named <a href="https://github.com/alexjlockwood/kyrie">Kyrie</a>. Think of it as a superset of Android’s <code class="highlighter-rouge">VectorDrawable</code> and <code class="highlighter-rouge">AnimatedVectorDrawable</code> classes: it can do everything they can do and more.</p>
<!--more-->
<h3 id="motivation">Motivation</h3>
<p>Let me start by explaining why I began writing this library in the first place.</p>
<p>If you read <a href="/2016/11/introduction-to-icon-animation-techniques.html">my blog post on icon animations</a>, you know that <code class="highlighter-rouge">VectorDrawable</code>s are great because they provide density independence—they can be scaled arbitrarily on any device without loss of quality. <code class="highlighter-rouge">AnimatedVectorDrawable</code>s make them even more awesome, allowing us to animate specific properties of a <code class="highlighter-rouge">VectorDrawable</code> in a variety of ways.</p>
<p>However, these two classes also have several limitations:</p>
<ul>
<li>They can’t be dynamically created at runtime (they must be inflated from a drawable resource).</li>
<li>They can’t be paused, resumed, or seeked.</li>
<li>They only support a small subset of features that SVGs provide on the web.</li>
</ul>
<p>I started writing <a href="https://github.com/alexjlockwood/kyrie">Kyrie</a> in an attempt to address these problems.</p>
<h3 id="examples">Examples</h3>
<p>Let’s walk through a few examples from <a href="https://github.com/alexjlockwood/kyrie/tree/master/sample/src/main/java/com/example/kyrie">the sample app</a> that accompanies the library.</p>
<p>The first snippet of code below shows how we can use Kyrie to transform an existing <code class="highlighter-rouge">AnimatedVectorDrawable</code> resource into a <a href="https://alexjlockwood.github.io/kyrie/com/github/alexjlockwood/kyrie/KyrieDrawable.html"><code class="highlighter-rouge">KyrieDrawable</code></a> that can be scrubbed with a <code class="highlighter-rouge">SeekBar</code>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">KyrieDrawable</span> <span class="n">drawable</span> <span class="o">=</span> <span class="n">KyrieDrawable</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">drawable</span><span class="o">.</span><span class="na">avd_heartbreak</span><span class="o">);</span>
<span class="n">seekBar</span><span class="o">.</span><span class="na">setOnSeekBarChangeListener</span><span class="o">(</span><span class="k">new</span> <span class="n">SeekBar</span><span class="o">.</span><span class="na">OnSeekBarChangeListener</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onProgressChanged</span><span class="o">(</span><span class="n">SeekBar</span> <span class="n">seekBar</span><span class="o">,</span> <span class="kt">int</span> <span class="n">progress</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">fromUser</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">long</span> <span class="n">totalDuration</span> <span class="o">=</span> <span class="n">drawable</span><span class="o">.</span><span class="na">getTotalDuration</span><span class="o">();</span>
<span class="n">drawable</span><span class="o">.</span><span class="na">setCurrentPlayTime</span><span class="o">((</span><span class="kt">long</span><span class="o">)</span> <span class="o">(</span><span class="n">progress</span> <span class="o">/</span> <span class="mi">100</span><span class="n">f</span> <span class="o">*</span> <span class="n">totalDuration</span><span class="o">));</span>
<span class="o">}</span>
<span class="cm">/* ... */</span>
<span class="o">});</span>
</code></pre></div></div>
<p>The video in <strong>Figure 1</strong> shows the resulting animation. We can pause/resume the animation by calling <a href="https://alexjlockwood.github.io/kyrie/com/github/alexjlockwood/kyrie/KyrieDrawable.html#pause--"><code class="highlighter-rouge">pause()</code></a> and <a href="https://alexjlockwood.github.io/kyrie/com/github/alexjlockwood/kyrie/KyrieDrawable.html#resume--"><code class="highlighter-rouge">resume()</code></a> respectively, and can also listen to animation events using a <a href="https://alexjlockwood.github.io/kyrie/com/github/alexjlockwood/kyrie/KyrieDrawable.Listener.html"><code class="highlighter-rouge">KyrieDrawable.Listener</code></a>. In the future, I plan to add a couple more features as well, such as the ability to customize the playback speed and/or play the animation in reverse.</p>
<div class="figure-container">
<div class="figure-parent">
<video class="figure-video figure-element" poster="/assets/videos/posts/2018/03/06/poster-introducing-kyrie-heartbreak.jpg" preload="auto" onclick="resumeVideo(this)">
<source src="/assets/videos/posts/2018/03/06/introducing-kyrie-heartbreak.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2018/03/06/introducing-kyrie-heartbreak.webm" type="video/webm" />
</video>
</div>
</div>
<div class="caption-container">
<p class="caption-element">
<strong>Figure 1</strong> - Creating a seekable animation from an existing <code>AnimatedVectorDrawable</code> resource (<a href="https://github.com/alexjlockwood/kyrie/blob/master/sample/src/main/java/com/example/kyrie/HeartbreakFragment.java">source code</a>). Click to play.</p>
</div>
<p>We can also create <code class="highlighter-rouge">KyrieDrawable</code>s dynamically at runtime using the builder pattern. <code class="highlighter-rouge">KyrieDrawable</code>s are similar to SVGs and <code class="highlighter-rouge">VectorDrawable</code>s in that they are tree-like structures built of <a href="https://alexjlockwood.github.io/kyrie/com/github/alexjlockwood/kyrie/Node.html"><code class="highlighter-rouge">Node</code></a>s. As we build the tree, we can optionally assign <a href="https://alexjlockwood.github.io/kyrie/com/github/alexjlockwood/kyrie/Animation.html"><code class="highlighter-rouge">Animation</code></a>s to the properties of each <code class="highlighter-rouge">Node</code> to create a more elaborate animation. The code below shows how we can create a path morphing animation this way:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Fill colors.</span>
<span class="kt">int</span> <span class="n">hippoFillColor</span> <span class="o">=</span> <span class="n">ContextCompat</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">hippo</span><span class="o">);</span>
<span class="kt">int</span> <span class="n">elephantFillColor</span> <span class="o">=</span> <span class="n">ContextCompat</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">elephant</span><span class="o">);</span>
<span class="kt">int</span> <span class="n">buffaloFillColor</span> <span class="o">=</span> <span class="n">ContextCompat</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">buffalo</span><span class="o">);</span>
<span class="c1">// SVG path data objects.</span>
<span class="n">PathData</span> <span class="n">hippoPathData</span> <span class="o">=</span> <span class="n">PathData</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">getString</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">string</span><span class="o">.</span><span class="na">hippo</span><span class="o">));</span>
<span class="n">PathData</span> <span class="n">elephantPathData</span> <span class="o">=</span> <span class="n">PathData</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">getString</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">string</span><span class="o">.</span><span class="na">elephant</span><span class="o">));</span>
<span class="n">PathData</span> <span class="n">buffaloPathData</span> <span class="o">=</span> <span class="n">PathData</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">getString</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">string</span><span class="o">.</span><span class="na">buffalo</span><span class="o">));</span>
<span class="n">KyrieDrawable</span> <span class="n">drawable</span> <span class="o">=</span>
<span class="n">KyrieDrawable</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
<span class="o">.</span><span class="na">viewport</span><span class="o">(</span><span class="mi">409</span><span class="o">,</span> <span class="mi">280</span><span class="o">)</span>
<span class="o">.</span><span class="na">child</span><span class="o">(</span>
<span class="n">PathNode</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
<span class="o">.</span><span class="na">strokeColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">)</span>
<span class="o">.</span><span class="na">strokeWidth</span><span class="o">(</span><span class="mi">1</span><span class="n">f</span><span class="o">)</span>
<span class="o">.</span><span class="na">fillColor</span><span class="o">(</span>
<span class="n">Animation</span><span class="o">.</span><span class="na">ofArgb</span><span class="o">(</span><span class="n">hippoFillColor</span><span class="o">,</span> <span class="n">elephantFillColor</span><span class="o">).</span><span class="na">duration</span><span class="o">(</span><span class="mi">300</span><span class="o">),</span>
<span class="n">Animation</span><span class="o">.</span><span class="na">ofArgb</span><span class="o">(</span><span class="n">buffaloFillColor</span><span class="o">).</span><span class="na">startDelay</span><span class="o">(</span><span class="mi">600</span><span class="o">).</span><span class="na">duration</span><span class="o">(</span><span class="mi">300</span><span class="o">),</span>
<span class="n">Animation</span><span class="o">.</span><span class="na">ofArgb</span><span class="o">(</span><span class="n">hippoFillColor</span><span class="o">).</span><span class="na">startDelay</span><span class="o">(</span><span class="mi">1200</span><span class="o">).</span><span class="na">duration</span><span class="o">(</span><span class="mi">300</span><span class="o">))</span>
<span class="o">.</span><span class="na">pathData</span><span class="o">(</span>
<span class="n">Animation</span><span class="o">.</span><span class="na">ofPathMorph</span><span class="o">(</span>
<span class="n">Keyframe</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">hippoPathData</span><span class="o">),</span>
<span class="n">Keyframe</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mf">0.2f</span><span class="o">,</span> <span class="n">elephantPathData</span><span class="o">),</span>
<span class="n">Keyframe</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mf">0.4f</span><span class="o">,</span> <span class="n">elephantPathData</span><span class="o">),</span>
<span class="n">Keyframe</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mf">0.6f</span><span class="o">,</span> <span class="n">buffaloPathData</span><span class="o">),</span>
<span class="n">Keyframe</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mf">0.8f</span><span class="o">,</span> <span class="n">buffaloPathData</span><span class="o">),</span>
<span class="n">Keyframe</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">hippoPathData</span><span class="o">))</span>
<span class="o">.</span><span class="na">duration</span><span class="o">(</span><span class="mi">1500</span><span class="o">)))</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
</code></pre></div></div>
<p><strong>Figure 2</strong> shows the resulting animation. Note that <code class="highlighter-rouge">Animation</code>s can also be constructed using <a href="https://alexjlockwood.github.io/kyrie/com/github/alexjlockwood/kyrie/Keyframe.html"><code class="highlighter-rouge">Keyframe</code></a>s, just as we would do so with a <a href="https://developer.android.com/reference/android/animation/PropertyValuesHolder.html#ofKeyframe(java.lang.String,%20android.animation.Keyframe...)"><code class="highlighter-rouge">PropertyValuesHolder</code></a>.</p>
<div class="figure-container">
<div class="figure-parent">
<video class="figure-video figure-element" poster="/assets/videos/posts/2018/03/06/poster-introducing-kyrie-animals.jpg" preload="auto" onclick="resumeVideo(this)">
<source src="/assets/videos/posts/2018/03/06/introducing-kyrie-animals.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2018/03/06/introducing-kyrie-animals.webm" type="video/webm" />
</video>
</div>
</div>
<div class="caption-container">
<p class="caption-element">
<strong>Figure 2</strong> - Creating a path morphing animation using keyframes (<a href="https://github.com/alexjlockwood/kyrie/blob/master/sample/src/main/java/com/example/kyrie/PathMorphFragment.java">source code</a>). Click to play.</p>
</div>
<p>Kyrie also supports animating along a path using the <a href="https://alexjlockwood.github.io/kyrie/com/github/alexjlockwood/kyrie/Animation.html#ofPathMotion-android.graphics.Path-"><code class="highlighter-rouge">Animation#ofPathMotion</code></a> method. Say, for example, we wanted to recreate the polygon animations from Nick Butcher’s <a href="https://medium.com/google-developers/playing-with-paths-3fbc679a6f77">Playing with Paths</a> blog post (the <a href="https://github.com/alexjlockwood/kyrie/blob/master/sample/src/main/java/com/example/kyrie/PolygonsFragment.java">full source code</a> is available in the sample app):</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">KyrieDrawable</span><span class="o">.</span><span class="na">Builder</span> <span class="n">builder</span> <span class="o">=</span> <span class="n">KyrieDrawable</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">viewport</span><span class="o">(</span><span class="mi">1080</span><span class="o">,</span> <span class="mi">1080</span><span class="o">);</span>
<span class="c1">// Draw each polygon using a PathNode with a custom stroke color.</span>
<span class="k">for</span> <span class="o">(</span><span class="n">Polygon</span> <span class="n">polygon</span> <span class="o">:</span> <span class="n">polygons</span><span class="o">)</span> <span class="o">{</span>
<span class="n">builder</span><span class="o">.</span><span class="na">child</span><span class="o">(</span>
<span class="n">PathNode</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
<span class="o">.</span><span class="na">pathData</span><span class="o">(</span><span class="n">PathData</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">polygon</span><span class="o">.</span><span class="na">pathData</span><span class="o">))</span>
<span class="o">.</span><span class="na">strokeWidth</span><span class="o">(</span><span class="mi">4</span><span class="n">f</span><span class="o">)</span>
<span class="o">.</span><span class="na">strokeColor</span><span class="o">(</span><span class="n">polygon</span><span class="o">.</span><span class="na">color</span><span class="o">));</span>
<span class="o">}</span>
<span class="c1">// Animate a black dot along each polygon's perimeter.</span>
<span class="k">for</span> <span class="o">(</span><span class="n">Polygon</span> <span class="n">polygon</span> <span class="o">:</span> <span class="n">polygons</span><span class="o">)</span> <span class="o">{</span>
<span class="n">PathData</span> <span class="n">pathData</span> <span class="o">=</span>
<span class="n">PathData</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">TextUtils</span><span class="o">.</span><span class="na">join</span><span class="o">(</span><span class="s">" "</span><span class="o">,</span> <span class="n">Collections</span><span class="o">.</span><span class="na">nCopies</span><span class="o">(</span><span class="n">polygon</span><span class="o">.</span><span class="na">laps</span><span class="o">,</span> <span class="n">polygon</span><span class="o">.</span><span class="na">pathData</span><span class="o">)));</span>
<span class="n">Animation</span><span class="o"><</span><span class="n">PointF</span><span class="o">,</span> <span class="n">PointF</span><span class="o">></span> <span class="n">pathMotion</span> <span class="o">=</span>
<span class="n">Animation</span><span class="o">.</span><span class="na">ofPathMotion</span><span class="o">(</span><span class="n">PathData</span><span class="o">.</span><span class="na">toPath</span><span class="o">(</span><span class="n">pathData</span><span class="o">)).</span><span class="na">duration</span><span class="o">(</span><span class="mi">7500</span><span class="o">);</span>
<span class="n">builder</span><span class="o">.</span><span class="na">child</span><span class="o">(</span>
<span class="n">CircleNode</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
<span class="o">.</span><span class="na">fillColor</span><span class="o">(</span><span class="n">Color</span><span class="o">.</span><span class="na">BLACK</span><span class="o">)</span>
<span class="o">.</span><span class="na">radius</span><span class="o">(</span><span class="mi">8</span><span class="o">)</span>
<span class="o">.</span><span class="na">centerX</span><span class="o">(</span><span class="n">pathMotion</span><span class="o">.</span><span class="na">transform</span><span class="o">(</span><span class="n">p</span> <span class="o">-></span> <span class="n">p</span><span class="o">.</span><span class="na">x</span><span class="o">))</span>
<span class="o">.</span><span class="na">centerY</span><span class="o">(</span><span class="n">pathMotion</span><span class="o">.</span><span class="na">transform</span><span class="o">(</span><span class="n">p</span> <span class="o">-></span> <span class="n">p</span><span class="o">.</span><span class="na">y</span><span class="o">)));</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The left half of <strong>Figure 3</strong> shows the resulting animation. Note that <code class="highlighter-rouge">Animation#ofPathMotion</code> returns an <code class="highlighter-rouge">Animation</code> that computes <code class="highlighter-rouge">PointF</code> objects, where each point represents a location along the specified path as the animation progresses. In order to animate each black circle’s location along this path, we use the <a href="https://alexjlockwood.github.io/kyrie/com/github/alexjlockwood/kyrie/Animation.html#transform-com.github.alexjlockwood.kyrie.Animation.ValueTransformer-"><code class="highlighter-rouge">Animation#transform</code></a> method to transform the points into streams of x/y coordinates that can be consumed by the <code class="highlighter-rouge">CircleNode</code>’s <code class="highlighter-rouge">centerX</code> and <code class="highlighter-rouge">centerY</code> properties.</p>
<div class="figure-container">
<div class="figure-parent">
<video class="figure-video figure-element" poster="/assets/videos/posts/2018/03/06/poster-introducing-kyrie-polygons.jpg" preload="auto" onclick="resumeVideo(this)">
<source src="/assets/videos/posts/2018/03/06/introducing-kyrie-polygons.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2018/03/06/introducing-kyrie-polygons.webm" type="video/webm" />
</video>
</div>
</div>
<div class="caption-container">
<p class="caption-element">
<strong>Figure 3</strong> - Recreating the polygon animations from Nick Butcher's <a href="https://medium.com/google-developers/playing-with-paths-3fbc679a6f77">Playing with Paths</a> blog post (<a href="https://github.com/alexjlockwood/kyrie/blob/master/sample/src/main/java/com/example/kyrie/PolygonsFragment.java">source code</a>). Click to play.</p>
</div>
<h3 id="future-work">Future work</h3>
<p>I have a lot of ideas on how to further improve this library, but right now I am interested in what you think. Make sure you file any <a href="https://github.com/alexjlockwood/kyrie/issues">feature requests</a> you might have on GitHub! And like I said, the library is still in alpha, so make sure you report bugs too. :)</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/alexjlockwood/kyrie">GitHub</a></li>
<li><a href="https://alexjlockwood.github.io/kyrie">Documentation</a></li>
</ul>
Experimenting with Nested Scrollinghttps://www.androiddesignpatterns.com/2018/01/experimenting-with-nested-scrolling.html2018-01-24T00:00:00+00:002018-01-24T00:00:00+00:00<!--morestart-->
<!--morestart-->
<p>One of the coolest projects I worked on during my 3 years at Google was Google Expeditions, a virtual reality app that allows teachers to lead students on immersive virtual field trips around the world. I especially enjoyed working on the app’s field trip selector screen, which renders a <code class="highlighter-rouge">SurfaceView</code> behind a beautifully designed card-based layout that allows the user to quickly switch between different VR experiences.</p>
<!--more-->
<p>It’s been awhile since I’ve written Android code (I’ve spent a majority of the past year building Android developer tools like <a href="https://github.com/alexjlockwood/ShapeShifter">Shape Shifter</a> and <a href="https://github.com/alexjlockwood/avocado"><code class="highlighter-rouge">avocado</code></a>), so the other day I challenged myself to rewrite parts of the screen as an exercise. <strong>Figure 1</strong> shows a side-by-side comparison of Google Expeditions’ field trip selector screen and the resulting sample app I wrote (available on <a href="https://github.com/alexjlockwood/adp-nested-scrolling">GitHub</a> and <a href="https://play.google.com/store/apps/details?id=alexjlockwood.nestedscrolling">Google Play</a>).</p>
<!-- Figure 1 -->
<div class="figure-container">
<div class="figure-parent">
<video class="figure-video figure-element" poster="/assets/videos/posts/2018/01/24/poster-expeditions-sample.jpg" preload="auto" onclick="resumeVideo(this)">
<source src="/assets/videos/posts/2018/01/24/expeditions-sample-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2018/01/24/expeditions-sample-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2018/01/24/expeditions-sample-opt.ogv" type="video/ogg" />
</video>
</div>
</div>
<div class="caption-container">
<p class="caption-element">
<strong>Figure 1</strong> - A side-by-side comparison of the Google Expeditions' Android app vs. the sample app I wrote for this blog post.
</p>
</div>
<p>As I was working through the code, I was reminded of some of the challenges I faced with Android’s nested scrolling APIs when I first wrote the screen a couple of years ago. Introduced in API 21, the nested scrolling APIs make it possible for a scrollable parent view to contain scrollable children views, enabling us to create the scrolling gestures that Material Design formalizes on its <a href="https://material.io/design/components/app-bars-top.html#behavior">scrolling techniques</a> patterns page. <strong>Figure 2</strong> shows a common use case of these APIs involving a parent <code class="highlighter-rouge">CoordinatorLayout</code> and a child <code class="highlighter-rouge">NestedScrollView</code>. Without nested scrolling, the <code class="highlighter-rouge">NestedScrollView</code> scrolls independently of its surroundings. Once enabled, however, the <code class="highlighter-rouge">CoordinatorLayout</code> and <code class="highlighter-rouge">NestedScrollView</code> take turns intercepting and consuming the scroll, creating a ‘collapsing toolbar’ effect that looks more natural.</p>
<!-- Figure 2 -->
<div class="figure-container">
<div class="figure-parent">
<video class="figure-video figure-element" poster="/assets/videos/posts/2018/01/24/poster-cheesesquare.jpg" preload="auto" onclick="resumeVideo(this)">
<source src="/assets/videos/posts/2018/01/24/cheesesquare-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2018/01/24/cheesesquare-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2018/01/24/cheesesquare-opt.ogv" type="video/ogg" />
</video>
</div>
</div>
<div class="caption-container">
<p class="caption-element">
<strong>Figure 2</strong> - A side-by-side comparison of Chris Banes' <a href="https://github.com/chrisbanes/cheesesquare">Cheese Square</a> app with nested scrolling disabled vs. enabled.
</p>
</div>
<p>How exactly do the nested scrolling APIs work? For starters, you need a parent view that implements <a href="https://developer.android.com/reference/android/support/v4/view/NestedScrollingParent.html"><code class="highlighter-rouge">NestedScrollingParent</code></a> and a child view that implements <a href="https://developer.android.com/reference/android/support/v4/view/NestedScrollingChild.html"><code class="highlighter-rouge">NestedScrollingChild</code></a>. In <strong>Figure 3</strong> below, the <code class="highlighter-rouge">NestedScrollView</code> (<code class="highlighter-rouge">NSV</code>) is the parent and the <code class="highlighter-rouge">RecyclerView</code> (<code class="highlighter-rouge">RV</code>) is the child:</p>
<!-- Figure 3 -->
<div class="figure-container">
<img class="figure-image" src="/assets/images/posts/2018/01/24/sample-app-layouts.jpg" alt="Sample app layouts" title="Sample app layouts" />
</div>
<div class="caption-container">
<p class="caption-element">
<strong>Figure 3</strong> - The sample app consists of a parent <code>NestedScrollView</code> and a child <code>RecyclerView</code>.
</p>
</div>
<p>Let’s say the user attempts to scroll the <code class="highlighter-rouge">RV</code> above. Without nested scrolling, the <code class="highlighter-rouge">RV</code> will immediately consume the scroll event, resulting in the undesirable behavior we saw in <strong>Figure 2</strong>. What we <em>really</em> want is to create the illusion that the two views are scrolling together as a single unit. More specifically:<sup><a href="#footnote1" id="ref1">1</a></sup></p>
<ul>
<li>If the <code class="highlighter-rouge">RV</code> is at the top of its content, scrolling the <code class="highlighter-rouge">RV</code> up should cause the <code class="highlighter-rouge">NSV</code> to scroll up.</li>
<li>If the <code class="highlighter-rouge">NSV</code> is <em>not</em> at the bottom of its content, scrolling the <code class="highlighter-rouge">RV</code> down should cause the <code class="highlighter-rouge">NSV</code> to scroll down.</li>
</ul>
<p>As you might expect, the nested scrolling APIs provide a way for the <code class="highlighter-rouge">NSV</code> and <code class="highlighter-rouge">RV</code> to communicate with each other throughout the duration of the scroll, so that each view can confidently determine who should consume each scroll event. This becomes clear when you consider the sequence of events that takes place when the user drags their finger on top of the <code class="highlighter-rouge">RV</code>:</p>
<ol>
<li>The <code class="highlighter-rouge">RV</code>’s <code class="highlighter-rouge">onTouchEvent(ACTION_MOVE)</code> method is called.</li>
<li>The <code class="highlighter-rouge">RV</code> calls its own <code class="highlighter-rouge">dispatchNestedPreScroll()</code> method, which notifies the <code class="highlighter-rouge">NSV</code> that it is about to consume a portion of the scroll.</li>
<li>The <code class="highlighter-rouge">NSV</code>’s <code class="highlighter-rouge">onNestedPreScroll()</code> method is called, giving the <code class="highlighter-rouge">NSV</code> an opportunity to react to the scroll event before the <code class="highlighter-rouge">RV</code> consumes it.</li>
<li>The <code class="highlighter-rouge">RV</code> consumes the remainder of the scroll (or does nothing if the <code class="highlighter-rouge">NSV</code> consumed the entire event).</li>
<li>The <code class="highlighter-rouge">RV</code> calls its own <code class="highlighter-rouge">dispatchNestedScroll()</code> method, which notifies the <code class="highlighter-rouge">NSV</code> that it has consumed a portion of the scroll.</li>
<li>The <code class="highlighter-rouge">NSV</code>’s <code class="highlighter-rouge">onNestedScroll()</code> method is called, giving the <code class="highlighter-rouge">NSV</code> an opportunity to consume any remaining scroll pixels that have still not been consumed.</li>
<li>The <code class="highlighter-rouge">RV</code> returns <code class="highlighter-rouge">true</code> from the current call to <code class="highlighter-rouge">onTouchEvent(ACTION_MOVE)</code>, consuming the touch event.<sup><a href="#footnote2" id="ref2">2</a></sup></li>
</ol>
<p>Unfortunately, simply using a <code class="highlighter-rouge">NSV</code> and <code class="highlighter-rouge">RV</code> was not enough to get the scrolling behavior I wanted. <strong>Figure 4</strong> shows the two problematic bugs that I needed to fix. The cause of both problems is that the <code class="highlighter-rouge">RV</code> is consuming scroll and fling events when it shouldn’t be. On the left, the <code class="highlighter-rouge">RV</code> should <em>not</em> begin scrolling until the card has reached the top of the screen. On the right, flinging the <code class="highlighter-rouge">RV</code> downwards should collapse the card in a single smooth motion.</p>
<!-- Figure 4 -->
<div class="figure-container">
<div class="figure-parent">
<video class="figure-video figure-element" poster="/assets/videos/posts/2018/01/24/poster-nested-scrolling-bugs1.jpg" preload="auto" onclick="resumeVideo(this)">
<source src="/assets/videos/posts/2018/01/24/nested-scrolling-bugs1-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2018/01/24/nested-scrolling-bugs1-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2018/01/24/nested-scrolling-bugs1-opt.ogv" type="video/ogg" />
</video>
</div>
</div>
<div class="caption-container">
<p class="caption-element">
<strong>Figure 4</strong> - On the left, the <code>RV</code> should <i>not</i> begin scrolling until the card has reached the top of the screen. On the right, flinging the <code>RV</code> downwards should collapse the card in a single smooth motion.
</p>
</div>
<p>Fixing these two problems is relatively straightforward now that we understand how the nested scrolling APIs work. All we need to do is create a <code class="highlighter-rouge">CustomNestedScrollView</code> class and customize its scrolling behavior by overriding the <code class="highlighter-rouge">onNestedPreScroll()</code> and <code class="highlighter-rouge">onNestedPreFling()</code> methods:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* A NestedScrollView with our custom nested scrolling behavior.
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomNestedScrollView</span> <span class="kd">extends</span> <span class="n">NestedScrollView</span> <span class="o">{</span>
<span class="c1">// The NestedScrollView should steal the scroll/fling events away from</span>
<span class="c1">// the RecyclerView if: (1) the user is dragging their finger down and</span>
<span class="c1">// the RecyclerView is scrolled to the top of its content, or (2) the</span>
<span class="c1">// user is dragging their finger up and the NestedScrollView is not</span>
<span class="c1">// scrolled to the bottom of its content.</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onNestedPreScroll</span><span class="o">(</span><span class="n">View</span> <span class="n">target</span><span class="o">,</span> <span class="kt">int</span> <span class="n">dx</span><span class="o">,</span> <span class="kt">int</span> <span class="n">dy</span><span class="o">,</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">consumed</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">RecyclerView</span> <span class="n">rv</span> <span class="o">=</span> <span class="o">(</span><span class="n">RecyclerView</span><span class="o">)</span> <span class="n">target</span><span class="o">;</span>
<span class="k">if</span> <span class="o">((</span><span class="n">dy</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">&&</span> <span class="n">isRvScrolledToTop</span><span class="o">(</span><span class="n">rv</span><span class="o">))</span> <span class="o">||</span> <span class="o">(</span><span class="n">dy</span> <span class="o">></span> <span class="mi">0</span> <span class="o">&&</span> <span class="o">!</span><span class="n">isNsvScrolledToBottom</span><span class="o">(</span><span class="k">this</span><span class="o">)))</span> <span class="o">{</span>
<span class="c1">// Scroll the NestedScrollView's content and record the number of pixels consumed</span>
<span class="c1">// (so that the RecyclerView will know not to perform the scroll as well).</span>
<span class="n">scrollBy</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">dy</span><span class="o">);</span>
<span class="n">consumed</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="n">dy</span><span class="o">;</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onNestedPreScroll</span><span class="o">(</span><span class="n">target</span><span class="o">,</span> <span class="n">dx</span><span class="o">,</span> <span class="n">dy</span><span class="o">,</span> <span class="n">consumed</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">onNestedPreFling</span><span class="o">(</span><span class="n">View</span> <span class="n">target</span><span class="o">,</span> <span class="kt">float</span> <span class="n">velX</span><span class="o">,</span> <span class="kt">float</span> <span class="n">velY</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">RecyclerView</span> <span class="n">rv</span> <span class="o">=</span> <span class="o">(</span><span class="n">RecyclerView</span><span class="o">)</span> <span class="n">target</span><span class="o">;</span>
<span class="k">if</span> <span class="o">((</span><span class="n">velY</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">&&</span> <span class="n">isRvScrolledToTop</span><span class="o">(</span><span class="n">rv</span><span class="o">))</span> <span class="o">||</span> <span class="o">(</span><span class="n">velY</span> <span class="o">></span> <span class="mi">0</span> <span class="o">&&</span> <span class="o">!</span><span class="n">isNsvScrolledToBottom</span><span class="o">(</span><span class="k">this</span><span class="o">)))</span> <span class="o">{</span>
<span class="c1">// Fling the NestedScrollView's content and return true (so that the RecyclerView</span>
<span class="c1">// will know not to perform the fling as well).</span>
<span class="n">fling</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span> <span class="n">velY</span><span class="o">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kd">super</span><span class="o">.</span><span class="na">onNestedPreFling</span><span class="o">(</span><span class="n">target</span><span class="o">,</span> <span class="n">velX</span><span class="o">,</span> <span class="n">velY</span><span class="o">);</span>
<span class="o">}</span>
<span class="cm">/**
* Returns true iff the NestedScrollView is scrolled to the bottom of its
* content (i.e. if the card's inner RecyclerView is completely visible).
*/</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isNsvScrolledToBottom</span><span class="o">(</span><span class="n">NestedScrollView</span> <span class="n">nsv</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="o">!</span><span class="n">nsv</span><span class="o">.</span><span class="na">canScrollVertically</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="o">}</span>
<span class="cm">/**
* Returns true iff the RecyclerView is scrolled to the top of its
* content (i.e. if the RecyclerView's first item is completely visible).
*/</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isRvScrolledToTop</span><span class="o">(</span><span class="n">RecyclerView</span> <span class="n">rv</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">LinearLayoutManager</span> <span class="n">lm</span> <span class="o">=</span> <span class="o">(</span><span class="n">LinearLayoutManager</span><span class="o">)</span> <span class="n">rv</span><span class="o">.</span><span class="na">getLayoutManager</span><span class="o">();</span>
<span class="k">return</span> <span class="n">lm</span><span class="o">.</span><span class="na">findFirstVisibleItemPosition</span><span class="o">()</span> <span class="o">==</span> <span class="mi">0</span>
<span class="o">&&</span> <span class="n">lm</span><span class="o">.</span><span class="na">findViewByPosition</span><span class="o">(</span><span class="mi">0</span><span class="o">).</span><span class="na">getTop</span><span class="o">()</span> <span class="o">==</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>We’re nearly there! For the perfectionists out there, however, <strong>Figure 5</strong> shows one more bug that would be nice to fix. In the video on the left, the fling stops abruptly once the child reaches the top of its content. What we want is for the fling to finish in a single, fluid motion, as shown in the video on the right.</p>
<!-- Figure 5 -->
<div class="figure-container">
<div class="figure-parent">
<video class="figure-video figure-element" poster="/assets/videos/posts/2018/01/24/poster-nested-scrolling-bugs2.jpg" preload="auto" onclick="resumeVideo(this)">
<source src="/assets/videos/posts/2018/01/24/nested-scrolling-bugs2-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2018/01/24/nested-scrolling-bugs2-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2018/01/24/nested-scrolling-bugs2-opt.ogv" type="video/ogg" />
</video>
</div>
</div>
<div class="caption-container">
<p class="caption-element">
<strong>Figure 5</strong> - On the left, the fling stops abruptly once the child reaches the top of its content. On the right, the fling completes in a single, fluid motion.
</p>
</div>
<p>The crux of the problem is that up until recently, the support library did not provide a way for nested scroll children to transfer leftover nested fling velocity up to a nested scroll parent. I won’t go into too much detail here because Chris Banes has already written a detailed <a href="https://chris.banes.me/2017/06/09/carry-on-scrolling/">blog post</a> explaining the issue and how it should be fixed.<sup><a href="#footnote3" id="ref3">3</a></sup> But to summarize, all we need to do is update our parent and child views to implement the new-and-improved <code class="highlighter-rouge">NestedScrollingParent2</code> and <code class="highlighter-rouge">NestedScrollingChild2</code> interfaces, which were specifically added to address this problem in v26 of the support library.</p>
<p>Unfortunately <code class="highlighter-rouge">NestedScrollView</code> still implements the older <code class="highlighter-rouge">NestedScrollingParent</code> interface, so I had to create my own <a href="https://github.com/alexjlockwood/adp-nested-scrolling/blob/master/app/src/main/java/alexjlockwood/nestedscrolling/NestedScrollView2.java"><code class="highlighter-rouge">NestedScrollView2</code></a> class that implements the <code class="highlighter-rouge">NestedScrollingParent2</code> interface to get things working.<sup><a href="#footnote4" id="ref4">4</a></sup> The final, bug-free <code class="highlighter-rouge">NestedScrollView</code> implementation is given below:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* A NestedScrollView that implements the new-and-improved NestedScrollingParent2
* interface and that defines its own customized nested scrolling behavior. View
* source code for the NestedScrollView2 class here: j.mp/NestedScrollView2
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomNestedScrollView2</span> <span class="kd">extends</span> <span class="n">NestedScrollView2</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onNestedPreScroll</span><span class="o">(</span><span class="n">View</span> <span class="n">target</span><span class="o">,</span> <span class="kt">int</span> <span class="n">dx</span><span class="o">,</span> <span class="kt">int</span> <span class="n">dy</span><span class="o">,</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">consumed</span><span class="o">,</span> <span class="kt">int</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">RecyclerView</span> <span class="n">rv</span> <span class="o">=</span> <span class="o">(</span><span class="n">RecyclerView</span><span class="o">)</span> <span class="n">target</span><span class="o">;</span>
<span class="k">if</span> <span class="o">((</span><span class="n">dy</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">&&</span> <span class="n">isRvScrolledToTop</span><span class="o">(</span><span class="n">rv</span><span class="o">))</span> <span class="o">||</span> <span class="o">(</span><span class="n">dy</span> <span class="o">></span> <span class="mi">0</span> <span class="o">&&</span> <span class="o">!</span><span class="n">isNsvScrolledToBottom</span><span class="o">(</span><span class="k">this</span><span class="o">)))</span> <span class="o">{</span>
<span class="n">scrollBy</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">dy</span><span class="o">);</span>
<span class="n">consumed</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="n">dy</span><span class="o">;</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onNestedPreScroll</span><span class="o">(</span><span class="n">target</span><span class="o">,</span> <span class="n">dx</span><span class="o">,</span> <span class="n">dy</span><span class="o">,</span> <span class="n">consumed</span><span class="o">,</span> <span class="n">type</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// Note that we no longer need to override onNestedPreFling() here; the</span>
<span class="c1">// new-and-improved nested scrolling APIs give us the nested flinging</span>
<span class="c1">// behavior we want already by default!</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isNsvScrolledToBottom</span><span class="o">(</span><span class="n">NestedScrollView</span> <span class="n">nsv</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="o">!</span><span class="n">nsv</span><span class="o">.</span><span class="na">canScrollVertically</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isRvScrolledToTop</span><span class="o">(</span><span class="n">RecyclerView</span> <span class="n">rv</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">LinearLayoutManager</span> <span class="n">lm</span> <span class="o">=</span> <span class="o">(</span><span class="n">LinearLayoutManager</span><span class="o">)</span> <span class="n">rv</span><span class="o">.</span><span class="na">getLayoutManager</span><span class="o">();</span>
<span class="k">return</span> <span class="n">lm</span><span class="o">.</span><span class="na">findFirstVisibleItemPosition</span><span class="o">()</span> <span class="o">==</span> <span class="mi">0</span>
<span class="o">&&</span> <span class="n">lm</span><span class="o">.</span><span class="na">findViewByPosition</span><span class="o">(</span><span class="mi">0</span><span class="o">).</span><span class="na">getTop</span><span class="o">()</span> <span class="o">==</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>That’s all I’ve got for now! Thanks for reading, and don’t forget to leave a comment below if you have any questions!</p>
<hr class="footnote-divider" />
<p><sup id="footnote1">1</sup> This blog post uses the same terminology that <a href="https://developer.android.com/reference/android/view/View.html#canScrollVertically(int)">the framework uses</a> to describe scroll directions. That is, dragging your finger toward the bottom of the screen causes the view to scroll <i>up</i>, and dragging your finger towards the top of the screen causes the view to scroll <i>down</i>. <a href="#ref1" title="Jump to footnote 1.">↩</a></p>
<p><sup id="footnote2">2</sup> It’s worth noting that nested flings are handled in a very similar fashion. The child detects a fling in its <code>onTouchEvent(ACTION_UP)</code> method and notifies the parent by calling its own <code>dispatchNestedPreFling()</code> and <code>dispatchNestedFling()</code> methods. This triggers calls to the parent’s <code>onNestedPreFling()</code> and <code>onNestedFling()</code> methods and gives the parent an opportunity to react to the fling before and after the child consumes it. <a href="#ref2" title="Jump to footnote 2.">↩</a></p>
<p><sup id="footnote3">3</sup> I recommend watching the second half of Chris Banes’ <a href="https://www.youtube.com/watch?v=-bhjI_qLPdE">Droidcon 2016 talk</a> for more information on this topic as well. <a href="#ref3" title="Jump to footnote 3.">↩</a></p>
<p><sup id="footnote4">4</sup> If enough people star <a href="https://issuetracker.google.com/issues/69819421">this bug</a>, maybe this won’t be necessary in the future! :) <a href="#ref4" title="Jump to footnote 4.">↩</a></p>
An Introduction to Icon Animation Techniqueshttps://www.androiddesignpatterns.com/2016/11/introduction-to-icon-animation-techniques.html2016-11-29T00:00:00+00:002016-11-29T00:00:00+00:00<link rel="stylesheet" type="text/css" href="/css/posts/2016/11/29/style.css" />
<link rel="stylesheet" type="text/css" href="/css/posts/2016/11/29/style.css" />
<!--morestart-->
<p><a href="https://material.google.com/motion/creative-customization.html">Creative customization</a> is one of the tenets of material design; the subtle addition of an icon animation can add an element of wonder to the user experience, making your app feel more natural and alive. Unfortunately, building an icon animation from scratch using <code class="highlighter-rouge">VectorDrawable</code>s can be challenging. Not only does it take a fair amount of work to implement, but it also requires a vision of how the final result should look and feel. If you aren’t familiar with the different techniques that are most often used to create icon animations, you’re going to have a hard time designing your own.</p>
<p>This blog post covers several different techniques that you can use to create beautiful icon animations. The best way to learn is by example, so as you read through the post you’ll encounter interactive demos highlighting how each technique works. I hope this blog post can at the very least open your eyes to how icon animations behave under-the-hood, because I genuinely believe that understanding how they work is the first step towards creating your own.</p>
<!--more-->
<p>This post is split into the following sections:</p>
<ol class="icon-anim-table-of-contents">
<li><a href="#drawing-paths">Drawing <code>path</code>s</a></li>
<li><a href="#transforming-groups-of-paths">Transforming <code>group</code>s of <code>path</code>s</a></li>
<li><a href="#trimming-stroked-paths">Trimming stroked <code>path</code>s</a></li>
<li><a href="#morphing-paths">Morphing <code>path</code>s</a></li>
<li><a href="#clipping-paths">Clipping <code>path</code>s</a></li>
<li><a href="#conclusion-putting-it-all-together">Conclusion: putting it all together</a></li>
</ol>
<p>All of the icon animations in this blog post are available in <code class="highlighter-rouge">AnimatedVectorDrawable</code> format on <a href="https://github.com/alexjlockwood/adp-delightful-details">GitHub</a>. I
also encourage you to check out <a href="https://shapeshifter.design/">Shape Shifter</a>, a side project I’ve been working on
that helps simplify the process of creating path morphing animations for Android and the web.</p>
<h3 id="drawing-paths">Drawing <code class="highlighter-rouge">path</code>s</h3>
<p>Before we can begin creating animated icons, we first need to understand how they are drawn. In Android, we’ll create each icon using the relatively new <a href="https://developer.android.com/reference/android/graphics/drawable/VectorDrawable.html"><code class="highlighter-rouge">VectorDrawable</code></a> class. <code class="highlighter-rouge">VectorDrawable</code>s are similar in concept to SVGs on the web: they allow us to create scalable, density-independent assets by representing each icon as a series of lines and shapes called <code class="highlighter-rouge">path</code>s. Each path’s shape is determined by a sequence of <em>drawing commands</em>, represented by a space/comma-separated string using a subset of the <a href="http://www.w3.org/TR/SVG11/paths.html#PathData">SVG path data spec</a>. The spec defines many different types of commands, some of which are summarized in the table below:</p>
<table>
<thead>
<tr>
<th>Command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">M x,y</code></td>
<td>Begin a new subpath by moving to <code class="highlighter-rouge">(x,y)</code>.</td>
</tr>
<tr>
<td><code class="highlighter-rouge">L x,y</code></td>
<td>Draw a line to <code class="highlighter-rouge">(x,y)</code>.</td>
</tr>
<tr>
<td><code>C x<sub>1</sub>,y<sub>1</sub> x<sub>2</sub>,y<sub>2</sub> x,y</code></td>
<td>Draw a <a href="https://en.wikipedia.org/wiki/B%C3%A9zier_curve">cubic bezier curve</a> to <code class="highlighter-rouge">(x,y)</code> using control points <code>(x<sub>1</sub>,y<sub>1</sub>)</code> and <code>(x<sub>2</sub>,y<sub>2</sub>)</code>.</td>
</tr>
<tr>
<td><code class="highlighter-rouge">Z</code></td>
<td>Close the path by drawing a line back to the beginning of the current subpath.</td>
</tr>
</tbody>
</table>
<p>All <code class="highlighter-rouge">path</code>s come in one of two forms: <em>filled</em> or <em>stroked</em>. If the path is filled, the interiors of its shape will be painted. If the path is stroked, the paint will be applied along the outline of its shape. Both types of <code class="highlighter-rouge">path</code>s have their own set of animatable attributes that further modify their appearance:</p>
<table>
<thead>
<tr>
<th>Property name</th>
<th>Element type</th>
<th>Value type</th>
<th>Min</th>
<th>Max</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">android:fillAlpha</code></td>
<td><code class="highlighter-rouge"><path></code></td>
<td><code class="highlighter-rouge">float</code></td>
<td><code class="highlighter-rouge">0</code></td>
<td><code class="highlighter-rouge">1</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:fillColor</code></td>
<td><code class="highlighter-rouge"><path></code></td>
<td><code class="highlighter-rouge">integer</code></td>
<td>- - -</td>
<td>- - -</td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:strokeAlpha</code></td>
<td><code class="highlighter-rouge"><path></code></td>
<td><code class="highlighter-rouge">float</code></td>
<td><code class="highlighter-rouge">0</code></td>
<td><code class="highlighter-rouge">1</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:strokeColor</code></td>
<td><code class="highlighter-rouge"><path></code></td>
<td><code class="highlighter-rouge">integer</code></td>
<td>- - -</td>
<td>- - -</td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:strokeWidth</code></td>
<td><code class="highlighter-rouge"><path></code></td>
<td><code class="highlighter-rouge">float</code></td>
<td><code class="highlighter-rouge">0</code></td>
<td>- - -</td>
</tr>
</tbody>
</table>
<p>Let’s see how this all works with an example. Say we wanted to create a play, pause, and record icon for a music application. We can represent each icon using a single <code class="highlighter-rouge">path</code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><vector</span>
<span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">android:width=</span><span class="s">"48dp"</span>
<span class="na">android:height=</span><span class="s">"48dp"</span>
<span class="na">android:viewportHeight=</span><span class="s">"12"</span>
<span class="na">android:viewportWidth=</span><span class="s">"12"</span><span class="nt">></span>
<span class="c"><!-- This path draws an orange triangular play icon. --></span>
<span class="nt"><path</span>
<span class="na">android:fillColor=</span><span class="s">"#FF9800"</span>
<span class="na">android:pathData=</span><span class="s">"M 4,2.5 L 4,9.5 L 9.5,6 Z"</span><span class="nt">/></span>
<span class="c"><!-- This path draws two green stroked vertical pause bars. --></span>
<span class="nt"><path</span>
<span class="na">android:pathData=</span><span class="s">"M 4,2.5 L 4,9.5 M 8,2.5 L 8,9.5"</span>
<span class="na">android:strokeColor=</span><span class="s">"#0F9D58"</span>
<span class="na">android:strokeWidth=</span><span class="s">"2"</span><span class="nt">/></span>
<span class="c"><!-- This path draws a red circle. --></span>
<span class="nt"><path</span>
<span class="na">android:fillColor=</span><span class="s">"#DB4437"</span>
<span class="na">android:pathData=</span><span class="s">"M 2,6 C 2,3.8 3.8,2 6,2 C 8.2,2 10,3.8 10,6 C 10,8.2 8.2,10 6,10 C 3.8,10 2,8.2 2,6"</span><span class="nt">/></span>
<span class="nt"></vector></span>
</code></pre></div></div>
<p>The triangular play and circular record icons are both filled <code class="highlighter-rouge">path</code>s with orange and red fill colors respectively. The pause icon, on the other hand, is a stroked <code class="highlighter-rouge">path</code> with a green stroke color and a stroke width of 2. <strong>Figure 1</strong> illustrates each <code class="highlighter-rouge">path</code>’s drawing commands executed inside a <code class="highlighter-rouge">12x12</code> grid:</p>
<div>
<div class="svgDemoContainer">
<ul class="flex-container">
<li class="flex-item">
<div>
<svg viewBox="0 0 241 241" class="svgDemoGraphic">
<defs>
<pattern id="smallGrid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="gray" stroke-width="0.5" />
</pattern>
<pattern id="grid" width="80" height="80" patternUnits="userSpaceOnUse">
<rect width="80" height="80" fill="url(#smallGrid)" />
<path d="M 80 0 L 0 0 0 80" fill="none" stroke="gray" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
<path id="ic_play_basic_demo_path" fill="#FF9800" d="M 80 50 L 80 190 L 190 120 Z" />
<path id="ic_play_basic_demo_path_strokes" fill="none" stroke="#000" stroke-width="2" d="M 80 50 L 80 190 L 190 120 Z" />
<path id="ic_play_demo_path_end_points" fill="#000" d="
M 80 50 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z
M 80 190 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z
M 190 120 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z" />
<text text-anchor="end" x="70" y="40">1, 4</text>
<text text-anchor="end" x="70" y="208">2</text>
<text text-anchor="start" x="208" y="126">3</text>
</svg>
<div class="svgBasicDemoPathInstructionList">
Path commands:
<ol>
<li class="svgBasicDemoPathInstruction"><code>M 4,2.5</code></li>
<li class="svgBasicDemoPathInstruction"><code>L 4,9.5</code></li>
<li class="svgBasicDemoPathInstruction"><code>L 9.5,6</code></li>
<li class="svgBasicDemoPathInstruction"><code>Z</code></li>
</ol>
</div>
</div>
</li>
<li class="flex-item">
<div>
<svg viewBox="0 0 241 241" class="svgDemoGraphic">
<defs>
<pattern id="smallGrid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="gray" stroke-width="0.5" />
</pattern>
<pattern id="grid" width="80" height="80" patternUnits="userSpaceOnUse">
<rect width="80" height="80" fill="url(#smallGrid)" />
<path d="M 80 0 L 0 0 0 80" fill="none" stroke="gray" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
<path id="ic_pause_basic_demo_path" stroke="#0F9D58" stroke-width="40" d="M 80,50 L 80,190 M 160,50 L 160,190" />
<path id="ic_pause_basic_demo_path_strokes" fill="none" stroke="#000" stroke-width="2" d="M 80,50 L 80,190 M 160,50 L 160,190" />
<path id="ic_pause_demo_path_end_points" fill="#000" d="
M 80 50 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z
M 80 190 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z
M 160 50 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z
M 160 190 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z" />
<text text-anchor="middle" x="80" y="35">1</text>
<text text-anchor="middle" x="80" y="215">2</text>
<text text-anchor="middle" x="160" y="35">3</text>
<text text-anchor="middle" x="160" y="215">4</text>
</svg>
<div class="svgBasicDemoPathInstructionList">
Path commands:
<ol>
<li class="svgBasicDemoPathInstruction"><code>M 4,2.5</code></li>
<li class="svgBasicDemoPathInstruction"><code>L 4,9.5</code></li>
<li class="svgBasicDemoPathInstruction"><code>M 8,2.5</code></li>
<li class="svgBasicDemoPathInstruction"><code>L 8,9.5</code></li>
</ol>
</div>
</div>
</li>
<li class="flex-item">
<div>
<svg viewBox="0 0 241 241" class="svgDemoGraphic">
<defs>
<pattern id="smallGrid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="gray" stroke-width="0.5" />
</pattern>
<pattern id="grid" width="80" height="80" patternUnits="userSpaceOnUse">
<rect width="80" height="80" fill="url(#smallGrid)" />
<path d="M 80 0 L 0 0 0 80" fill="none" stroke="gray" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
<path id="ic_record_basic_demo_path" fill="#DB4437" d="
M 40 120
C 40 75.817220016 75.817220016 40 120 40
C 164.182779984 40 200 75.817220016 200 120
C 200 164.182779984 164.182779984 200 120 200
C 75.817220016 200 40 164.182779984 40 120 Z" />
<path id="ic_record_basic_demo_path_strokes" fill="none" stroke="#000" stroke-width="2" d="
M 40 120
C 40 75.817220016 75.817220016 40 120 40
C 164.182779984 40 200 75.817220016 200 120
C 200 164.182779984 164.182779984 200 120 200
C 75.817220016 200 40 164.182779984 40 120 Z" />
<path id="ic_record_demo_path_control_points" fill="#000" d="
M 40 75.817220016 m -4 0 a 4 4 0 1 0 8 0 a 4 4 0 1 0 -8 0 z
M 75.817220016 40 m -4 0 a 4 4 0 1 0 8 0 a 4 4 0 1 0 -8 0 z
M 164.182779984 40 m -4 0 a 4 4 0 1 0 8 0 a 4 4 0 1 0 -8 0 z
M 200 75.817220016 m -4 0 a 4 4 0 1 0 8 0 a 4 4 0 1 0 -8 0 z
M 200 164.182779984 m -4 0 a 4 4 0 1 0 8 0 a 4 4 0 1 0 -8 0 z
M 164.182779984 200 m -4 0 a 4 4 0 1 0 8 0 a 4 4 0 1 0 -8 0 z
M 75.817220016 200 m -4 0 a 4 4 0 1 0 8 0 a 4 4 0 1 0 -8 0 z
M 40 164.182779984 m -4 0 a 4 4 0 1 0 8 0 a 4 4 0 1 0 -8 0 z" />
<path id="ic_record_demo_path_end_points" fill="#000" d="
M 40 120 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z
M 120 40 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z
M 200 120 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z
M 120 200 m -6 0 a 6 6 0 1 0 12 0 a 6 6 0 1 0 -12 0 z" />
<text text-anchor="end" x="26" y="126">1, 5</text>
<text text-anchor="middle" x="120" y="26">2</text>
<text text-anchor="start" x="215" y="126">3</text>
<text text-anchor="middle" x="120" y="225">4</text>
</svg>
<div class="svgBasicDemoPathInstructionList">
Path commands:
<ol>
<li class="svgBasicDemoPathInstruction"><code>M 2,6</code></li>
<li class="svgBasicDemoPathInstruction"><code>C 2,3.8 3.8,2 6,2</code></li>
<li class="svgBasicDemoPathInstruction"><code>C 8.2,2 10,3.8 10,6</code></li>
<li class="svgBasicDemoPathInstruction"><code>C 10,8.2 8.2,10 6,10</code></li>
<li class="svgBasicDemoPathInstruction"><code>C 3.8,10 2,8.2 2,6</code></li>
</ol>
</div>
</div>
</li>
</ul>
</div>
<p class="mdl-typography--caption mdl-typography--text-center">
<strong>Figure 1.</strong> Understanding how paths are drawn using path commands. The numbers
in the diagrams match the position of the path after each command is executed. In each diagram, the top left coordinate
is <code>(0,0)</code> and the bottom right coordinate is <code>(12,12)</code>. The source code
for each icon is available on
<a href="https://gist.github.com/alexjlockwood/e70717b7cb9c040899f08b58860ea3fb">GitHub</a>.</p>
</div>
<p>As we previously mentioned, one of the benefits of <code class="highlighter-rouge">VectorDrawable</code>s is that they provide density independence, meaning that they can be scaled arbitrarily on any device without loss of quality. This ends up being both convenient and efficient: developers no longer need to go through the tedious process of exporting different sized PNGs for each screen density, which in turn also leads to a smaller APK size. In our case, however, <strong>the reason we want to use <code class="highlighter-rouge">VectorDrawable</code>s is so we can animate their individual <code class="highlighter-rouge">path</code>s using the <a href="https://developer.android.com/reference/android/graphics/drawable/AnimatedVectorDrawable.html"><code class="highlighter-rouge">AnimatedVectorDrawable</code></a> class.</strong> <code class="highlighter-rouge">AnimatedVectorDrawable</code>s are the glue that connect <code class="highlighter-rouge">VectorDrawable</code>s with <code class="highlighter-rouge">ObjectAnimator</code>s: the <code class="highlighter-rouge">VectorDrawable</code> assigns each animated <code class="highlighter-rouge">path</code> (or <code class="highlighter-rouge">group</code> of <code class="highlighter-rouge">path</code>s) a unique name, and the <code class="highlighter-rouge">AnimatedVectorDrawable</code> maps each of these names to their corresponding <code class="highlighter-rouge">ObjectAnimator</code>s. As we’ll see below, the ability to animate the individual elements within a <code class="highlighter-rouge">VectorDrawable</code> can be quite powerful.</p>
<h3 id="transforming-groups-of-paths">Transforming <code class="highlighter-rouge">group</code>s of <code class="highlighter-rouge">path</code>s</h3>
<p>In the previous section we learned how to alter a <code class="highlighter-rouge">path</code>’s appearance by directly modifying its properties, such as its opacity and color. In addition to this, <code class="highlighter-rouge">VectorDrawable</code>s also support <em>group transformations</em> using the <code class="highlighter-rouge"><group></code> tag, which allows us to apply transformations on multiple <code class="highlighter-rouge">path</code>s at a time using the following animatable attributes:</p>
<table>
<thead>
<tr>
<th>Property name</th>
<th>Element type</th>
<th>Value type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">android:pivotX</code></td>
<td><code class="highlighter-rouge"><group></code></td>
<td><code class="highlighter-rouge">float</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:pivotY</code></td>
<td><code class="highlighter-rouge"><group></code></td>
<td><code class="highlighter-rouge">float</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:rotation</code></td>
<td><code class="highlighter-rouge"><group></code></td>
<td><code class="highlighter-rouge">float</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:scaleX</code></td>
<td><code class="highlighter-rouge"><group></code></td>
<td><code class="highlighter-rouge">float</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:scaleY</code></td>
<td><code class="highlighter-rouge"><group></code></td>
<td><code class="highlighter-rouge">float</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:translateX</code></td>
<td><code class="highlighter-rouge"><group></code></td>
<td><code class="highlighter-rouge">float</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:translateY</code></td>
<td><code class="highlighter-rouge"><group></code></td>
<td><code class="highlighter-rouge">float</code></td>
</tr>
</tbody>
</table>
<p>It’s important to understand the order in which nested <code class="highlighter-rouge">group</code> transformations are applied. The two rules to remember are (1) children <code class="highlighter-rouge">group</code>s inherit the transformations applied by their parent groups, and (2) transformations made to the same <code class="highlighter-rouge">group</code> are applied in order of scale, rotation, and then translation. As an example, consider the following <code class="highlighter-rouge">group</code> transformations applied to the play, pause, and record icons discussed above:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><vector</span>
<span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">android:width=</span><span class="s">"48dp"</span>
<span class="na">android:height=</span><span class="s">"48dp"</span>
<span class="na">android:viewportHeight=</span><span class="s">"12"</span>
<span class="na">android:viewportWidth=</span><span class="s">"12"</span><span class="nt">></span>
<span class="c"><!-- Translate the canvas, then rotate, then scale, then draw the play icon. --></span>
<span class="nt"><group</span> <span class="na">android:scaleX=</span><span class="s">"1.5"</span> <span class="na">android:pivotX=</span><span class="s">"6"</span> <span class="na">android:pivotY=</span><span class="s">"6"</span><span class="nt">></span>
<span class="nt"><group</span> <span class="na">android:rotation=</span><span class="s">"90"</span> <span class="na">android:pivotX=</span><span class="s">"6"</span> <span class="na">android:pivotY=</span><span class="s">"6"</span><span class="nt">></span>
<span class="nt"><group</span> <span class="na">android:translateX=</span><span class="s">"2"</span><span class="nt">></span>
<span class="nt"><path</span> <span class="na">android:name=</span><span class="s">"play_path"</span><span class="nt">/></span>
<span class="nt"></group></span>
<span class="nt"></group></span>
<span class="nt"></group></span>
<span class="c"><!-- Rotate the canvas, then translate, then scale, then draw the pause icon. --></span>
<span class="nt"><group</span> <span class="na">android:scaleX=</span><span class="s">"1.5"</span> <span class="na">android:pivotX=</span><span class="s">"6"</span> <span class="na">android:pivotY=</span><span class="s">"6"</span><span class="nt">></span>
<span class="nt"><group</span>
<span class="na">android:rotation=</span><span class="s">"90"</span> <span class="na">android:pivotX=</span><span class="s">"6"</span> <span class="na">android:pivotY=</span><span class="s">"6"</span>
<span class="na">android:translateX=</span><span class="s">"2"</span><span class="nt">></span>
<span class="nt"><path</span> <span class="na">android:name=</span><span class="s">"pause_path"</span><span class="nt">/></span>
<span class="nt"></group></span>
<span class="nt"></group></span>
<span class="c"><!-- Scale the canvas, then rotate, then translate, then draw the record icon. --></span>
<span class="nt"><group</span> <span class="na">android:translateX=</span><span class="s">"2"</span><span class="nt">></span>
<span class="nt"><group</span>
<span class="na">android:rotation=</span><span class="s">"90"</span>
<span class="na">android:scaleX=</span><span class="s">"1.5"</span>
<span class="na">android:pivotX=</span><span class="s">"6"</span>
<span class="na">android:pivotY=</span><span class="s">"6"</span><span class="nt">></span>
<span class="nt"><path</span> <span class="na">android:name=</span><span class="s">"record_path"</span><span class="nt">/></span>
<span class="nt"></group></span>
<span class="nt"></group></span>
<span class="nt"></vector></span>
</code></pre></div></div>
<p>The transformed icons are shown in <strong>Figure 2</strong> below. Toggle the checkboxes to see how the different combinations of transformations affect the results!</p>
<div id="drawing_path_commands_root">
<div class="svgDemoContainer">
<ul class="flex-container">
<li class="flex-item">
<div>
<svg viewBox="0 0 241 241" class="svgDemoGraphic">
<defs>
<pattern id="smallGrid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="gray" stroke-width="0.5" />
</pattern>
<pattern id="grid" width="80" height="80" patternUnits="userSpaceOnUse">
<rect width="80" height="80" fill="url(#smallGrid)" />
<path d="M 80 0 L 0 0 0 80" fill="none" stroke="gray" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
<g transform="translate(120,120)">
<g id="transform_paths_play_scale" transform="scale(1.5,1)">
<g id="transform_paths_play_rotation" transform="rotate(90)">
<g transform="translate(-120,-120)">
<g id="transform_paths_play_translation" transform="translate(40,0)">
<path id="ic_play_basic_demo_path" fill="#FF9800" d="M 80 50 L 80 190 L 190 120 Z" />
</g>
</g>
</g>
</g>
</g>
</svg>
<ul class="svgTransformPathsDemoList">
<li class="svgBasicDemoPathInstruction">
<label for="playTransformTranslationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="playTransformTranslationCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label"><code>translateX="2"</code></span>
</label>
</li>
<li class="svgBasicDemoPathInstruction">
<label for="playTransformRotationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="playTransformRotationCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label"><code>rotation="90"</code></span>
</label>
</li>
<li class="svgBasicDemoPathInstruction">
<label for="playTransformScaleCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="playTransformScaleCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label"><code>scaleX="1.5"</code></span>
</label>
</li>
</ul>
</div>
</li>
<li class="flex-item">
<div>
<svg viewBox="0 0 241 241" class="svgDemoGraphic">
<defs>
<pattern id="smallGrid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="gray" stroke-width="0.5" />
</pattern>
<pattern id="grid" width="80" height="80" patternUnits="userSpaceOnUse">
<rect width="80" height="80" fill="url(#smallGrid)" />
<path d="M 80 0 L 0 0 0 80" fill="none" stroke="gray" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
<g transform="translate(120,120)">
<g id="transform_paths_pause_scale" transform="scale(1.5,1)">
<g transform="translate(-120,-120)">
<g id="transform_paths_pause_translation" transform="translate(40,0)">
<g transform="translate(120,120)">
<g id="transform_paths_pause_rotation" transform="rotate(90)">
<g transform="translate(-120,-120)">
<path id="ic_pause_basic_demo_path" stroke="#0F9D58" stroke-width="40" d="M 80,50 L 80,190 M 160,50 L 160,190" />
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
<ul class="svgTransformPathsDemoList">
<li class="svgBasicDemoPathInstruction">
<label for="pauseTransformRotationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="pauseTransformRotationCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label"><code>rotation="90"</code></span>
</label>
</li>
<li class="svgBasicDemoPathInstruction">
<label for="pauseTransformTranslationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="pauseTransformTranslationCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label"><code>translateX="2"</code></span>
</label>
</li>
<li class="svgBasicDemoPathInstruction">
<label for="pauseTransformScaleCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="pauseTransformScaleCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label"><code>scaleX="1.5"</code></span>
</label>
</li>
</ul>
</div>
</li>
<li class="flex-item">
<div>
<svg viewBox="0 0 241 241" class="svgDemoGraphic">
<defs>
<pattern id="smallGrid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="gray" stroke-width="0.5" />
</pattern>
<pattern id="grid" width="80" height="80" patternUnits="userSpaceOnUse">
<rect width="80" height="80" fill="url(#smallGrid)" />
<path d="M 80 0 L 0 0 0 80" fill="none" stroke="gray" stroke-width="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
<g id="transform_paths_record_translation" transform="translate(40,0)">
<g transform="translate(120,120)">
<g id="transform_paths_record_rotation" transform="rotate(90)">
<g id="transform_paths_record_scale" transform="scale(1.5,1)">
<g transform="translate(-120,-120)">
<path id="ic_record_basic_demo_path" fill="#DB4437" d="
M 40 120
C 40 75.817220016 75.817220016 40 120 40
C 164.182779984 40 200 75.817220016 200 120
C 200 164.182779984 164.182779984 200 120 200
C 75.817220016 200 40 164.182779984 40 120 Z" />
</g>
</g>
</g>
</g>
</g>
</svg>
<ul class="svgTransformPathsDemoList">
<li class="svgBasicDemoPathInstruction">
<label for="recordTransformScaleCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="recordTransformScaleCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label"><code>scaleX="1.5"</code></span>
</label>
</li>
<li class="svgBasicDemoPathInstruction">
<label for="recordTransformRotationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="recordTransformRotationCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label"><code>rotation="90"</code></span>
</label>
</li>
<li class="svgBasicDemoPathInstruction">
<label for="recordTransformTranslationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="recordTransformTranslationCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label"><code>translateX="2"</code></span>
</label>
</li>
</ul>
</div>
</li>
</ul>
</div>
<p class="mdl-typography--caption mdl-typography--text-center"><strong>Figure 2.</strong> The effects of different combinations of <code><group></code> transformations on a play, pause, and record icon. The order of the checkboxes matches the order in which the transformations are applied in the sample code above. Android source code for each icon is available on <a href="https://gist.github.com/alexjlockwood/2d163aa6138a7f8894d76991456a9f68">GitHub</a>.</p>
</div>
<p>The ability to chain together <code class="highlighter-rouge">group</code> transformations makes it possible to achieve a variety of cool effects. <strong>Figure 3</strong> shows three such examples:</p>
<ul>
<li>
<p>The <em>expand/collapse icon</em> is drawn using two rectangular paths. When clicked, the two paths are simultaneously rotated 90° and vertically translated to create the transition.</p>
</li>
<li>
<p>The <em>alarm clock icon</em> draws its bells using two rectangular paths. When clicked, a <code class="highlighter-rouge"><group></code> containing the two paths is rotated back and forth about the center to create a ‘ringing’ effect.</p>
</li>
<li>
<p>The <em>radio button icon</em> animation is one of my favorites due to its clever simplicity. The icon is drawn using only two paths: a filled inner dot and a stroked outer ring. When the radio button transitions between an unchecked to checked state, three properties are animated:</p>
<table>
<thead>
<tr>
<th>Time</th>
<th>Outer ring <code class="highlighter-rouge">strokeWidth</code></th>
<th>Outer ring <code class="highlighter-rouge">scale{X,Y}</code></th>
<th>Inner dot <code class="highlighter-rouge">scale{X,Y}</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>2</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>0.333</td>
<td>18</td>
<td>0.5</td>
<td>0</td>
</tr>
<tr>
<td>0.334</td>
<td>2</td>
<td>0.9</td>
<td>1.5</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>1</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>Pay particular attention to the first third of the animation, when the outer ring’s stroke width and scale are simultaneously increased and decreased respectively to make it look as if the outer ring is collapsing inwards towards the center—a pretty awesome effect!</p>
</li>
</ul>
<div>
<div class="svgDemoContainer">
<ul class="flex-container">
<li class="flex-item">
<svg id="ic_expand_collapse" viewBox="0 0 24 24" class="svgDemoGraphic">
<g id="chevron" transform="translate(12,15)">
<g id="leftBar" transform="rotate(135)">
<g transform="translate(0,3)">
<path id="leftBarPath" class="delightIconFillPath" d="M1-4v8h-2v-8z" />
<path id="leftBarPathHighlight" class="delightIconHighlightPath" stroke-width="0.4" d="M1-4v8h-2v-8z" />
</g>
</g>
<g id="rightBar" transform="rotate(45)">
<g transform="translate(0,-3)">
<path id="rightBarPath" class="delightIconFillPath" d="M1-4v8h-2v-8z" />
<path id="rightBarPathHighlight" class="delightIconHighlightPath" stroke-width="0.4" d="M1-4v8h-2v-8z" />
</g>
</g>
</g>
</svg>
</li>
<li class="flex-item">
<svg id="ic_alarm" viewBox="0 0 24 24" class="svgDemoGraphic">
<g transform="translate(12,12)">
<g id="alarmclock_button_rotation">
<g transform="translate(-12,-12)">
<g transform="translate(19.0722,4.5758)">
<path id="basicTransformationAlarmLeftBell" class="delightIconFillPath" d="M2.94 1.162l-4.595-3.857L-2.94-1.16l4.595 3.855L2.94 1.162z" />
<path id="basicTransformationAlarmLeftBellHighlight" class="delightIconHighlightPath" stroke-width="0.4" d="M2.94 1.162l-4.595-3.857L-2.94-1.16l4.595 3.855L2.94 1.162z" />
</g>
<g transform="translate(4.9262,4.5729)">
<path id="basicTransformationAlarmRightBell" class="delightIconFillPath" d="M2.94-1.163L1.656-2.695-2.94 1.16l1.285 1.535L2.94-1.163z" />
<path id="basicTransformationAlarmRightBellHighlight" class="delightIconHighlightPath" stroke-width="0.4" d="M2.94-1.163L1.656-2.695-2.94 1.16l1.285 1.535L2.94-1.163z" />
</g>
</g>
</g>
</g>
<path id="basicTransformationAlarmHands" class="delightIconFillPath" d="M12.5 8.02H11v6l4.747 2.854.753-1.232-4-2.372V8.02z" />
<path id="basicTransformationAlarmRing" class="delightIconFillPath" d="M11.995 4.02C7.02 4.02 3 8.05 3 13.02s4.02 9 8.995 9S21 17.99 21 13.02s-4.03-9-9.005-9zm.005 16c-3.867 0-7-3.134-7-7s3.133-7 7-7 7 3.134 7 7-3.133 7-7 7z" />
</svg>
</li>
<li class="flex-item">
<svg id="ic_radiobutton" viewBox="0 0 32 32" class="svgDemoGraphic">
<g transform="translate(16,16)">
<g id="radiobutton_ring_group">
<path id="radiobutton_ring_path" class="delightIconStrokePath" stroke-width="2" d="M-9 0A9 9 0 1 0 9 0 9 9 0 1 0-9 0" />
<path id="radiobutton_ring_path_highlight" class="delightIconHighlightPath" stroke-width="0.3" d="M-9 0A9 9 0 1 0 9 0 9 9 0 1 0-9 0" />
</g>
<g id="radiobutton_dot_group" transform="scale(0,0)">
<path id="radiobutton_dot_path" class="delightIconFillPath" d="M-5 0A5 5 0 1 0 5 0 5 5 0 1 0-5 0" />
<path id="radiobutton_dot_path_highlight" class="delightIconHighlightPath" stroke-width="0.3" d="M-5 0A5 5 0 1 0 5 0 5 5 0 1 0-5 0" />
</g>
</g>
</svg>
</li>
</ul>
<div class="svgDemoCheckboxContainer">
<label for="basicTransformationHighlightAnimatingPathsCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="basicTransformationHighlightAnimatingPathsCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Highlight animated paths</span>
</label>
<label for="basicTransformationSlowAnimationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="basicTransformationSlowAnimationCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Slow animation</span>
</label>
</div>
</div>
<p class="mdl-typography--caption mdl-typography--text-center"><strong>Figure 3.</strong> Understanding how <code><group></code> transformations can be used to create icon animations. Android source code for each is available on GitHub: (a)
<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_checkable_expandcollapse.xml">expand to collapse</a>, (b)
<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/avd_clock_alarm.xml">alarm clock</a>, and (c)
<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_checkable_radiobutton.xml">radio button</a>. Click each icon to start its animation.</p>
</div>
<p>One last animation that makes use of group transformations is the <em>horizontal indeterminate progress bar</em>. A horizontal indeterminate progress bar consists of three paths: a translucent background and two inner rectangular paths. During the animation the two inner rectangles are horizontally translated and scaled at varying degrees. Toggle the checkboxes in <strong>Figure 4</strong> below to see how each transformation individually contributes to the animation!</p>
<div>
<div id="svgLinearProgressDemo" class="svgDemoContainer">
<div id="progressBarContainer">
<div id="progressBar">
<div id="progressBarOuterRect1">
<div id="progressBarInnerRect1"></div>
</div>
<div id="progressBarOuterRect2">
<div id="progressBarInnerRect2"></div>
</div>
</div>
</div>
<div class="svgDemoCheckboxContainer">
<label for="linearProgressScaleCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="linearProgressScaleCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label">Animate scale</span>
</label>
<label for="linearProgressTranslateCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="linearProgressTranslateCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label">Animate translation</span>
</label>
</div>
</div>
<p class="mdl-typography--caption mdl-typography--text-center"><strong>Figure 4.</strong> Understanding how scale and translation are used to animate a horizontal indeterminate progress indicator (<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/avd_progress_indeterminate_horizontal.xml">source code</a>).</p>
</div>
<h3 id="trimming-stroked-paths">Trimming stroked <code class="highlighter-rouge">path</code>s</h3>
<p>A lesser known property of stroked paths is that they can be <em>trimmed</em>. That is, given a stroked path we can choose to show only a portion of it before it is drawn to the display. In Android, this is done using the following animatable attributes:</p>
<table>
<thead>
<tr>
<th>Property name</th>
<th>Element type</th>
<th>Value type</th>
<th>Min</th>
<th>Max</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">android:trimPathStart</code></td>
<td><code class="highlighter-rouge"><path></code></td>
<td><code class="highlighter-rouge">float</code></td>
<td><code class="highlighter-rouge">0</code></td>
<td><code class="highlighter-rouge">1</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:trimPathEnd</code></td>
<td><code class="highlighter-rouge"><path></code></td>
<td><code class="highlighter-rouge">float</code></td>
<td><code class="highlighter-rouge">0</code></td>
<td><code class="highlighter-rouge">1</code></td>
</tr>
<tr>
<td><code class="highlighter-rouge">android:trimPathOffset</code></td>
<td><code class="highlighter-rouge"><path></code></td>
<td><code class="highlighter-rouge">float</code></td>
<td><code class="highlighter-rouge">0</code></td>
<td><code class="highlighter-rouge">1</code></td>
</tr>
</tbody>
</table>
<p><code class="highlighter-rouge">trimPathStart</code> determines where the visible portion of the path will begin, while <code class="highlighter-rouge">trimPathEnd</code> determines where the visible portion of the path will end. An additional <code class="highlighter-rouge">trimPathOffset</code> may also be appended to the start and end values if desired. <strong>Figure 5</strong> demonstrates how this all works—update the sliders to see how different values affect what is drawn to the display! Note that it is perfectly fine for <code class="highlighter-rouge">trimPathStart</code> to be greater than <code class="highlighter-rouge">trimPathEnd</code>; if this occurs, the visible portion of the path simply wraps around the end of the segment back to the beginning.</p>
<div>
<div class="svgDemoContainer">
<svg id="ic_line_path" viewBox="0 0 24 1" width="95%">
<path id="line_path_background" fill="none" stroke="#000" stroke-opacity="0.26" stroke-width=".25" d="M 0.5,0.5 h 23" />
<path id="line_path" fill="none" stroke="#000" stroke-width=".25" stroke-dasharray="11.5,11.5" d="M 0.5,0.5 h 23" />
</svg>
<div class="sliderContainer">
<div class="sliderTextContainer">
<div class="slider">
<input id="trimPathStart" class="mdl-slider mdl-js-slider sliderInput" type="range" min="0" max="100" value="0" tabindex="0" />
</div>
<div class="sliderText"><code>trimPathStart="<span id="trimPathStartValue">0</span>"</code></div>
</div>
<div class="sliderTextContainer">
<div class="slider">
<input id="trimPathEnd" class="mdl-slider mdl-js-slider sliderInput" type="range" min="0" max="100" value="50" tabindex="0" />
</div>
<div class="sliderText"><code>trimPathEnd="<span id="trimPathEndValue">0.5</span>"</code></div>
</div>
<div class="sliderTextContainer">
<div class="slider">
<input id="trimPathOffset" class="mdl-slider mdl-js-slider sliderInput" type="range" min="0" max="100" value="0" tabindex="0" />
</div>
<div class="sliderText"><code>trimPathOffset="<span id="trimPathOffsetValue">0</span>"</code></div>
</div>
</div>
</div>
<p class="mdl-typography--caption mdl-typography--text-center"><strong>Figure 5.</strong> Understanding the effects of the <code>trimPathStart</code>, <code>trimPathEnd</code>, and <code>trimPathOffset</code> properties on a stroked path.</p>
</div>
<p>The ability to animate these three properties opens up a world of possibilities. <strong>Figure 6</strong> shows four such examples:</p>
<ul>
<li>
<p>The <em>fingerprint icon</em> consists of 5 stroked paths, each with their trim path start and end values initially set to <code class="highlighter-rouge">0</code> and <code class="highlighter-rouge">1</code> respectively. When hidden, the difference is quickly animated to <code class="highlighter-rouge">0</code> until the icon is no longer visible, and then quickly back to <code class="highlighter-rouge">1</code> when the icon is later shown. The <em>cursive handwriting icon</em> behaves similarly, except instead of animating the individual paths all at once, they are animated sequentially as if the word was being written out by hand.</p>
</li>
<li>
<p>The <em>search to back icon</em> uses a clever combination of trim path animations in order to seamlessly transition between the stem of the search icon and the stem of a back arrow. Enable the ‘show trim paths’ checkbox and you’ll see how the changing <code class="highlighter-rouge">trimPathStart</code> and <code class="highlighter-rouge">trimPathEnd</code> values affect the relative location of the stem as it animates to its new state. Enable the ‘slow animation’ checkbox and you’ll also notice that the visible length of the stem changes over time: it expands slightly at the beginning and shrinks towards the end, creating a subtle ‘stretching’ effect that feels more natural. Creating this effect is actually quite easy: just begin animating one of the trim properties with a small start delay to make it look like one end of the path is animating faster than the other.</p>
</li>
<li>
<p>Each animating digit in the <em>Google IO 2016 icon</em> consists of 4 paths, each with a different stroke color and each with trim path start and end values covering a quarter of the digit’s total length. Each path’s <code class="highlighter-rouge">trimPathOffset</code> is then animated from <code class="highlighter-rouge">0</code> to <code class="highlighter-rouge">1</code> in order to create the effect.</p>
</li>
</ul>
<div id="includes6">
<div class="svgDemoContainer">
<ul class="flex-container">
<li class="flex-item">
<svg id="ic_fingerprint" viewBox="0 0 32 32" class="svgTrimPathDemoGraphic">
<g transform="translate(49.3335,50.66685)">
<path id="ridge_5_path_debug" class="delightIconFingerPrintStrokePathDebug" d="M-25.36-24.414c-.568.107-1.126.14-1.454.14-1.297 0-2.532-.343-3.62-1.123-1.677-1.204-2.77-3.17-2.77-5.392" />
<path id="ridge_7_path_debug" class="delightIconFingerPrintStrokePathDebug" d="M-36.14-21.784c-1.006-1.193-1.576-1.918-2.366-3.502-.828-1.66-1.314-3.492-1.314-5.485 0-3.664 2.97-6.633 6.633-6.633 3.662 0 6.632 2.97 6.632 6.632" />
<path id="ridge_6_path_debug" class="delightIconFingerPrintStrokePathDebug" d="M-42.19-25.676c-.76-2.143-.897-3.87-.897-5.13 0-1.46.25-2.847.814-4.096 1.562-3.45 5.035-5.85 9.068-5.85 5.495 0 9.95 4.453 9.95 9.947 0 1.832-1.486 3.316-3.318 3.316-1.83 0-3.316-1.483-3.316-3.315 0-1.83-1.483-3.316-3.315-3.316-1.83 0-3.316 1.484-3.316 3.315 0 2.57.99 4.887 2.604 6.587 1.222 1.285 2.432 2.1 4.476 2.69" />
<path id="ridge_2_path_debug" class="delightIconFingerPrintStrokePathDebug" d="M-44.065-38.167c1.19-1.775 2.675-3.246 4.56-4.273 1.883-1.028 4.044-1.61 6.34-1.61 2.29 0 4.44.578 6.32 1.597 1.878 1.02 3.36 2.48 4.552 4.242" />
<path id="ridge_1_path_debug" class="delightIconFingerPrintStrokePathDebug" d="M71.78 97.05c-2.27-1.313-4.712-2.07-7.56-2.07-2.85 0-5.234.78-7.345 2.07" />
<path id="ridge_5_path" class="delightIconFingerPrintStrokePath" d="M-25.36-24.414c-.568.107-1.126.14-1.454.14-1.297 0-2.532-.343-3.62-1.123-1.677-1.204-2.77-3.17-2.77-5.392" />
<path id="ridge_7_path" class="delightIconFingerPrintStrokePath" d="M-36.14-21.784c-1.006-1.193-1.576-1.918-2.366-3.502-.828-1.66-1.314-3.492-1.314-5.485 0-3.664 2.97-6.633 6.633-6.633 3.662 0 6.632 2.97 6.632 6.632" />
<path id="ridge_6_path" class="delightIconFingerPrintStrokePath" d="M-42.19-25.676c-.76-2.143-.897-3.87-.897-5.13 0-1.46.25-2.847.814-4.096 1.562-3.45 5.035-5.85 9.068-5.85 5.495 0 9.95 4.453 9.95 9.947 0 1.832-1.486 3.316-3.318 3.316-1.83 0-3.316-1.483-3.316-3.315 0-1.83-1.483-3.316-3.315-3.316-1.83 0-3.316 1.484-3.316 3.315 0 2.57.99 4.887 2.604 6.587 1.222 1.285 2.432 2.1 4.476 2.69" />
<path id="ridge_2_path" class="delightIconFingerPrintStrokePath" d="M-44.065-38.167c1.19-1.775 2.675-3.246 4.56-4.273 1.883-1.028 4.044-1.61 6.34-1.61 2.29 0 4.44.578 6.32 1.597 1.878 1.02 3.36 2.48 4.552 4.242" />
<path id="ridge_1_path" class="delightIconFingerPrintStrokePath" d="M71.78 97.05c-2.27-1.313-4.712-2.07-7.56-2.07-2.85 0-5.234.78-7.345 2.07" />
</g>
</svg>
</li>
<li class="flex-item">
<svg id="ic_search_back" viewBox="0 0 48 24" class="svgTrimPathDemoGraphic">
<path id="stem_debug" class="delightIconSearchToBackStrokePathDebug" d="M24.7 12.7l7.117 7.207C32.787 20.7 34.46 23 37.5 23s5.5-2.46 5.5-5.5-2.46-5.5-5.5-5.5h-5.683-12.97" />
<path id="search_circle_debug" class="delightIconSearchToBackStrokePathDebug" d="M25.39 13.39a5.5 5.5 0 1 1-7.78-7.78 5.5 5.5 0 1 1 7.78 7.78" />
<g id="arrow_head_debug">
<path id="arrow_head_top_debug" class="delightIconSearchToBackStrokePathDebug" d="M16.702 12.696l8.002-8.003" />
<path id="arrow_head_bottom_debug" class="delightIconSearchToBackStrokePathDebug" d="M16.71 11.276l8.012 8.012" />
</g>
<path id="stem" class="delightIconSearchToBackStrokePath" d="M24.7 12.7l7.117 7.207C32.787 20.7 34.46 23 37.5 23s5.5-2.46 5.5-5.5-2.46-5.5-5.5-5.5h-5.683-12.97" stroke-dasharray="9.75516635929,42.975462608" />
<path id="search_circle" class="delightIconSearchToBackStrokePath" d="M25.39 13.39a5.5 5.5 0 1 1-7.78-7.78 5.5 5.5 0 1 1 7.78 7.78" />
<g id="arrow_head" transform="translate(8,0)">
<path id="arrow_head_top" class="delightIconSearchToBackStrokePath" d="M16.702 12.696l8.002-8.003" stroke-dashoffset="11.317" stroke-dasharray="11.317" />
<path id="arrow_head_bottom" class="delightIconSearchToBackStrokePath" d="M16.71 11.276l8.012 8.012" stroke-dashoffset="11.33" stroke-dasharray="11.33" />
</g>
</svg>
</li>
<li class="flex-item">
<svg id="ic_android_handwriting" viewBox="0 0 170 68" class="svgTrimPathDemoGraphic">
<g transform="translate(2, 12)">
<path id="andro_debug" class="delightIconHandwritingStrokePathDebug" d="M.342 40.576c10.073 8.093 17.46-26.214 24.843-37.008-2.504 13.87-.942 31.505 5.634 34.256 6.575 2.752 10.747-12.91 13.866-20.387 0 7.477-7.16 19.9-5.436 20.876 3.597-7.226 10.768-15.395 13.076-16.554 2.307-1.16-1.44 14.734.942 14.376 8.927 2.946 8.88-19.38 21.295-12.37-12.416-4.875-12.516 11.16-11.494 12.643C76.07 34.924 86 6.615 81.632.9 72.673-.873 72.18 37.314 76.07 38.14c10.548-.318 14.896-18.363 13.145-22.848-5.363 7.766 2.17 5.983 4.633 9.62 2.506 3.4-3.374 14.54 2.506 13.907 4.856-.844 15.163-23.165 17.118-17.82-5.727-2.37-10.81 16.224-4.143 16.824 8.588.318 9.125-16.823 4.142-17.34" />
<path id="id_debug" class="delightIconHandwritingStrokePathDebug" d="M126.046 22.4c-4.284 6.404-2.96 14.827-.092 15.973 4.31 3.24 12.428-18.428 18.5-16.612-13.063 5.738-9.164 14.542-7.253 14.542 15.016-1.847 21.977-34.67 18.283-36.193-9.478 5.223-9.927 36.192-5.008 38.058 6.956 0 10.04-9.364 10.04-9.364" />
<path id="a_debug" class="delightIconHandwritingStrokePathDebug" d="M15.513 25.218c4.082 0 15.976-2.228 15.976-2.228" />
<path id="i1_dot_debug" class="delightIconHandwritingStrokePathDebug" d="M127.723 15.887l-.56 1.116" />
<path id="andro" class="delightIconHandwritingStrokePath" d="M.342 40.576c10.073 8.093 17.46-26.214 24.843-37.008-2.504 13.87-.942 31.505 5.634 34.256 6.575 2.752 10.747-12.91 13.866-20.387 0 7.477-7.16 19.9-5.436 20.876 3.597-7.226 10.768-15.395 13.076-16.554 2.307-1.16-1.44 14.734.942 14.376 8.927 2.946 8.88-19.38 21.295-12.37-12.416-4.875-12.516 11.16-11.494 12.643C76.07 34.924 86 6.615 81.632.9 72.673-.873 72.18 37.314 76.07 38.14c10.548-.318 14.896-18.363 13.145-22.848-5.363 7.766 2.17 5.983 4.633 9.62 2.506 3.4-3.374 14.54 2.506 13.907 4.856-.844 15.163-23.165 17.118-17.82-5.727-2.37-10.81 16.224-4.143 16.824 8.588.318 9.125-16.823 4.142-17.34" />
<path id="id" class="delightIconHandwritingStrokePath" d="M126.046 22.4c-4.284 6.404-2.96 14.827-.092 15.973 4.31 3.24 12.428-18.428 18.5-16.612-13.063 5.738-9.164 14.542-7.253 14.542 15.016-1.847 21.977-34.67 18.283-36.193-9.478 5.223-9.927 36.192-5.008 38.058 6.956 0 10.04-9.364 10.04-9.364" />
<path id="a" class="delightIconHandwritingStrokePath" d="M15.513 25.218c4.082 0 15.976-2.228 15.976-2.228" />
<path id="i1_dot" class="delightIconHandwritingStrokePath" d="M127.723 15.887l-.56 1.116" />
</g>
</svg>
</li>
<li class="flex-item">
<svg id="ic_io16_handwriting" viewBox="0 0 360 200" class="svgTrimPathDemoGraphic">
<path id="io16_hash" class="delightIconIo16StrokePath" stroke="#5C6BC0" stroke-linecap="round" d="M39,45L39,80 M57,45L57,80 M66,54L31,54 M66,71L31,71" />
<path id="io16_i_body" class="delightIconIo16StrokePath" stroke="#5C6BC0" d="M83,82L107,82A2,2 0,0 1,109 84L109,155A2,2 0,0 1,107 157L83,157A2,2 0,0 1,81 155L81,84A2,2 0,0 1,83 82z" />
<path id="io16_i_dot" class="delightIconIo16StrokePath" stroke="#5C6BC0" d="M94,59m-14,0a14,14 0,1 1,28 0a14,14 0,1 1,-28 0" />
<path id="io16_o" class="delightIconIo16StrokePath" stroke="#5C6BC0" d="M159.5,119.5m-37.5,0a37.5,37.5 0,1 1,75 0a37.5,37.5 0,1 1,-75 0" />
<path id="io16_one_1" class="delightIconIo16StrokePath" stroke="#84FFFF" d="M211,45L235,45A2,2 0,0 1,237 47L237,155A2,2 0,0 1,235 157L211,157A2,2 0,0 1,209 155L209,47A2,2 0,0 1,211 45z" />
<path id="io16_one_2" class="delightIconIo16StrokePath" stroke="#E91E63" d="M211,45L235,45A2,2 0,0 1,237 47L237,155A2,2 0,0 1,235 157L211,157A2,2 0,0 1,209 155L209,47A2,2 0,0 1,211 45z" />
<path id="io16_one_3" class="delightIconIo16StrokePath" stroke="#5C6BC0" d="M211,45L235,45A2,2 0,0 1,237 47L237,155A2,2 0,0 1,235 157L211,157A2,2 0,0 1,209 155L209,47A2,2 0,0 1,211 45z" />
<path id="io16_one_4" class="delightIconIo16StrokePath" stroke="#4DD0E1" d="M211,45L235,45A2,2 0,0 1,237 47L237,155A2,2 0,0 1,235 157L211,157A2,2 0,0 1,209 155L209,47A2,2 0,0 1,211 45z" />
<path id="io16_six_1" class="delightIconIo16StrokePath" stroke="#84FFFF" d="M302.14,60.72C302.29,61.46 276.46,97.06 270.1,112.55C260.87,138.44 278.6,149.83 284.3,152.76C299.15,160.38 316.85,150.27 323.08,141.43C329.3,132.59 333.05,109.99 316.85,100.57C306.85,94.75 290.54,97.32 290.2,97.06C289.85,96.79 276.32,81.31 276.46,80.88C276.6,80.45 294.73,77.62 302.88,84.28C315.76,92.99 315.62,114.99 306.84,127.42C298.06,139.85 276.46,144.38 260.54,130.73C238.46,111.79 259.06,85.64 260.87,83.1C260.87,83.1 286.23,46.19 286.83,46.03C287.43,45.87 301.99,59.99 302.14,60.72Z" />
<path id="io16_six_2" class="delightIconIo16StrokePath" stroke="#E91E63" d="M302.14,60.72C302.29,61.46 276.46,97.06 270.1,112.55C260.87,138.44 278.6,149.83 284.3,152.76C299.15,160.38 316.85,150.27 323.08,141.43C329.3,132.59 333.05,109.99 316.85,100.57C306.85,94.75 290.54,97.32 290.2,97.06C289.85,96.79 276.32,81.31 276.46,80.88C276.6,80.45 294.73,77.62 302.88,84.28C315.76,92.99 315.62,114.99 306.84,127.42C298.06,139.85 276.46,144.38 260.54,130.73C238.46,111.79 259.06,85.64 260.87,83.1C260.87,83.1 286.23,46.19 286.83,46.03C287.43,45.87 301.99,59.99 302.14,60.72Z" />
<path id="io16_six_3" class="delightIconIo16StrokePath" stroke="#5C6BC0" d="M302.14,60.72C302.29,61.46 276.46,97.06 270.1,112.55C260.87,138.44 278.6,149.83 284.3,152.76C299.15,160.38 316.85,150.27 323.08,141.43C329.3,132.59 333.05,109.99 316.85,100.57C306.85,94.75 290.54,97.32 290.2,97.06C289.85,96.79 276.32,81.31 276.46,80.88C276.6,80.45 294.73,77.62 302.88,84.28C315.76,92.99 315.62,114.99 306.84,127.42C298.06,139.85 276.46,144.38 260.54,130.73C238.46,111.79 259.06,85.64 260.87,83.1C260.87,83.1 286.23,46.19 286.83,46.03C287.43,45.87 301.99,59.99 302.14,60.72Z" />
<path id="io16_six_4" class="delightIconIo16StrokePath" stroke="#4DD0E1" d="M302.14,60.72C302.29,61.46 276.46,97.06 270.1,112.55C260.87,138.44 278.6,149.83 284.3,152.76C299.15,160.38 316.85,150.27 323.08,141.43C329.3,132.59 333.05,109.99 316.85,100.57C306.85,94.75 290.54,97.32 290.2,97.06C289.85,96.79 276.32,81.31 276.46,80.88C276.6,80.45 294.73,77.62 302.88,84.28C315.76,92.99 315.62,114.99 306.84,127.42C298.06,139.85 276.46,144.38 260.54,130.73C238.46,111.79 259.06,85.64 260.87,83.1C260.87,83.1 286.23,46.19 286.83,46.03C287.43,45.87 301.99,59.99 302.14,60.72Z" />
</svg>
</li>
</ul>
<div class="svgDemoCheckboxContainer">
<label for="includes6_showTrimPathsCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="includes6_showTrimPathsCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Show trim paths</span>
</label>
<label for="includes6_slowAnimationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="includes6_slowAnimationCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Slow animation</span>
</label>
</div>
</div>
<p class="mdl-typography--caption mdl-typography--text-center"><strong>Figure 6.</strong> Understanding how trimming stroked paths can be used to create icon animations. Android source code for each is available on GitHub: (a) <a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_fingerprint.xml">fingerprint</a>, (b) <a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_trimclip_searchback.xml">search to back arrow</a>, (c) <a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/avd_handwriting_android_design.xml">cursive handwriting</a>, and (d) <a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/avd_handwriting_io16.xml">Google IO 2016</a>. Click each icon to start its animation.</p>
</div>
<p>Lastly, <strong>Figure 7</strong> shows how a stroked trim path is used to animate the familiar <em>circular indeterminate progress bar</em>. The icon consists of a single, circular stroked path that is animated as follows:</p>
<ol>
<li>
<p>A <code class="highlighter-rouge"><group></code> containing the progress bar path is rotated from 0° to 720° over the course of 4,444ms.</p>
</li>
<li>
<p>The progress bar path’s trim path offset is animated from <code class="highlighter-rouge">0</code> to <code class="highlighter-rouge">0.25</code> over the course of 1,333ms.</p>
</li>
<li>
<p>Portions of the progress bar path are trimmed over the course of 1,333ms. Specifically, it animates through the following values:</p>
<table>
<thead>
<tr>
<th>Time</th>
<th><code class="highlighter-rouge">trimPathStart</code></th>
<th><code class="highlighter-rouge">trimPathEnd</code></th>
<th><code class="highlighter-rouge">trimPathOffset</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>0</td>
<td>0.03</td>
<td>0</td>
</tr>
<tr>
<td>0.5</td>
<td>0</td>
<td>0.75</td>
<td>0.125</td>
</tr>
<tr>
<td>1</td>
<td>0.75</td>
<td>0.78</td>
<td>0.25</td>
</tr>
</tbody>
</table>
<p>At time <code class="highlighter-rouge">t = 0.0</code>, the progress bar is at its smallest size (only 3% is visible). At <code class="highlighter-rouge">t = 0.5</code>, the progress bar has stretched to its maximum size (75% is visible). And at time <code class="highlighter-rouge">t = 1.0</code>, the progress bar has shrunk back to its smallest size, just as the animation is about to restart.</p>
</li>
</ol>
<div>
<div id="svgCircularProgressDemos" class="svgDemoContainer">
<svg id="circular_progress" viewBox="0 0 48 48" style="max-width: 320px; max-height: 320px;">
<g id="circular_progress_position" transform="translate(24,24)">
<g id="circular_progress_outer_rotation">
<g id="circular_progress_inner_rotation">
<path id="circular_progress_circle_path_debug" d="M0,0 m 0,-18 a 18,18 0 1,1 0,36 a 18,18 0 1,1 0,-36" style="visibility: hidden;" stroke="#690" stroke-opacity="0.3" stroke-width="4" fill="none" />
<path id="circular_progress_circle_path" d="M0,0 m 0,-18 a 18,18 0 1,1 0,36 a 18,18 0 1,1 0,-36" stroke="#690" stroke-width="4" stroke-dasharray="3.39292006587,109.704415463" fill="none" />
</g>
</g>
</g>
</svg>
<div class="svgDemoCheckboxContainer">
<label for="circularProgressOuterRotationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="circularProgressOuterRotationCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label">Animate rotation</span>
</label>
<label for="circularProgressTrimPathOffsetCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="circularProgressTrimPathOffsetCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label">Animate trim path offset</span>
</label>
<label for="circularProgressTrimPathStartEndCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="circularProgressTrimPathStartEndCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label">Animate trim path start/end</span>
</label>
<label for="circularProgressShowTrimPathsCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="circularProgressShowTrimPathsCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Show trim paths</span>
</label>
<label for="circularProgressSlowAnimationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="circularProgressSlowAnimationCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Slow animation</span>
</label>
</div>
</div>
<p class="mdl-typography--caption mdl-typography--text-center"><strong>Figure 7.</strong> Understanding how rotation and a stroked trim path are used to animate a circular indeterminate progress indicator (<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/avd_progress_indeterminate_circular.xml">source code</a>).</p>
</div>
<h3 id="morphing-paths">Morphing <code class="highlighter-rouge">path</code>s</h3>
<p>The most advanced icon animation technique we’ll cover in this post is path morphing. Path morphing allows us to seamlessly transform the shapes of two paths by animating the differences in their drawing commands, as specified in their <code class="highlighter-rouge">android:pathData</code> attributes. With path morphing, we can transform a plus sign into a minus sign, a play icon into a pause icon, or even an overflow icon into a back arrow, as seen in <strong>Figure 8</strong> below.</p>
<table>
<thead>
<tr>
<th>Property name</th>
<th>Element type</th>
<th>Value type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">android:pathData</code></td>
<td><code class="highlighter-rouge"><path></code></td>
<td><code class="highlighter-rouge">string</code></td>
</tr>
</tbody>
</table>
<p>The first thing to consider when implementing a path morphing animation is whether or not the paths you want to morph are <em>compatible</em>. In order to morph path <code class="highlighter-rouge">A</code> into path <code class="highlighter-rouge">B</code> the following conditions must be met:</p>
<ol>
<li><code class="highlighter-rouge">A</code> and <code class="highlighter-rouge">B</code> have the same number of drawing commands.</li>
<li>The <code class="highlighter-rouge">i</code>th drawing command in <code class="highlighter-rouge">A</code> must have the same type as the <code class="highlighter-rouge">i</code>th drawing command in <code class="highlighter-rouge">B</code>, for all <code class="highlighter-rouge">i</code>.</li>
<li>The <code class="highlighter-rouge">i</code>th drawing command in <code class="highlighter-rouge">A</code> must have the same number of parameters as the <code class="highlighter-rouge">i</code>th drawing command in <code class="highlighter-rouge">B</code>, for all <code class="highlighter-rouge">i</code>.</li>
</ol>
<p>If any of these conditions aren’t met (i.e. attempting to morph an <code class="highlighter-rouge">L</code> command into a <code class="highlighter-rouge">C</code> command, or an <code class="highlighter-rouge">l</code> command with 2 coordinates into an <code class="highlighter-rouge">l</code> command with 4 coordinates, etc.), the application will crash with an exception. The reason these rules must be enforced is due to the way path morphing animations are implemented under-the-hood. Before the animation begins, the framework extracts the command types and their coordinates from each path’s <code class="highlighter-rouge">android:pathData</code> attribute. If the conditions above are met, then the framework can assume that the only difference between the two paths are the values of the coordinates embedded in their drawing command strings. Under this assumption, the framework can execute the same sequence of drawing commands on each new display frame, re-calculating the values of the coordinates to use based on the current progress of the animation. <strong>Figure 8</strong> illustrates this concept nicely. First disable ‘animate rotation’, then enable the ‘show path coordinates’ and ‘slow animation’ checkboxes below. Notice how each path’s red coordinates change during the course of the animation: they travel a straight line from their starting positions in path <code class="highlighter-rouge">A</code> to their ending positions in path <code class="highlighter-rouge">B</code>. Path morphing animations are really that simple!</p>
<div>
<div class="svgDemoContainer">
<ul class="flex-container">
<li class="flex-item">
<svg id="ic_plus_minus" viewBox="0 0 24 24" class="svgDemoGraphic">
<g transform="translate(12,12)">
<g id="plus_minus_container_rotate">
<g id="plus_minus_container_translate" transform="translate(-12,-12)">
<path id="plus_minus_path" d="M5 11h6V5h2v6h6v2h-6v6h-2v-6H5z">
<animate id="plus_to_minus_path_animation" attributeName="d" begin="indefinite" dur="250ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" values="M 5,11 L 11,11 L 11,5 L 13,5 L 13,11 L 19,11 L 19,13 L 13,13 L 13,19 L 11,19 L 11,13 L 5,13 Z;M 5,11 L 11,11 L 11,11 L 13,11 L 13,11 L 19,11 L 19,13 L 13,13 L 13,13 L 11,13 L 11,13 L 5,13 Z" />
<animate id="minus_to_plus_path_animation" attributeName="d" begin="indefinite" dur="250ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" values="M 5,11 L 11,11 L 11,11 L 13,11 L 13,11 L 19,11 L 19,13 L 13,13 L 13,13 L 11,13 L 11,13 L 5,13 Z;M 5,11 L 11,11 L 11,5 L 13,5 L 13,11 L 19,11 L 19,13 L 13,13 L 13,19 L 11,19 L 11,13 L 5,13 Z" />
</path>
<path id="plus_minus_end_points_path" fill="#e00" style="visibility: hidden;">
<animate id="plus_minus_end_points_animation" attributeName="d" begin="indefinite" dur="250ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" />
</path>
</g>
</g>
</g>
</svg>
</li>
<li class="flex-item">
<svg id="ic_cross_tick" viewBox="0 0 24 24" class="svgDemoGraphic">
<g transform="translate(12,12)">
<g id="cross_tick_container_rotate">
<g id="cross_tick_container_translate" transform="translate(-12,-12)">
<path id="cross_tick_path" stroke="#000" stroke-width="2" stroke-linecap="square" d="M6.4 6.4l11.2 11.2m-11.2 0L17.6 6.4">
<animate id="cross_to_tick_path_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" values="M6.4,6.4 L17.6,17.6 M6.4,17.6 L17.6,6.4;M4.8,13.4 L9,17.6 M10.4,16.2 L19.6,7" />
<animate id="tick_to_cross_path_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" values="M4.8,13.4 L9,17.6 M10.4,16.2 L19.6,7;M6.4,6.4 L17.6,17.6 M6.4,17.6 L17.6,6.4" />
</path>
<path id="cross_tick_end_points_path" fill="#e00" style="visibility: hidden;">
<animate id="cross_tick_end_points_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" />
</path>
</g>
</g>
</g>
</svg>
</li>
<li class="flex-item">
<svg id="ic_arrow_drawer" viewBox="0 0 24 24" class="svgDemoGraphic">
<g transform="translate(12,12)">
<g id="arrow_drawer_container_rotate">
<g id="arrow_drawer_container_translate" transform="translate(-12,-12)">
<path id="arrow_drawer_path" d="M 3,6 L 3,8 L 21,8 L 21,6 L 3,6 z M 3,11 L 3,13 L 21,13 L 21, 12 L 21,11 L 3,11 z M 3,18 L 3,16 L 21,16 L 21,18 L 3,18 z">
<animate id="drawer_to_arrow_path_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" values="M 3,6 L 3,8 L 21,8 L 21,6 L 3,6 z M 3,11 L 3,13 L 21,13 L 21, 12 L 21,11 L 3,11 z M 3,18 L 3,16 L 21,16 L 21,18 L 3,18 z;M 12, 4 L 10.59,5.41 L 16.17,11 L 18.99,11 L 12,4 z M 4, 11 L 4, 13 L 18.99, 13 L 20, 12 L 18.99, 11 L 4, 11 z M 12,20 L 10.59, 18.59 L 16.17, 13 L 18.99, 13 L 12, 20z" />
<animate id="arrow_to_drawer_path_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" values="M 12, 4 L 10.59,5.41 L 16.17,11 L 18.99,11 L 12,4 z M 4, 11 L 4, 13 L 18.99, 13 L 20, 12 L 18.99, 11 L 4, 11 z M 12,20 L 10.59, 18.59 L 16.17, 13 L 18.99, 13 L 12, 20z;M 3,6 L 3,8 L 21,8 L 21,6 L 3,6 z M 3,11 L 3,13 L 21,13 L 21, 12 L 21,11 L 3,11 z M 3,18 L 3,16 L 21,16 L 21,18 L 3,18 z" />
</path>
<path id="arrow_drawer_end_points_path" fill="#e00" style="visibility: hidden;">
<animate id="drawer_arrow_end_points_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" />
</path>
</g>
</g>
</g>
</svg>
</li>
<li class="flex-item">
<svg id="ic_arrow_overflow" viewBox="0 0 24 24" class="svgDemoGraphic">
<g transform="translate(12,12)">
<g id="arrow_overflow_translate_dot3" transform="translate(0,6)">
<g id="arrow_overflow_rotate_dot3">
<g id="arrow_overflow_pivot_dot3">
<path id="arrow_overflow_path3" fill="#000" d="M 0,-2 l 0,0 c 1.05,0 2,0.895 2,2 l 0,0 c 0,1.05 -0.895,2 -2,2 l 0,0 c -1.05,0 -2,-0.895 -2,-2 l 0,0 c 0,-1.05 0.895,-2 2,-2 Z">
<animate id="overflow_to_arrow_path3_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" keySplines="0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1" fill="freeze" />
<animate id="arrow_to_overflow_path3_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;0.125;0.25;0.375;0.5;0.625;0.75;0.875;1" keySplines="0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1" fill="freeze" />
</path>
<path id="arrow_overflow_end_points_path3" style="visibility: hidden;" fill="#e00">
<animate id="arrow_overflow_end_points3_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" fill="freeze" />
</path>
</g>
</g>
</g>
<g id="arrow_overflow_translate_dot1" transform="translate(0,-6)">
<g id="arrow_overflow_rotate_dot1">
<g id="arrow_overflow_pivot_dot1">
<path id="arrow_overflow_path1" fill="#000" d="M 0,-2 l 0,0 c 1.05,0 2,0.895 2,2 l 0,0 c 0,1.05 -0.895,2 -2,2 l 0,0 c -1.05,0 -2,-0.895 -2,-2 l 0,0 c 0,-1.05 0.895,-2 2,-2 Z">
<animate id="overflow_to_arrow_path1_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" keySplines="0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1" fill="freeze" />
<animate id="arrow_to_overflow_path1_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;0.125;0.25;0.375;0.5;0.625;0.75;0.875;1" keySplines="0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1" fill="freeze" />
</path>
<path id="arrow_overflow_end_points_path1" style="visibility: hidden;" fill="#e00">
<animate id="arrow_overflow_end_points1_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" fill="freeze" />
</path>
</g>
</g>
</g>
<g id="arrow_overflow_translate_dot2">
<g id="arrow_overflow_pivot_dot2">
<path id="arrow_overflow_path2" fill="#000" d="M 0,-2 l 0,0 c 1.05,0 2,0.895 2,2 l 0,0 c 0,1.05 -0.895,2 -2,2 l 0,0 c -1.05,0 -2,-0.895 -2,-2 l 0,0 c 0,-1.05 0.895,-2 2,-2 Z">
<animate id="overflow_to_arrow_path2_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;0.1667;0.3333;0.5;0.6666;0.83333;1" keySplines="0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1" fill="freeze" />
<animate id="arrow_to_overflow_path2_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;0.125;0.25;0.375;0.5;0.625;0.75;0.875;1" keySplines="0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1" fill="freeze" />
</path>
<path id="arrow_overflow_end_points_path2" style="visibility: hidden;" fill="#e00">
<animate id="arrow_overflow_end_points2_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" fill="freeze" />
</path>
</g>
</g>
</g>
</svg>
</li>
<li class="flex-item">
<svg id="ic_play_pause_stop" viewBox="0 0 18 18" class="svgDemoGraphic">
<g id="play_pause_stop_translateX" transform="translate(0.75,0)">
<g transform="translate(9,9)">
<g id="play_pause_stop_rotate" transform="rotate(90)">
<g transform="translate(-9,-9)">
<path id="play_pause_stop_path" d="M9 5v8H4l5-8m0 0l5 8H9V5">
<animate id="play_pause_stop_animation" fill="freeze" attributeName="d" begin="indefinite" dur="200ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" />
</path>
<path id="play_pause_stop_end_points_path" style="visibility: hidden;" fill="#e00">
<animate id="play_pause_stop_end_points_animation" fill="freeze" attributeName="d" begin="indefinite" dur="200ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" />
</path>
</g>
</g>
</g>
</g>
</svg>
</li>
<li class="flex-item">
<svg id="ic_countdown" viewBox="0 0 1 1" class="svgDemoGraphic">
<g id="scale_container" transform="scale(0.8,0.8)">
<g id="countdown_container" transform="translate(0.1,0.1)">
<path id="countdown_digits" stroke="#000" stroke-width="0.02" fill="none" d="M.246.552C.246.332.37.1.552.1c.183 0 .31.23.31.452 0 .22-.127.442-.31.442C.37.994.246.774.246.552">
<animate id="countdown_digits_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" />
</path>
<path id="countdown_digits_cp1" style="visibility: hidden;" fill="#e00">
<animate id="countdown_digits_cp1_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" />
</path>
<path id="countdown_digits_cp2" style="visibility: hidden;" fill="#e00">
<animate id="countdown_digits_cp2_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" />
</path>
<path id="countdown_digits_end" style="visibility: hidden;" fill="#e00">
<animate id="countdown_digits_end_animation" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" fill="freeze" />
</path>
</g>
</g>
</svg>
</li>
</ul>
<div class="svgDemoCheckboxContainer">
<label for="pathMorphRotateCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="pathMorphRotateCheckbox" class="mdl-checkbox__input" checked="" />
<span class="mdl-checkbox__label">Animate rotation</span>
</label>
<label for="pathMorphShowPathPointsCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="pathMorphShowPathPointsCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Show path coordinates</span>
</label>
<label for="pathMorphSlowAnimationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="pathMorphSlowAnimationCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Slow animation</span>
</label>
</div>
</div>
<p class="mdl-typography--caption mdl-typography--text-center"><strong>Figure 8.</strong> Understanding how path morphing can be used to create icon animations. Android source code for each is available on GitHub: (a)
<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_pathmorph_plusminus.xml">plus to minus</a>, (b)
<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_pathmorph_crosstick.xml">cross to tick</a>, (c)
<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_pathmorph_drawer.xml">drawer to arrow</a>, (d)
<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_pathmorph_arrowoverflow.xml">overflow to arrow</a>, (e)
<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_playpausestop.xml">play to pause to stop</a>, and (f)
<a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_pathmorph_digits.xml">animating digits</a>. Click each icon to start its animation.</p>
</div>
<p>Although conceptually simple, path morphing animations are known at times for being tedious and time-consuming to implement. For example, you’ll often need to tweak the start and end paths by hand in order to make the two paths compatible to be morphed, which, depending on the complexity of the paths, is where most of the work will probably be spent. Listed below are several tips and tricks that I’ve found useful in getting started:</p>
<ul>
<li>
<p>Adding <em>dummy coordinates</em> is often necessary in order to make a simple path compatible with a more complex path. Dummy coordinates were added to nearly all of the examples shown <strong>Figure 8</strong>. For example, consider the plus-to-minus animation. We could draw a rectangular minus path using only 4 drawing commands. However, drawing the more complex plus path requires 12 drawing commands, so in order to make the two paths compatible we must add 8 additional noop drawing commands to the simpler minus path. Compare the two paths’ <a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/values/pathmorph_plusminus.xml">drawing command strings</a> and see if you can identify these dummy coordinates for yourself!</p>
</li>
<li>
<p>A cubic bezier curve command can be used to draw a straight line by setting its pair of control points equal to its start and end points respectively. This can be useful to know if you ever find yourself morphing an <code class="highlighter-rouge">L</code> command into a <code class="highlighter-rouge">C</code> command (such as in the overflow-to-arrow and animating digit examples above). It is also possible to estimate an <a href="https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands">elliptical arc command</a> using one or more cubic bezier curves, as I previously discussed <a href="https://plus.google.com/+AlexLockwood/posts/1q26J7qqkTZ">here</a>. This can also be useful to know if you ever find yourself in a situation where you need to morph a <code class="highlighter-rouge">C</code> command into an <code class="highlighter-rouge">A</code> command.</p>
</li>
<li>
<p>Sometimes morphing one path into another looks awkward no matter how you do it. In my experience, I’ve found that adding a 180° or 360° degree rotation to the animation can make them look significantly better: the additional rotation distracts the eye from the morphing paths and adds a layer of motion that makes the animation seem more responsive to the user’s touch.</p>
</li>
<li>
<p>Remember that path morphing animations are ultimately determined by the relative positioning of each path’s drawing command coordinates. For best results, try to minimize the distance each coordinate has to travel over the course of the animation: the smaller the distance each coordinate has to animate, the more seamless the path morphing animation will usually appear.</p>
</li>
</ul>
<h3 id="clipping-paths">Clipping <code class="highlighter-rouge">path</code>s</h3>
<p>The last technique we’ll cover involves animating the bounds of a <code class="highlighter-rouge"><clip-path></code>. A clip path restricts the region to which paint can be applied to the canvas—anything that lies outside of the region bounded by a clip path will not be drawn. By animating the bounds of these regions, we can create some cool effects, as we’ll see below.</p>
<table>
<thead>
<tr>
<th>Property name</th>
<th>Element type</th>
<th>Value type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">android:pathData</code></td>
<td><code class="highlighter-rouge"><clip-path></code></td>
<td><code class="highlighter-rouge">string</code></td>
</tr>
</tbody>
</table>
<p>A <code class="highlighter-rouge"><clip-path></code>’s bounds can be animated via path morphing by animating the differences in its path commands, as specified by its <code class="highlighter-rouge">android:pathData</code> attribute. Take a look at the examples in <strong>Figure 9</strong> to get a better idea of how these animations work. Enabling the ‘show clip paths’ checkbox will show a red overlay mask representing the bounds of the currently active <code class="highlighter-rouge"><clip-path></code>, which in turn dictates the portions of its sibling <code class="highlighter-rouge"><path></code>s that will be drawn. Clip path are especially useful for animating ‘fill’ effects, as demonstrated in the hourglass and heart fill/break examples below.</p>
<div>
<div class="svgDemoContainer">
<ul class="flex-container">
<li class="flex-item">
<svg id="ic_timer" viewBox="0 0 24 24" class="svgDemoGraphic">
<g transform="translate(12,12)">
<g id="hourglass_frame_rotation">
<path d="M 1,0 c 0,0 6.29,-6.29 6.29,-6.29 c 0.63,-0.63 0.19,-1.71 -0.7,-1.71 c 0,0 -13.18,0 -13.18,0 c -0.89,0 -1.33,1.08 -0.7,1.71 c 0,0 6.29,6.29 6.29,6.29 c 0,0 -6.29,6.29 -6.29,6.29 c -0.63,0.63 -0.19,1.71 0.7,1.71 c 0,0 13.18,0 13.18,0 c 0.89,0 1.33,-1.08 0.7,-1.71 c 0,0 -6.29,-6.29 -6.29,-6.29 Z M -4.17,-6 c 0,0 8.34,0 8.34,0 c 0,0 -4.17,4.17 -4.17,4.17 c 0,0 -4.17,-4.17 -4.17,-4.17 Z M -4.17,6 c 0,0 4.17,-4.17 4.17,-4.17 c 0,0 4.17,4.17 4.17,4.17 c 0,0 -8.34,0 -8.34,0 Z" fill="#000000" />
</g>
</g>
<g transform="translate(12,12)">
<g id="hourglass_fill_rotation">
<g transform="translate(-12,-12)">
<clipPath id="hourglass_clip_mask">
<path d="M 24,13.4 c 0,0 -24,0 -24,0 c 0,0 0,10.6 0,10.6 c 0,0 24,0 24,0 c 0,0 0,-10.6 0,-10.6 Z">
<animate id="hourglass_clip_mask_animation" fill="freeze" attributeName="d" begin="indefinite" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" values="M 24,13.4 c 0,0 -24,0 -24,0 c 0,0 0,10.6 0,10.6 c 0,0 24,0 24,0 c 0,0 0,-10.6 0,-10.6 Z;M 24,0 c 0,0 -24,0 -24,0 c 0,0 0,10.7 0,10.7 c 0,0 24,0 24,0 c 0,0 0,-10.7 0,-10.7 Z" />
</path>
</clipPath>
<g clip-path="url(#hourglass_clip_mask)">
<path d="M 13,12 c 0,0 6.29,-6.29 6.29,-6.29 c 0.63,-0.63 0.18,-1.71 -0.71,-1.71 c 0,0 -13.17,0 -13.17,0 c -0.89,0 -1.34,1.08 -0.71,1.71 c 0,0 6.29,6.29 6.29,6.29 c 0,0 -6.29,6.29 -6.29,6.29 c -0.63,0.63 -0.18,1.71 0.71,1.71 c 0,0 13.17,0 13.17,0 c 0.89,0 1.34,-1.08 0.71,-1.71 c 0,0 -6.29,-6.29 -6.29,-6.29 Z" fill="#000000" />
</g>
<path id="hourglass_clip_mask_debug" d="M 24,13.4 c 0,0 -24,0 -24,0 c 0,0 0,10.6 0,10.6 c 0,0 24,0 24,0 c 0,0 0,-10.6 0,-10.6 Z" fill="#F44336" fill-opacity="0.3" style="visibility: hidden;">
<animate id="hourglass_clip_mask_debug_animation" fill="freeze" attributeName="d" begin="indefinite" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" values="M 24,13.4 c 0,0 -24,0 -24,0 c 0,0 0,10.6 0,10.6 c 0,0 24,0 24,0 c 0,0 0,-10.6 0,-10.6 Z;M 24,0 c 0,0 -24,0 -24,0 c 0,0 0,10.7 0,10.7 c 0,0 24,0 24,0 c 0,0 0,-10.7 0,-10.7 Z" />
</path>
</g>
</g>
</g>
</svg>
</li>
<li class="flex-item">
<svg id="ic_visibility" class="svgDemoGraphic" viewBox="0 0 24 24">
<path id="cross_out_path" fill="none" stroke="#000" stroke-width="1.8" stroke-linecap="square" d="M3.27 4.27l16.47 16.47" />
<clipPath id="eye_mask_clip_path">
<path id="eye_mask" d="M2 4.27L19.73 22l2.54-2.54L4.54 1.73V1H23v22H1V4.27z">
<animate id="eye_mask_animation" fill="freeze" attributeName="d" begin="indefinite" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" />
</path>
</clipPath>
<g id="eye_mask_clip_path_group" clip-path="url(#eye_mask_clip_path)">
<path id="eye" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" />
</g>
<path id="eye_mask_clip_path_debug" d="M2 4.27L19.73 22l2.54-2.54L4.54 1.73V1H23v22H1V4.27z" fill="#F44336" fill-opacity=".3" style="visibility: hidden;">
<animate id="eye_mask_debug_animation" fill="freeze" attributeName="d" begin="indefinite" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" />
</path>
</svg>
</li>
<li class="flex-item">
<svg id="ic_heart" class="svgDemoGraphic" viewBox="0 0 56 56">
<g transform="translate(28,28) scale(1.5,1.5) translate(-28,-28)">
<path id="heart_stroke_left" fill="none" stroke="#000" stroke-width="2" d="M28.72 38.296l-3.05-2.744c-4.05-3.76-7.654-6.66-7.654-10.707 0-3.257 2.615-4.88 5.618-4.88 1.365 0 3.165 1.216 5.01 3.165" />
<path id="heart_stroke_right" fill="none" stroke="#000" stroke-width="2" d="M27.23 38.294l3.535-3.094c4.07-3.965 6.987-6.082 7.24-10.116.163-2.625-2.232-5.05-4.626-5.05-2.948 0-3.708 1.013-6.15 3.1" />
<clipPath id="heart_clip">
<path id="heart_clip_path" d="M14 42 L42 42 L42 42 L14 42 Z">
<animate id="heart_fill_animation" fill="freeze" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" values="M14 42 L42 42 L42 42 L14 42 Z;M14 14 L42 14 L42 42 L14 42 Z" />
</path>
</clipPath>
<g id="clip_path_group" clip-path="url(#heart_clip)">
<path id="heart_full_path" fill="#000" style="visibility: hidden;" d="M28 39l-1.595-1.433C20.74 32.47 17 29.11 17 24.995 17 21.632 19.657 19 23.05 19c1.914 0 3.75.883 4.95 2.272C29.2 19.882 31.036 19 32.95 19c3.393 0 6.05 2.632 6.05 5.995 0 4.114-3.74 7.476-9.405 12.572L28 39z" />
</g>
<g id="broken_heart_left_group" transform="translate(28,37.3)">
<g id="broken_heart_rotate_left_group">
<g id="broken_heart_translate_left_group" transform="translate(-28,-37.3)">
<path id="broken_heart_left_path" fill-opacity="0" d="M28.03 21.054l-.03.036C26.91 19.81 25.24 19 23.5 19c-3.08 0-5.5 2.42-5.5 5.5 0 3.78 3.4 6.86 8.55 11.53L28 37.35l.002-.002-.22-.36.707-.915-.984-1.31 1.276-1.736-1.838-2.02 2.205-2.282-2.033-1.582 2.032-2.125-2.662-2.04 1.543-1.924z" />
</g>
</g>
</g>
<g id="broken_heart_right_group" transform="translate(28,37.3)">
<g id="broken_heart_rotate_right_group">
<g id="broken_heart_translate_right_group" transform="translate(-28,-37.3)">
<path id="broken_heart_right_path" fill-opacity="0" d="M28.03 21.054c.14-.16.286-.31.44-.455l.445-.374C29.925 19.456 31.193 19 32.5 19c3.08 0 5.5 2.42 5.5 5.5 0 3.78-3.4 6.86-8.55 11.54l-1.448 1.308-.22-.36.707-.915-.984-1.31 1.276-1.736-1.838-2.02 2.205-2.282-2.033-1.582 2.032-2.125-2.662-2.04 1.543-1.924z" />
</g>
</g>
</g>
<path id="heart_clip_path_debug" style="visibility: hidden;" d="M14 42 L42 42 L42 42 L14 42 Z" fill="#F44336" fill-opacity="0.3">
<animate id="heart_fill_debug_animation" fill="freeze" attributeName="d" begin="indefinite" dur="300ms" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" values="M14 42 L42 42 L42 42 L14 42 Z;M14 14 L42 14 L42 42 L14 42 Z" />
</path>
</g>
</svg>
</li>
</ul>
<div class="svgDemoCheckboxContainer">
<label for="clipPathShowClipMaskCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="clipPathShowClipMaskCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Show clip paths</span>
</label>
<label for="clipPathSlowAnimationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="clipPathSlowAnimationCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Slow animation</span>
</label>
</div>
</div>
<p class="mdl-typography--caption mdl-typography--text-center"><strong>Figure 9.</strong> Understanding how <code><clip-path></code>s can be used to create icon animations. Android source code for each is available on GitHub: (a) <a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/avd_clock_timer.xml">hourglass</a>, (b) <a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_trimclip_eye.xml">eye visibility</a>, and (c) <a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/asl_trimclip_heart.xml">heart fill/break</a>. Click each icon to start its animation.</p>
</div>
<h3 id="conclusion-putting-it-all-together">Conclusion: putting it all together</h3>
<p>If you’ve made it this far in the blog post, that means you now have all of the fundamental building blocks you need in order to design your own icon animations from scratch! To celebrate, let’s finish off this ridiculously enormous blog post once and for all with one last kickass example! Consider the progress icon in <strong>Figure 10</strong>, which animates the following six properties:</p>
<ol>
<li>Fill alpha (at the end when fading out the downloading arrow).</li>
<li>Stroke width (during the progress indicator to check mark animation).</li>
<li>Translation and rotation (at the beginning to create the ‘bouncing arrow’ effect).</li>
<li>Trim path start/end (at the end when transitioning from the progress bar to the check mark).</li>
<li>Path morphing (at the beginning to create the ‘bouncing line’ effect, and at the end while transitioning the check mark back into an arrow).</li>
<li>Clip path (vertically filling the contents of the downloading arrow to indicate indeterminate progress).</li>
</ol>
<div id="includes10">
<div class="svgDemoContainer">
<svg id="ic_downloading" viewBox="0 0 240 240" style="max-width: 320px; max-height: 320px;">
<g transform="translate(120,120)">
<g transform="scale(0.91,0.91)">
<g id="downloading_progress_bar_outer_rotation">
<g id="downloading_progress_bar_inner_rotation">
<path id="downloading_progress_bar" fill="none" stroke="#000" stroke-opacity="0" stroke-linecap="square" stroke-linejoin="miter" stroke-width="20" d="M 0,-120 a 120,120 0 1,1 0,240 a 120,120 0 1,1 0,-240" />
<path id="downloading_progress_bar_check" transform="translate(-120,-120)" fill="none" stroke="#000" stroke-opacity="0" stroke-width="20" stroke-linecap="square" stroke-linejoin="miter" d="M 120,0 a 120,120 0 1,1 0,240 a 120,120 0 1,1 0,-240 C 224,30 162,83 162,83 L 106.5,138.5 L 80.45,112.45" />
<path id="downloading_progress_bar_check_debug" transform="translate(-120,-120)" fill="none" stroke="#000" stroke-opacity="0.3" style="visibility: hidden;" stroke-width="20" stroke-linecap="square" stroke-linejoin="miter" d="M 120,0 a 120,120 0 1,1 0,240 a 120,120 0 1,1 0,-240 C 224,30 162,83 162,83 L 106.5,138.5 L 80.45,112.45" />
</g>
</g>
</g>
</g>
<g transform="translate(120,120)">
<g transform="scale(0.65,0.65)">
<g transform="translate(-120,-120)">
<path id="downloading_line_path" fill="none" stroke="#000" stroke-width="20" d="M 50,190 c 0,0 47.6596,0 70,0 c 22.3404,0 70,0 70,0">
<animate id="downloading_line_path_animation" attributeName="d" begin="indefinite" calcMode="spline" keyTimes="0;0.5126;0.62885;0.8375;1" keySplines="0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1" fill="freeze" values="M 50,190 c 0,0 47.66,0 70,0 c 22.34,0 70,0 70,0;M 50,190 c 0,0 47.66,0 70,0 c 22.34,0 70,0 70,0;M 50,190 c 0,0 32.34,19.79 70,19.79 c 37.66,0 70,-19.79 70,-19.79;M 50,190 c 0,0 26.45,-7.98 69.67,-7.98 c 43.21,0 70.33,7.98 70.33,7.98;M 50,190 c 0,0 47.66,0 70,0 c 22.34,0 70,0 70,0" />
</path>
<path id="downloading_line_points_path" fill="#e00" style="visibility: hidden;">
<animate id="downloading_line_points_path_animation" attributeName="d" begin="indefinite" calcMode="spline" keyTimes="0;0.5126;0.62885;0.8375;1" keySplines="0 0 1 1;0 0 1 1;0 0 1 1;0 0 1 1" fill="freeze" />
</path>
<g id="downloading_arrow_group_translate">
<g transform="translate(120, 180)">
<g id="downloading_arrow_group_rotate">
<g transform="translate(-120, -180)">
<path id="downloading_arrow_path" fill="#4d4d4d" d="M 190,90 c 0,0 -40,0 -40,0 c 0,0 0,-60 0,-60 c 0,0 -60,0 -60,0 c 0,0 0,60 0,60 c 0,0 -40,0 -40,0 c 0,0 70,70 70,70 c 0,0 70,-70 70,-70 Z" />
<clipPath id="downloading_arrow_fill_clip">
<path id="downloading_arrow_clip_path" d="M 0,0 L 240,0 L 240,240 L 0,240 L 0,0 Z">
<animate id="downloading_arrow_fill_clip_animation" fill="freeze" attributeName="d" begin="indefinite" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" values="M 0,0 L 240,0 L 240,0 L 0,0 L 0,0 Z;M 0,0 L 240,0 L 240,240 L 0,240 L 0,0 Z" repeatCount="indefinite" />
</path>
</clipPath>
<g clip-path="url(#downloading_arrow_fill_clip)">
<path id="downloading_arrow_filling" fill="#000" d="M 190,90 c 0,0 -40,0 -40,0 c 0,0 0,-60 0,-60 c 0,0 -60,0 -60,0 c 0,0 0,60 0,60 c 0,0 -40,0 -40,0 c 0,0 70,70 70,70 c 0,0 70,-70 70,-70 Z" />
</g>
<path id="downloading_arrow_fill_clip_debug" d="M 0,0 L 240,0 L 240,240 L 0,240 L 0,0 Z" fill="#F44336" fill-opacity="0.3" style="visibility: hidden;">
<animate id="downloading_arrow_fill_clip_animation_debug" fill="freeze" attributeName="d" begin="indefinite" calcMode="spline" keyTimes="0;1" keySplines="0.4 0 0.2 1" values="M 0,0 L 240,0 L 240,0 L 0,0 L 0,0 Z;M 0,0 L 240,0 L 240,240 L 0,240 L 0,0 Z" repeatCount="indefinite" />
</path>
</g>
</g>
</g>
<g id="downloading_check_arrow_group_translate" transform="translate(94,153)">
<g id="downloading_check_arrow_group_rotate" transform="rotate(45)">
<g transform="translate(-120,-164)">
<path id="downloading_check_arrow_path" fill="#000" fill-opacity="0" d="M 129.12,164 c 0,0 0.88,0 0.88,0 c 0,0 0,-134 0,-134 c 0,0 -20,0 -20,0 c 0,0 -0.1,114.38 -0.1,114.38 c 0,0 -51.8,-0.13 -51.8,-0.13 c 0,0 0.01,19.87 0.01,19.87 c 0,0 68.02,-0.11 68.02,-0.11 c 0,0 2.98,0 2.98,0 Z">
<animate id="downloading_check_arrow_path_animation" begin="indefinite" attributeName="d" calcMode="spline" values="M 129.12,164 c 0,0 0.88,0 0.88,0 c 0,0 0,-134 0,-134 c 0,0 -20,0 -20,0 c 0,0 -0.1,114.38 -0.1,114.38 c 0,0 -51.8,-0.13 -51.8,-0.13 c 0,0 0.01,19.87 0.01,19.87 c 0,0 68.02,-0.11 68.02,-0.11 c 0,0 2.98,0 2.98,0 Z;M 129.12,164 c 0,0 0.88,0 0.88,0 c 0,0 0,-134 0,-134 c 0,0 -20,0 -20,0 c 0,0 -0.1,114.38 -0.1,114.38 c 0,0 0,-0.02 0,-0.02 c 0,0 0.01,19.87 0.01,19.87 c 0,0 18.4,-0.21 18.4,-0.21 c 0,0 0.81,-0.01 0.81,-0.01 Z;M 119.5,164 c 0,0 10.5,0 10.5,0 c 0,0 0,-134 0,-134 c 0,0 -20,0 -20,0 c 0,0 0,134 0,134 c 0,0 9.5,0 9.5,0 c 0,0 0,0 0,0 c 0,0 0,0 0,0 c 0,0 0,0 0,0 Z;M 119.5,90 c 0,0 30.5,0 30.5,0 c 0,0 0,-60 0,-60 c 0,0 -60,0 -60,0 c 0,0 0,60 0,60 c 0,0 29.5,0 29.5,0 c 0,0 0,0 0,0 c 0,0 0,0 0,0 c 0,0 0,0 0,0 Z;M 119.5,90 c 0,0 30.5,0 30.5,0 c 0,0 0,-60 0,-60 c 0,0 -60,0 -60,0 c 0,0 0,60 0,60 c 0,0 29.5,0 29.5,0 c 0,0 0,0 0,0 c 0,0 0,0 0,0 c 0,0 0,0 0,0 Z;M 190,90 c 0,0 -40,0 -40,0 c 0,0 0,-60 0,-60 c 0,0 -60,0 -60,0 c 0,0 0,60 0,60 c 0,0 -40,0 -40,0 c 0,0 70,70 70,70 c 0,0 70,-70 70,-70 c 0,0 0,0 0,0 Z" keyTimes="0; 0.12; 0.14; 0.34; 0.64; 1" keySplines="0.536 0 0.8333 0.73855; 0 0 0.6666 1; 0.2854 0.4477 0.0099875 1; 0 0 0.16846 1; 0.06557 0 0 1" fill="freeze" />
</path>
<path id="downloading_check_arrow_points_path" fill="#e00" fill-opacity="0" style="visibility: hidden;">
<animate id="downloading_check_arrow_points_path_animation" begin="indefinite" attributeName="d" calcMode="spline" keyTimes="0; 0.12; 0.14; 0.34; 0.64; 1" keySplines="0.536 0 0.8333 0.73855; 0 0 0.6666 1; 0.2854 0.4477 0.0099875 1; 0 0 0.16846 1; 0.06557 0 0 1" fill="freeze" />
</path>
</g>
</g>
<animateMotion id="downloading_check_arrow_path_motion_animation" begin="indefinite" calcMode="spline" path="M 0,0 c 4.02083,10.83333 20.66667,12.16667 26.0,11" keyPoints="0;1" keyTimes="0;1" keySplines="0.15324408203 0 0 1;" fill="freeze" />
</g>
</g>
</g>
</g>
</g>
</svg>
<div class="svgDemoCheckboxContainer">
<label for="includes10_showPathPointsCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="includes10_showPathPointsCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Show path coordinates</span>
</label>
<label for="includes10_showTrimPathsCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="includes10_showTrimPathsCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Show trim paths</span>
</label>
<label for="includes10_showClipMaskCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="includes10_showClipMaskCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Show clip paths</span>
</label>
<label for="includes10_slowAnimationCheckbox" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
<input type="checkbox" id="includes10_slowAnimationCheckbox" class="mdl-checkbox__input" />
<span class="mdl-checkbox__label">Slow animation</span>
</label>
</div>
</div>
<p class="mdl-typography--caption mdl-typography--text-center"><strong>Figure 10.</strong> A downloading progress icon animation that demonstrates a combination of several techniques discussed in this blog post. Android source code is available on GitHub: (a) <a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/avd_downloading_begin.xml">in-progress download</a> and (b) <a href="https://github.com/alexjlockwood/adp-delightful-details/blob/master/app/src/main/res/drawable/avd_downloading_finish.xml">download complete</a>. Click the icon to start its animation.</p>
</div>
<p>That’s all I’ve got for now… thanks for reading! Remember to +1 this blog or leave a comment below if you have any questions. And remember that all of the icon animations in this blog post (and more) are available in <code class="highlighter-rouge">AnimatedVectorDrawable</code> format on <a href="https://github.com/alexjlockwood/adp-delightful-details">GitHub</a>. Feel free to steal them for your own application if you want!</p>
<h3 id="reporting-bugs--feedback">Reporting bugs & feedback</h3>
<p>If you notice a glitch in one of the animated demos on this page, please report them <a href="https://github.com/alexjlockwood/alexjlockwood.github.io/issues/new">here</a>. All of the animations work fine for me using the latest version of Chrome. That said, I only began learning JavaScript a few weeks ago so I wouldn’t be surprised if I made a mistake somewhere along the line. I want this blog post to be perfect, so I’d really appreciate it! :)</p>
<h3 id="special-thanks">Special thanks</h3>
<p>I’d like to give a <strong>huge</strong> thanks to <a href="https://twitter.com/crafty">Nick Butcher</a>, because I probably would never have written this blog post without his help and advice! Several of the animations in this blog post were borrowed from his amazing open source application <a href="https://github.com/nickbutcher/plaid">Plaid</a>, which I highly recommend you check out if you haven’t already. I’d also like to thank <a href="https://twitter.com/romannurik">Roman Nurik</a> for his <a href="https://romannurik.github.io/AndroidIconAnimator/">Android Icon Animator</a> tool and for inspiring the path morphing animations in Figure 10. Finally, I’d like to thank <a href="https://sriramramani.wordpress.com/">Sriram Ramani</a> for his <a href="https://sriramramani.wordpress.com/2013/10/14/number-tweening/">blog post on number tweening</a>, which inspired the animated digits demo in Figure 8. Thanks again!</p>
Coloring Buttons w/ ThemeOverlays & Background Tintshttps://www.androiddesignpatterns.com/2016/08/coloring-buttons-with-themeoverlays-background-tints.html2016-08-11T00:00:00+00:002016-08-11T00:00:00+00:00<!--morestart-->
<!--morestart-->
<p>Say you want to change the background color of a <code class="highlighter-rouge">Button</code>.
How can this be done?</p>
<p>This blog post covers two different approaches. In the first approach,
we’ll use AppCompat’s <code class="highlighter-rouge">Widget.AppCompat.Button.Colored</code> style and a custom <code class="highlighter-rouge">ThemeOverlay</code>
to modify the button’s background color directly, and in the second, we’ll use
AppCompat’s built-in background tinting support to achieve an identical effect.</p>
<!--more-->
<h3 id="approach-1-modifying-the-buttons-background-color-w-a-themeoverlay">Approach #1: Modifying the button’s background color w/ a <code class="highlighter-rouge">ThemeOverlay</code></h3>
<p>Before we get too far ahead of ourselves, we should first understand how button
background colors are actually determined. The <a href="http://material.google.com/components/buttons.html">material design spec</a>
has very specific requirements about what a button should look like in both light
and dark themes. How are these requirements met under-the-hood?</p>
<h4 id="the-widgetappcompatbutton-button-styles">The <code class="highlighter-rouge">Widget.AppCompat.Button</code> button styles</h4>
<p>To answer this question, we’ll first need a basic understanding of how
AppCompat determines the default appearance of a standard button.
AppCompat defines a number of styles that can be used to alter
the appearance of a button, each of which extend a base
<a href="https://github.com/android/platform_frameworks_support/blob/3bc41c8f3870bca72a6c52f39a7e66fe967d5e9c/v7/appcompat/res/values/styles_base.xml#L409-L417"><code class="highlighter-rouge">Widget.AppCompat.Button</code></a> style that is applied to all
buttons by default.<sup><a href="#footnote1" id="ref1">1</a></sup>
Specifying a default style to be applied to all views of a certain type is a common technique
used throughout the Android source code. It gives the framework an
opportunity to apply a set of default values for each widget,
encouraging a more consistent user experience. For <code class="highlighter-rouge">Button</code>s, the default
<code class="highlighter-rouge">Widget.AppCompat.Button</code> style ensures that:</p>
<ul>
<li>All buttons share the same default minimum width and minimum height
(<code class="highlighter-rouge">88dp</code> and <code class="highlighter-rouge">48dp</code> respectively, as specified by the
<a href="http://material.google.com/components/buttons.html">material design spec</a>).</li>
<li>All buttons share the same default <code class="highlighter-rouge">TextAppearance</code> (i.e. text displayed in all capital
letters, the same default font family, font size, etc.).</li>
<li>All buttons share the same default button background (i.e. same background
color, same rounded-rectangular shape, same amount of insets and padding, etc.).</li>
</ul>
<p>Great, so the <code class="highlighter-rouge">Widget.AppCompat.Button</code> style helps ensure that all buttons
look roughly the same by default. But how are characteristics such as the button’s
background color chosen in light vs. dark themes, not only in its normal state, but
in its disabled, pressed, and focused states as well? To achieve this, AppCompat depends mainly on
three different theme attributes:</p>
<ul>
<li><a href="https://developer.android.com/reference/android/support/v7/appcompat/R.attr.html#colorButtonNormal"><strong><code class="highlighter-rouge">R.attr.colorButtonNormal</code></strong></a>: The color used as a button’s
background color in
its normal state. Resolves to <code class="highlighter-rouge">#ffd6d7d7</code> for light themes and <code class="highlighter-rouge">#ff5a595b</code> for dark themes.</li>
<li><a href="https://developer.android.com/reference/android/R.attr.html#disabledAlpha"><strong><code class="highlighter-rouge">android.R.attr.disabledAlpha</code></strong></a>: A floating point number that
determines the
alpha values to use for disabled framework widgets. Resolves to <code class="highlighter-rouge">0.26f</code> for light themes
and <code class="highlighter-rouge">0.30f</code> for dark themes.</li>
<li><a href="https://developer.android.com/reference/android/support/v7/appcompat/R.attr.html#colorControlHighlight"><strong><code class="highlighter-rouge">R.attr.colorControlHighlight</code></strong></a>: The translucent overlay color
drawn on top of widgets
when they are pressed and/or focused (used by things like ripples on post-Lollipop devices
and foreground list selectors on pre-Lollipop devices). Resolves to 12% black for light themes
and 20% white for dark themes (<code class="highlighter-rouge">#1f000000</code> and <code class="highlighter-rouge">#33ffffff</code> respectively).</li>
</ul>
<p>That’s a lot to take in for something as simple as changing the background
color of a button! Fortunately, AppCompat handles almost everything for us behind the scenes
by providing a second <a href="https://github.com/android/platform_frameworks_support/blob/3bc41c8f3870bca72a6c52f39a7e66fe967d5e9c/v7/appcompat/res/values/styles_base.xml#L426-L429"><code class="highlighter-rouge">Widget.AppCompat.Button.Colored</code></a>
style that makes altering the background color of a button relatively easy.
As its name suggests, the style extends <code class="highlighter-rouge">Widget.AppCompat.Button</code> and thus
inherits all of the same attributes with one notable exception: the
<a href="https://developer.android.com/reference/android/support/v7/appcompat/R.attr.html#colorAccent"><code class="highlighter-rouge">R.attr.colorAccent</code></a>
theme attribute determines the button’s base background color instead.</p>
<h4 id="creating-custom-themes-using-themeoverlays">Creating custom themes using <code class="highlighter-rouge">ThemeOverlay</code>s</h4>
<p>So now we know that button backgrounds can be customized using the
<code class="highlighter-rouge">Widget.AppCompat.Button.Colored</code> style, but how should we go about customizing the
theme’s accent color? One way we could
update the color pointed to by the <code class="highlighter-rouge">R.attr.colorAccent</code> theme attribute is by modifying the
application’s theme directly. However, this is rarely desirable since most of the
time we only want to change the background color of a single button in our app.
Modifying the theme attribute at the application level will change the
background color of <em>all buttons in the entire application</em>.</p>
<p>Instead, a much better solution is to assign the button its own custom theme in
XML using <code class="highlighter-rouge">android:theme</code> and a <code class="highlighter-rouge">ThemeOverlay</code>. Let’s say we want to change the button’s background
color to Google Red 500. To achieve this, we can define the following theme:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- res/values/themes.xml --></span>
<span class="nt"><style</span> <span class="na">name=</span><span class="s">"RedButtonLightTheme"</span> <span class="na">parent=</span><span class="s">"ThemeOverlay.AppCompat.Light"</span><span class="nt">></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorAccent"</span><span class="nt">></span>@color/googred500<span class="nt"></item></span>
<span class="nt"></style></span>
</code></pre></div></div>
<p>…and set it on our button in the layout XML as follows:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Button</span>
<span class="na">style=</span><span class="s">"@style/Widget.AppCompat.Button.Colored"</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="na">android:theme=</span><span class="s">"@style/RedButtonLightTheme"</span><span class="nt">/></span>
</code></pre></div></div>
<p>And that’s it! You’re probably still wondering what’s up with that weird
<code class="highlighter-rouge">ThemeOverlay</code> though. Unlike the themes we use in our <code class="highlighter-rouge">AndroidManifest.xml</code>
files (i.e. <code class="highlighter-rouge">Theme.AppCompat.Light</code>, <code class="highlighter-rouge">Theme.AppCompat.Dark</code>, etc.),
<code class="highlighter-rouge">ThemeOverlay</code>s define only a small set of material-styled theme attributes that
are most often used when theming each view’s appearance (see the
<a href="https://github.com/android/platform_frameworks_support/blob/d57359e205b2c04a4f0f0ecf9dcb8d6086e75663/v7/appcompat/res/values/themes_base.xml#L551-L604)">source code</a> for a complete list of these attributes).
As a result, they are very useful in cases where you only want to modify one or
two properties of a particular view: just extend the <code class="highlighter-rouge">ThemeOverlay</code>, update the
attributes you want to modify with their new values, and you can be sure that
your view will still inherit all of the correct light/dark themed values that
would have otherwise been used by default.<sup><a href="#footnote2" id="ref2">2</a></sup></p>
<h3 id="approach-2-setting-the-appcompatbuttons-background-tint">Approach #2: Setting the <code class="highlighter-rouge">AppCompatButton</code>’s background tint</h3>
<p>Hopefully you’ve made it this far in the post, because you’ll be happy to know that
there is an <em>even more powerful</em>
way to color a button’s background using a relatively new feature in AppCompat known as
background tinting. You probably know that AppCompat injects its own widgets in
place of many framework
widgets, giving AppCompat greater control over tinting widgets according to the material design
spec even on pre-Lollipop devices. At runtime, <code class="highlighter-rouge">Button</code>s become <code class="highlighter-rouge">AppCompatButton</code>s,
<code class="highlighter-rouge">ImageView</code>s become <code class="highlighter-rouge">AppCompatImageView</code>s, <code class="highlighter-rouge">CheckBox</code>s become <code class="highlighter-rouge">AppCompatCheckBox</code>s,
and so on and so forth. What you may not know is that any
AppCompat widget that implements the <a href="https://developer.android.com/reference/android/support/v4/view/TintableBackgroundView.html"><code class="highlighter-rouge">TintableBackgroundView</code></a>
interface can have its background tint color changed by declaring a <code class="highlighter-rouge">ColorStateList</code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- res/color/btn_colored_background_tint.xml --></span>
<span class="nt"><selector</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span><span class="nt">></span>
<span class="c"><!-- Disabled state. --></span>
<span class="nt"><item</span> <span class="na">android:state_enabled=</span><span class="s">"false"</span>
<span class="na">android:color=</span><span class="s">"?attr/colorButtonNormal"</span>
<span class="na">android:alpha=</span><span class="s">"?android:attr/disabledAlpha"</span><span class="nt">/></span>
<span class="c"><!-- Enabled state. --></span>
<span class="nt"><item</span> <span class="na">android:color=</span><span class="s">"?attr/colorAccent"</span><span class="nt">/></span>
<span class="nt"></selector></span>
</code></pre></div></div>
<p>…and either setting it in the layout XML:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><android.support.v7.widget.AppCompatButton</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="na">app:backgroundTint=</span><span class="s">"@color/btn_colored_background_tint"</span><span class="nt">/></span>
</code></pre></div></div>
<p>…or programatically via the
<a href="https://developer.android.com/reference/android/support/v4/view/ViewCompat.html#setBackgroundTintList(android.view.View, android.content.res.ColorStateList)"><code class="highlighter-rouge">ViewCompat#setBackgroundTintList(View, ColorStateList)</code></a>
method:<sup><a href="#footnote3" id="ref3">3</a></sup></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="n">ColorStateList</span> <span class="n">backgroundTintList</span> <span class="o">=</span>
<span class="n">AppCompatResources</span><span class="o">.</span><span class="na">getColorStateList</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">btn_colored_background_tint</span><span class="o">);</span>
<span class="n">ViewCompat</span><span class="o">.</span><span class="na">setBackgroundTintList</span><span class="o">(</span><span class="n">button</span><span class="o">,</span> <span class="n">backgroundTintList</span><span class="o">);</span>
</code></pre></div></div>
<p>While this approach to coloring a button is much more powerful in the sense that it can be
done entirely programatically (whereas <code class="highlighter-rouge">ThemeOverlay</code>s must be defined in XML and cannot
be constructed at
runtime), it also requires a bit more work on our end if we want to ensure our button exactly meets
the material design spec. Let’s create a simple <code class="highlighter-rouge">BackgroundTints</code> utility class that makes
it quick and easy to construct colored background tint lists:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Utility class for creating background tint {@link ColorStateList}s.
*/</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">BackgroundTints</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">DISABLED_STATE_SET</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[]{-</span><span class="n">android</span><span class="o">.</span><span class="na">R</span><span class="o">.</span><span class="na">attr</span><span class="o">.</span><span class="na">state_enabled</span><span class="o">};</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">PRESSED_STATE_SET</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[]{</span><span class="n">android</span><span class="o">.</span><span class="na">R</span><span class="o">.</span><span class="na">attr</span><span class="o">.</span><span class="na">state_pressed</span><span class="o">};</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">FOCUSED_STATE_SET</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[]{</span><span class="n">android</span><span class="o">.</span><span class="na">R</span><span class="o">.</span><span class="na">attr</span><span class="o">.</span><span class="na">state_focused</span><span class="o">};</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">EMPTY_STATE_SET</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="mi">0</span><span class="o">];</span>
<span class="cm">/**
* Returns a {@link ColorStateList} that can be used as a colored button's background tint.
* Note that this code makes use of the {@code android.support.v4.graphics.ColorUtils}
* utility class.
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">ColorStateList</span> <span class="nf">forColoredButton</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nd">@ColorInt</span> <span class="kt">int</span> <span class="n">backgroundColor</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// On pre-Lollipop devices, we need 4 states total (disabled, pressed, focused, and default).</span>
<span class="c1">// On post-Lollipop devices, we need 2 states total (disabled and default). The button's</span>
<span class="c1">// RippleDrawable will animate the pressed and focused state changes for us automatically.</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">numStates</span> <span class="o">=</span> <span class="n">Build</span><span class="o">.</span><span class="na">VERSION</span><span class="o">.</span><span class="na">SDK_INT</span> <span class="o"><</span> <span class="n">Build</span><span class="o">.</span><span class="na">VERSION_CODES</span><span class="o">.</span><span class="na">LOLLIPOP</span> <span class="o">?</span> <span class="mi">4</span> <span class="o">:</span> <span class="mi">2</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">int</span><span class="o">[][]</span> <span class="n">states</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="n">numStates</span><span class="o">][];</span>
<span class="kd">final</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">colors</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[</span><span class="n">numStates</span><span class="o">];</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="n">states</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">DISABLED_STATE_SET</span><span class="o">;</span>
<span class="n">colors</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">getDisabledButtonBackgroundColor</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
<span class="n">i</span><span class="o">++;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">Build</span><span class="o">.</span><span class="na">VERSION</span><span class="o">.</span><span class="na">SDK_INT</span> <span class="o"><</span> <span class="n">Build</span><span class="o">.</span><span class="na">VERSION_CODES</span><span class="o">.</span><span class="na">LOLLIPOP</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">highlightedBackgroundColor</span> <span class="o">=</span> <span class="n">getHighlightedBackgroundColor</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">backgroundColor</span><span class="o">);</span>
<span class="n">states</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">PRESSED_STATE_SET</span><span class="o">;</span>
<span class="n">colors</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">highlightedBackgroundColor</span><span class="o">;</span>
<span class="n">i</span><span class="o">++;</span>
<span class="n">states</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">FOCUSED_STATE_SET</span><span class="o">;</span>
<span class="n">colors</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">highlightedBackgroundColor</span><span class="o">;</span>
<span class="n">i</span><span class="o">++;</span>
<span class="o">}</span>
<span class="n">states</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">EMPTY_STATE_SET</span><span class="o">;</span>
<span class="n">colors</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">backgroundColor</span><span class="o">;</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ColorStateList</span><span class="o">(</span><span class="n">states</span><span class="o">,</span> <span class="n">colors</span><span class="o">);</span>
<span class="o">}</span>
<span class="cm">/**
* Returns the theme-dependent ARGB background color to use for disabled buttons.
*/</span>
<span class="nd">@ColorInt</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">getDisabledButtonBackgroundColor</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Extract the disabled alpha to apply to the button using the context's theme.</span>
<span class="c1">// (0.26f for light themes and 0.30f for dark themes).</span>
<span class="kd">final</span> <span class="n">TypedValue</span> <span class="n">tv</span> <span class="o">=</span> <span class="k">new</span> <span class="n">TypedValue</span><span class="o">();</span>
<span class="n">context</span><span class="o">.</span><span class="na">getTheme</span><span class="o">().</span><span class="na">resolveAttribute</span><span class="o">(</span><span class="n">android</span><span class="o">.</span><span class="na">R</span><span class="o">.</span><span class="na">attr</span><span class="o">.</span><span class="na">disabledAlpha</span><span class="o">,</span> <span class="n">tv</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">float</span> <span class="n">disabledAlpha</span> <span class="o">=</span> <span class="n">tv</span><span class="o">.</span><span class="na">getFloat</span><span class="o">();</span>
<span class="c1">// Use the disabled alpha factor and the button's default normal color</span>
<span class="c1">// to generate the button's disabled background color.</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">colorButtonNormal</span> <span class="o">=</span> <span class="n">getThemeAttrColor</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">attr</span><span class="o">.</span><span class="na">colorButtonNormal</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">originalAlpha</span> <span class="o">=</span> <span class="n">Color</span><span class="o">.</span><span class="na">alpha</span><span class="o">(</span><span class="n">colorButtonNormal</span><span class="o">);</span>
<span class="k">return</span> <span class="n">ColorUtils</span><span class="o">.</span><span class="na">setAlphaComponent</span><span class="o">(</span>
<span class="n">colorButtonNormal</span><span class="o">,</span> <span class="n">Math</span><span class="o">.</span><span class="na">round</span><span class="o">(</span><span class="n">originalAlpha</span> <span class="o">*</span> <span class="n">disabledAlpha</span><span class="o">));</span>
<span class="o">}</span>
<span class="cm">/**
* Returns the theme-dependent ARGB color that results when colorControlHighlight is drawn
* on top of the provided background color.
*/</span>
<span class="nd">@ColorInt</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">getHighlightedBackgroundColor</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nd">@ColorInt</span> <span class="kt">int</span> <span class="n">backgroundColor</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">colorControlHighlight</span> <span class="o">=</span> <span class="n">getThemeAttrColor</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">attr</span><span class="o">.</span><span class="na">colorControlHighlight</span><span class="o">);</span>
<span class="k">return</span> <span class="n">ColorUtils</span><span class="o">.</span><span class="na">compositeColors</span><span class="o">(</span><span class="n">colorControlHighlight</span><span class="o">,</span> <span class="n">backgroundColor</span><span class="o">);</span>
<span class="o">}</span>
<span class="cm">/** Returns the theme-dependent ARGB color associated with the provided theme attribute. */</span>
<span class="nd">@ColorInt</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">getThemeAttrColor</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nd">@AttrRes</span> <span class="kt">int</span> <span class="n">attr</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">TypedArray</span> <span class="n">array</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">obtainStyledAttributes</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[]{</span><span class="n">attr</span><span class="o">});</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">array</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="n">array</span><span class="o">.</span><span class="na">recycle</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nf">BackgroundTints</span><span class="o">()</span> <span class="o">{}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Using this class, we can then simply apply the background tint to the button programatically using:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ViewCompat</span><span class="o">.</span><span class="na">setBackgroundTintList</span><span class="o">(</span>
<span class="n">button</span><span class="o">,</span> <span class="n">BackgroundTints</span><span class="o">.</span><span class="na">forColoredButton</span><span class="o">(</span><span class="n">button</span><span class="o">.</span><span class="na">getContext</span><span class="o">(),</span> <span class="n">backgroundColor</span><span class="o">);</span>
</code></pre></div></div>
<h3 id="pop-quiz">Pop quiz!</h3>
<p>Let’s test our knowledge of how this all works with a simple example.
Consider a sample app that sets the following theme in its <code class="highlighter-rouge">AndroidManifest.xml</code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- res/values/themes.xml --></span>
<span class="nt"><style</span> <span class="na">name=</span><span class="s">"AppTheme"</span> <span class="na">parent=</span><span class="s">"Theme.AppCompat.Light.DarkActionBar"</span><span class="nt">></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorPrimary"</span><span class="nt">></span>@color/indigo500<span class="nt"></item></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorPrimaryDark"</span><span class="nt">></span>@color/indigo700<span class="nt"></item></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorAccent"</span><span class="nt">></span>@color/pinkA200<span class="nt"></item></span>
<span class="nt"></style></span>
</code></pre></div></div>
<p>In addition to this, the following custom themes are declared as well:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- res/values/themes.xml --></span>
<span class="nt"><style</span> <span class="na">name=</span><span class="s">"RedButtonLightTheme"</span> <span class="na">parent=</span><span class="s">"ThemeOverlay.AppCompat.Light"</span><span class="nt">></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorAccent"</span><span class="nt">></span>@color/googred500<span class="nt"></item></span>
<span class="nt"></style></span>
<span class="nt"><style</span> <span class="na">name=</span><span class="s">"RedButtonDarkTheme"</span> <span class="na">parent=</span><span class="s">"ThemeOverlay.AppCompat.Dark"</span><span class="nt">></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorAccent"</span><span class="nt">></span>@color/googred500<span class="nt"></item></span>
<span class="nt"></style></span>
</code></pre></div></div>
<p>What will the following XML look like in the on API 19 and API 23 devices
when the buttons are put in default, pressed, and disabled states?</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><LinearLayout</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="nt"><Button</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"><Button</span>
<span class="na">style=</span><span class="s">"@style/Widget.AppCompat.Button.Colored"</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"><Button</span>
<span class="na">style=</span><span class="s">"@style/Widget.AppCompat.Button.Colored"</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="na">android:theme=</span><span class="s">"@style/RedButtonLightTheme"</span><span class="nt">/></span>
<span class="nt"><Button</span>
<span class="na">android:id=</span><span class="s">"@+id/button4"</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"><Button</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="na">android:theme=</span><span class="s">"@style/ThemeOverlay.AppCompat.Dark"</span><span class="nt">/></span>
<span class="nt"><Button</span>
<span class="na">style=</span><span class="s">"@style/Widget.AppCompat.Button.Colored"</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="na">android:theme=</span><span class="s">"@style/ThemeOverlay.AppCompat.Dark"</span><span class="nt">/></span>
<span class="nt"><Button</span>
<span class="na">style=</span><span class="s">"@style/Widget.AppCompat.Button.Colored"</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="na">android:theme=</span><span class="s">"@style/RedButtonDarkTheme"</span><span class="nt">/></span>
<span class="nt"><Button</span>
<span class="na">android:id=</span><span class="s">"@+id/button8"</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="na">android:theme=</span><span class="s">"@style/ThemeOverlay.AppCompat.Dark"</span><span class="nt">/></span>
<span class="nt"></LinearLayout></span>
</code></pre></div></div>
<p>Assume that background tints are set programatically
on the 4th and 8th buttons as follows:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kt">int</span> <span class="n">googRed500</span> <span class="o">=</span> <span class="n">ContextCompat</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="n">activity</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">googred500</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">View</span> <span class="n">button4</span> <span class="o">=</span> <span class="n">activity</span><span class="o">.</span><span class="na">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">button4</span><span class="o">);</span>
<span class="n">ViewCompat</span><span class="o">.</span><span class="na">setBackgroundTintList</span><span class="o">(</span>
<span class="n">button4</span><span class="o">,</span> <span class="n">BackgroundTints</span><span class="o">.</span><span class="na">forColoredButton</span><span class="o">(</span><span class="n">button4</span><span class="o">.</span><span class="na">getContext</span><span class="o">(),</span> <span class="n">googRed500</span><span class="o">));</span>
<span class="kd">final</span> <span class="n">View</span> <span class="n">button8</span> <span class="o">=</span> <span class="n">activity</span><span class="o">.</span><span class="na">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">button8</span><span class="o">);</span>
<span class="n">ViewCompat</span><span class="o">.</span><span class="na">setBackgroundTintList</span><span class="o">(</span>
<span class="n">button8</span><span class="o">,</span> <span class="n">BackgroundTints</span><span class="o">.</span><span class="na">forColoredButton</span><span class="o">(</span><span class="n">button8</span><span class="o">.</span><span class="na">getContext</span><span class="o">(),</span> <span class="n">googRed500</span><span class="o">));</span>
</code></pre></div></div>
<h4 id="solutions">Solutions</h4>
<p>See the below links to view screenshots of the solutions:</p>
<ul>
<li><a href="/assets/images/posts/2016/08/11/themed-buttons-enabled-unpressed-19.png">API 19, default state</a></li>
<li><a href="/assets/images/posts/2016/08/11/themed-buttons-enabled-pressed-19.png">API 19, pressed state</a></li>
<li><a href="/assets/images/posts/2016/08/11/themed-buttons-disabled-19.png">API 19, disabled state</a></li>
<li><a href="/assets/images/posts/2016/08/11/themed-buttons-enabled-unpressed-23.png">API 23, default state</a></li>
<li><a href="/assets/images/posts/2016/08/11/themed-buttons-enabled-pressed-23.png">API 23, pressed state</a></li>
<li><a href="/assets/images/posts/2016/08/11/themed-buttons-disabled-23.png">API 23, disabled state</a></li>
</ul>
<p>(Note that the incorrect disabled text color in the screenshots is a <a href="https://code.google.com/p/android/issues/detail?id=219276">known issue</a>
and will be fixed in an upcoming version of the support library.)</p>
<p>As always, thanks for reading! Feel free to leave a comment if you have any questions,
and don’t forget to +1 and/or share this blog post if you found it helpful! And check out the
<a href="https://github.com/alexjlockwood/adp-theming-buttons-with-themeoverlays">source code for these examples on GitHub</a> as well!</p>
<hr class="footnote-divider" />
<p><sup id="footnote1">1</sup> Just in case you don’t believe me, the default style applied to an <code class="highlighter-rouge">AppCompatButton</code>s
is the style pointed to by the <a href="https://github.com/android/platform_frameworks_support/blob/c1e65b3f856d8c559e04857949a79ab2fac7095b/v7/appcompat/src/android/support/v7/widget/AppCompatButton.java#L60"><code class="highlighter-rouge">R.attr.buttonStyle</code></a> theme attribute, which points to the
<code class="highlighter-rouge">Widget.AppCompat.Button</code> style <a href="https://github.com/android/platform_frameworks_support/blob/c1e65b3f856d8c559e04857949a79ab2fac7095b/v7/appcompat/res/values/themes_base.xml#L237">here</a>. Check out
<a href="http://blog.danlew.net/2016/07/19/a-deep-dive-into-android-view-constructors/">Dan Lew’s great blog post</a> for more information about default styles in
Android. <a href="#ref1" title="Jump to footnote 1.">↩</a></p>
<p><sup id="footnote2">2</sup> <code class="highlighter-rouge">ThemeOverlay</code>s aren’t only useful for changing your
theme’s accent color. They can be used to alter any theme attribute you want!
For example, you could use one to customize the
color of an <code class="highlighter-rouge">RecyclerView</code>’s overscroll ripple by modifying the color of the
<code class="highlighter-rouge">android.R.attr.colorEdgeEffect</code> theme
attribute. Check out <a href="https://medium.com/google-developers/theming-with-appcompat-1a292b754b35#.ebo3ua3bu">this Medium post</a> and this
<a href="https://plus.google.com/+AndroidDevelopers/posts/JXHKyhsWHAH">Google+ pro tip</a> for more information
about <code class="highlighter-rouge">ThemeOverlay</code>s. <a href="#ref2" title="Jump to footnote 2">↩</a></p>
<p><sup id="footnote3">3</sup> Note that AppCompat widgets do not expose a <code class="highlighter-rouge">setBackgroundTintList()</code>
methods as part of their public API. Clients <em>must</em> use the <code class="highlighter-rouge">ViewCompat#setBackgroundTintList()</code>
static helper methods to modify background tints programatically. Also note that using the
<a href="https://developer.android.com/reference/android/support/v7/content/res/AppCompatResources.html"><code class="highlighter-rouge">AppCompatResources</code></a>
class to inflate the <code class="highlighter-rouge">ColorStateList</code> is important here. Check out <a href="/2016/08/contextcompat-getcolor-getdrawable.html">my previous blog post</a>
for more detailed information on that
topic. <a href="#ref3" title="Jump to footnote 3.">↩</a></p>
Styling Colors & Drawables w/ Theme Attributeshttps://www.androiddesignpatterns.com/2016/08/contextcompat-getcolor-getdrawable.html2016-08-07T00:00:00+00:002016-08-07T00:00:00+00:00<!--morestart-->
<!--morestart-->
<p>You’ve probably noticed that when you write something like:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">context</span><span class="o">.</span><span class="na">getResources</span><span class="o">().</span><span class="na">getColor</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">some_color_resource_id</span><span class="o">);</span>
</code></pre></div></div>
<p>Android Studio will give you a lint message warning you that the
<code class="highlighter-rouge">Resources#getColor(int)</code> method was deprecated in Marshmallow in favor of the
new, <code class="highlighter-rouge">Theme</code>-aware <code class="highlighter-rouge">Resources#getColor(int, Theme)</code> method. You also
probably know by now that the easy alternative to avoiding this lint warning
these days is to call:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ContextCompat</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">some_color_resource_id</span><span class="o">);</span>
</code></pre></div></div>
<p>which under-the-hood is essentially just a shorthand way of writing:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">Build</span><span class="o">.</span><span class="na">VERSION</span><span class="o">.</span><span class="na">SDK_INT</span> <span class="o">>=</span> <span class="n">Build</span><span class="o">.</span><span class="na">VERSION_CODES</span><span class="o">.</span><span class="na">M</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">context</span><span class="o">.</span><span class="na">getResources</span><span class="o">().</span><span class="na">getColor</span><span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="n">context</span><span class="o">.</span><span class="na">getTheme</span><span class="o">());</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">context</span><span class="o">.</span><span class="na">getResources</span><span class="o">().</span><span class="na">getColor</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Easy enough. But what is actually going on here? Why were these methods
deprecated in the first place and what do the new <code class="highlighter-rouge">Theme</code>-aware methods have to
offer that didn’t exist before?</p>
<!--more-->
<h3 id="the-problem-with-resourcesgetcolorint--resourcesgetcolorstatelistint">The problem with <code class="highlighter-rouge">Resources#getColor(int)</code> & <code class="highlighter-rouge">Resources#getColorStateList(int)</code></h3>
<p>First, let’s be clear on what these old, deprecated methods actually do:</p>
<ul>
<li>
<p><a href="http://developer.android.com/reference/android/content/res/Resources.html#getColor(int)"><code class="highlighter-rouge">Resources#getColor(int)</code></a>
returns the color associated with the passed in color resource ID. If the resource
ID points to a <code class="highlighter-rouge">ColorStateList</code>, the method will return the <code class="highlighter-rouge">ColorStateList</code>’s
<a href="http://developer.android.com/reference/android/content/res/ColorStateList.html#getDefaultColor()">default color</a>.</p>
</li>
<li>
<p><a href="http://developer.android.com/reference/android/content/res/Resources.html#getColorStateList(int)"><code class="highlighter-rouge">Resources#getColorStateList(int)</code></a>
returns the <code class="highlighter-rouge">ColorStateList</code> associated with the passed in resource ID.</p>
</li>
</ul>
<h4 id="when-will-these-two-methods-break-my-code">“When will these two methods break my code?”</h4>
<p>To understand why these methods were deprecated in the first place, consider the
<code class="highlighter-rouge">ColorStateList</code> declared in XML below. When this <code class="highlighter-rouge">ColorStateList</code> is applied to a
<code class="highlighter-rouge">TextView</code>, its disabled and enabled text colors should take on the colors pointed to by the
<code class="highlighter-rouge">R.attr.colorAccent</code> and <code class="highlighter-rouge">R.attr.colorPrimary</code> theme attributes respectively:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- res/colors/button_text_csl.xml --></span>
<span class="nt"><selector</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span><span class="nt">></span>
<span class="nt"><item</span> <span class="na">android:color=</span><span class="s">"?attr/colorAccent"</span> <span class="na">android:state_enabled=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"><item</span> <span class="na">android:color=</span><span class="s">"?attr/colorPrimary"</span><span class="nt">/></span>
<span class="nt"></selector></span>
</code></pre></div></div>
<p>Now let’s say you want to obtain an instance of this <code class="highlighter-rouge">ColorStateList</code> programatically:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ColorStateList</span> <span class="n">csl</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">getResources</span><span class="o">().</span><span class="na">getColorStateList</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">button_text_csl</span><span class="o">);</span>
</code></pre></div></div>
<p>Perhaps surprisingly, <strong>the result of the above call is undefined!</strong>
You’ll see a stack trace in your logcat output similar to the one below:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>W/Resources: ColorStateList color/button_text_csl has unresolved theme attributes!
Consider using Resources.getColorStateList(int, Theme)
or Context.getColorStateList(int)
at android.content.res.Resources.getColorStateList(Resources.java:1011)
...
</code></pre></div></div>
<h4 id="what-went-wrong">“What went wrong?”</h4>
<p>The problem is that <code class="highlighter-rouge">Resources</code> objects are not intrinsically linked to a specific
<code class="highlighter-rouge">Theme</code> in your app, and as a result, they will be unable to resolve the values pointed to by
theme attributes such as <code class="highlighter-rouge">R.attr.colorAccent</code> and <code class="highlighter-rouge">R.attr.colorPrimary</code> on their own. In fact,
specifying theme attributes in <code class="highlighter-rouge">ColorStateList</code> XML files <em>was not supported until API 23</em>,
which introduced two new methods for extracting <code class="highlighter-rouge">ColorStateList</code>s from XML:</p>
<ul>
<li>
<p><a href="http://developer.android.com/reference/android/content/res/Resources.html#getColor(int, android.content.res.Resources.Theme)"><code class="highlighter-rouge">Resources#getColor(int, Theme)</code></a>
returns the color associated with the passed in resource ID. If the resource
ID points to a <code class="highlighter-rouge">ColorStateList</code>, the method will return the <code class="highlighter-rouge">ColorStateList</code>’s
<a href="http://developer.android.com/reference/android/content/res/ColorStateList.html#getDefaultColor()">default color</a>.
Any theme attributes specified in the <code class="highlighter-rouge">ColorStateList</code> will be
resolved using the passed in <code class="highlighter-rouge">Theme</code> argument.</p>
</li>
<li>
<p><a href="http://developer.android.com/reference/android/content/res/Resources.html#getColorStateList(int, android.content.res.Resources.Theme)"><code class="highlighter-rouge">Resources#getColorStateList(int, Theme)</code></a>
returns the <code class="highlighter-rouge">ColorStateList</code> associated with the passed in resource ID. Any
theme attributes specified in the <code class="highlighter-rouge">ColorStateList</code> will be resolved using
the passed in <code class="highlighter-rouge">Theme</code> argument.</p>
</li>
</ul>
<p>Additional convenience methods were also added to <a href="http://developer.android.com/reference/android/content/Context.html"><code class="highlighter-rouge">Context</code></a> and to the support
library’s <a href="http://developer.android.com/reference/android/support/v4/content/res/ResourcesCompat.html"><code class="highlighter-rouge">ResourcesCompat</code></a> and <a href="http://developer.android.com/reference/android/support/v4/content/ContextCompat.html"><code class="highlighter-rouge">ContextCompat</code></a> classes as well.</p>
<h4 id="that-stinks-how-can-i-workaround-these-problems">“That stinks! How can I workaround these problems?”</h4>
<p>As of v24.0 of the AppCompat support library, you can now workaround all of these
problems using the new <a href="http://developer.android.com/reference/android/support/v7/content/res/AppCompatResources.html"><code class="highlighter-rouge">AppCompatResources</code></a>
class! To extract a themed <code class="highlighter-rouge">ColorStateList</code> from XML, just use:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ColorStateList</span> <span class="n">csl</span> <span class="o">=</span> <span class="n">AppCompatResources</span><span class="o">.</span><span class="na">getColorStateList</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">button_text_csl</span><span class="o">);</span>
</code></pre></div></div>
<p>On API 23+, AppCompat will delegate the call to the corresponding framework method,
and on earlier platforms it will manually parse the XML itself, resolving any
theme attributes it encounters along the way. If that isn’t enough, it also
backports the <code class="highlighter-rouge">ColorStateList</code>’s new
<a href="http://developer.android.com/reference/android/content/res/ColorStateList.html#attr_android:alpha"><code class="highlighter-rouge">android:alpha</code></a>
attribute as well (which was previously only available to devices running API 23 and above)!</p>
<h3 id="the-problem-with-resourcesgetdrawableint">The problem with <code class="highlighter-rouge">Resources#getDrawable(int)</code></h3>
<p>You guessed it! The recently deprecated <a href="http://developer.android.com/reference/android/content/res/Resources.html#getDrawable(int)"><code class="highlighter-rouge">Resources#getDrawable(int)</code></a> method shares
pretty much the exact same problem as the <code class="highlighter-rouge">Resources#getColor(int)</code> and
<code class="highlighter-rouge">Resources#getColorStateList(int)</code> methods discussed above.
As a result, theme attributes in
drawable XML files will not resolve properly prior to API 21, so if
your app supports pre-Lollipop devices, either avoid theme attributes entirely
or resolve them in your Java code and construct the <code class="highlighter-rouge">Drawable</code>
programatically instead.</p>
<h4 id="i-dont-believe-you-are-there-really-no-exceptions">“I don’t believe you! Are there really no exceptions?”</h4>
<p>Of course there is an exception, isn’t there always? :)</p>
<p>It turns out that similar to the <code class="highlighter-rouge">AppCompatResources</code> class, the <a href="http://developer.android.com/reference/android/support/graphics/drawable/VectorDrawableCompat.html"><code class="highlighter-rouge">VectorDrawableCompat</code></a>
and <a href="http://developer.android.com/reference/android/support/graphics/drawable/AnimatedVectorDrawableCompat.html"><code class="highlighter-rouge">AnimatedVectorDrawableCompat</code></a>
classes were able to workaround
these issues and are actually smart enough to resolve the theme
attributes it detects in XML <em>across all platform versions</em> as well. For example,
if you want to color your <code class="highlighter-rouge">VectorDrawableCompat</code> the standard shade of grey,
you can reliably tint the drawable with <code class="highlighter-rouge">?attr/colorControlNormal</code>
while still maintaining backwards compatibility with older platform versions:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><vector</span>
<span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">android:width=</span><span class="s">"24dp"</span>
<span class="na">android:height=</span><span class="s">"24dp"</span>
<span class="na">android:viewportWidth=</span><span class="s">"24.0"</span>
<span class="na">android:viewportHeight=</span><span class="s">"24.0"</span>
<span class="na">android:tint=</span><span class="s">"?attr/colorControlNormal"</span><span class="nt">></span>
<span class="nt"><path</span>
<span class="na">android:pathData=</span><span class="s">"..."</span>
<span class="na">android:fillColor=</span><span class="s">"@android:color/white"</span><span class="nt">/></span>
<span class="nt"></vector></span>
</code></pre></div></div>
<p>(If you’re curious how this is implemented under-the-hood, the short answer is that
the support library does their own custom XML parsing and uses the
<a href="http://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet, int[], int, int)"><code class="highlighter-rouge">Theme#obtainStyledAttributes(AttributeSet, int[], int, int)</code></a>
method to resolve the theme attributes it encounters. Pretty cool!)</p>
<h3 id="pop-quiz">Pop quiz!</h3>
<p>Let’s test our knowledge with a short example. Consider the following <code class="highlighter-rouge">ColorStateList</code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- res/colors/button_text_csl.xml --></span>
<span class="nt"><selector</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span><span class="nt">></span>
<span class="nt"><item</span> <span class="na">android:color=</span><span class="s">"?attr/colorAccent"</span> <span class="na">android:state_enabled=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"><item</span> <span class="na">android:color=</span><span class="s">"?attr/colorPrimary"</span><span class="nt">/></span>
<span class="nt"></selector></span>
</code></pre></div></div>
<p>And assume you’re writing an app that declares the following themes:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- res/values/themes.xml --></span>
<span class="nt"><style</span> <span class="na">name=</span><span class="s">"AppTheme"</span> <span class="na">parent=</span><span class="s">"Theme.AppCompat.Light.DarkActionBar"</span><span class="nt">></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorPrimary"</span><span class="nt">></span>@color/vanillared500<span class="nt"></item></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorPrimaryDark"</span><span class="nt">></span>@color/vanillared700<span class="nt"></item></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorAccent"</span><span class="nt">></span>@color/googgreen500<span class="nt"></item></span>
<span class="nt"></style></span>
<span class="nt"><style</span> <span class="na">name=</span><span class="s">"CustomButtonTheme"</span> <span class="na">parent=</span><span class="s">"ThemeOverlay.AppCompat.Light"</span><span class="nt">></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorPrimary"</span><span class="nt">></span>@color/brown500<span class="nt"></item></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"colorAccent"</span><span class="nt">></span>@color/yellow900<span class="nt"></item></span>
<span class="nt"></style></span>
</code></pre></div></div>
<p>And finally, assume that you have the following helper methods to resolve theme
attributes and construct <code class="highlighter-rouge">ColorStateList</code>s programatically:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ColorInt</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">getThemeAttrColor</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nd">@AttrRes</span> <span class="kt">int</span> <span class="n">colorAttr</span><span class="o">)</span> <span class="o">{</span>
<span class="n">TypedArray</span> <span class="n">array</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">obtainStyledAttributes</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[]{</span><span class="n">colorAttr</span><span class="o">});</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">array</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="n">array</span><span class="o">.</span><span class="na">recycle</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="n">ColorStateList</span> <span class="nf">createColorStateList</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ColorStateList</span><span class="o">(</span>
<span class="k">new</span> <span class="kt">int</span><span class="o">[][]{</span>
<span class="k">new</span> <span class="kt">int</span><span class="o">[]{-</span><span class="n">android</span><span class="o">.</span><span class="na">R</span><span class="o">.</span><span class="na">attr</span><span class="o">.</span><span class="na">state_enabled</span><span class="o">},</span> <span class="c1">// Disabled state.</span>
<span class="n">StateSet</span><span class="o">.</span><span class="na">WILD_CARD</span><span class="o">,</span> <span class="c1">// Enabled state.</span>
<span class="o">},</span>
<span class="k">new</span> <span class="kt">int</span><span class="o">[]{</span>
<span class="n">getThemeAttrColor</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">attr</span><span class="o">.</span><span class="na">colorAccent</span><span class="o">),</span> <span class="c1">// Disabled state.</span>
<span class="n">getThemeAttrColor</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">attr</span><span class="o">.</span><span class="na">colorPrimary</span><span class="o">),</span> <span class="c1">// Enabled state.</span>
<span class="o">});</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Try to see if you can predict the enabled and disabled appearance of a button on
both an API 19 and API 23 device in each of the following scenarios (for
scenarios #5 and #8, assume that the button has been given a custom theme in XML
using <code class="highlighter-rouge">android:theme="@style/CustomButtonTheme"</code>):</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Resources</span> <span class="n">res</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">.</span><span class="na">getResources</span><span class="o">();</span>
<span class="c1">// (1)</span>
<span class="kt">int</span> <span class="n">deprecatedTextColor</span> <span class="o">=</span> <span class="n">res</span><span class="o">.</span><span class="na">getColor</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">button_text_csl</span><span class="o">);</span>
<span class="n">button1</span><span class="o">.</span><span class="na">setTextColor</span><span class="o">(</span><span class="n">deprecatedTextColor</span><span class="o">);</span>
<span class="c1">// (2)</span>
<span class="n">ColorStateList</span> <span class="n">deprecatedTextCsl</span> <span class="o">=</span> <span class="n">res</span><span class="o">.</span><span class="na">getColorStateList</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">button_text_csl</span><span class="o">);</span>
<span class="n">button2</span><span class="o">.</span><span class="na">setTextColor</span><span class="o">(</span><span class="n">deprecatedTextCsl</span><span class="o">);</span>
<span class="c1">// (3)</span>
<span class="kt">int</span> <span class="n">textColorXml</span> <span class="o">=</span>
<span class="n">AppCompatResources</span><span class="o">.</span><span class="na">getColorStateList</span><span class="o">(</span><span class="n">ctx</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">button_text_csl</span><span class="o">).</span><span class="na">getDefaultColor</span><span class="o">();</span>
<span class="n">button3</span><span class="o">.</span><span class="na">setTextColor</span><span class="o">(</span><span class="n">textColorXml</span><span class="o">);</span>
<span class="c1">// (4)</span>
<span class="n">ColorStateList</span> <span class="n">textCslXml</span> <span class="o">=</span> <span class="n">AppCompatResources</span><span class="o">.</span><span class="na">getColorStateList</span><span class="o">(</span><span class="n">ctx</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">button_text_csl</span><span class="o">);</span>
<span class="n">button4</span><span class="o">.</span><span class="na">setTextColor</span><span class="o">(</span><span class="n">textCslXml</span><span class="o">);</span>
<span class="c1">// (5)</span>
<span class="n">Context</span> <span class="n">themedCtx</span> <span class="o">=</span> <span class="n">button5</span><span class="o">.</span><span class="na">getContext</span><span class="o">();</span>
<span class="n">ColorStateList</span> <span class="n">textCslXmlWithCustomTheme</span> <span class="o">=</span>
<span class="n">AppCompatResources</span><span class="o">.</span><span class="na">getColorStateList</span><span class="o">(</span><span class="n">themedCtx</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">color</span><span class="o">.</span><span class="na">button_text_csl</span><span class="o">);</span>
<span class="n">button5</span><span class="o">.</span><span class="na">setTextColor</span><span class="o">(</span><span class="n">textCslXmlWithCustomTheme</span><span class="o">);</span>
<span class="c1">// (6)</span>
<span class="kt">int</span> <span class="n">textColorJava</span> <span class="o">=</span> <span class="n">getThemeAttrColor</span><span class="o">(</span><span class="n">ctx</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">attr</span><span class="o">.</span><span class="na">colorPrimary</span><span class="o">);</span>
<span class="n">button6</span><span class="o">.</span><span class="na">setTextColor</span><span class="o">(</span><span class="n">textColorJava</span><span class="o">);</span>
<span class="c1">// (7)</span>
<span class="n">ColorStateList</span> <span class="n">textCslJava</span> <span class="o">=</span> <span class="n">createColorStateList</span><span class="o">(</span><span class="n">ctx</span><span class="o">);</span>
<span class="n">button7</span><span class="o">.</span><span class="na">setTextColor</span><span class="o">(</span><span class="n">textCslJava</span><span class="o">);</span>
<span class="c1">// (8)</span>
<span class="n">Context</span> <span class="n">themedCtx</span> <span class="o">=</span> <span class="n">button8</span><span class="o">.</span><span class="na">getContext</span><span class="o">();</span>
<span class="n">ColorStateList</span> <span class="n">textCslJavaWithCustomTheme</span> <span class="o">=</span> <span class="n">createColorStateList</span><span class="o">(</span><span class="n">themedCtx</span><span class="o">);</span>
<span class="n">button8</span><span class="o">.</span><span class="na">setTextColor</span><span class="o">(</span><span class="n">textCslJavaWithCustomTheme</span><span class="o">);</span>
</code></pre></div></div>
<h4 id="solutions">Solutions</h4>
<p>Here are the screenshots of what the buttons look like on API 19 vs. API 23 devices:</p>
<ul>
<li><a href="/assets/images/posts/2016/08/07/rant7-contextcompat-examples-19.png">API 19 solutions</a></li>
<li><a href="/assets/images/posts/2016/08/07/rant7-contextcompat-examples-23.png">API 23 solutions</a></li>
</ul>
<p>Note that there isn’t anything special about the weird pink color in the two
screenshots. That’s just the “undefined behavior” that results when you try to
resolve a theme attribute without a corresponding <code class="highlighter-rouge">Theme</code>. :)</p>
<p>As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to
+1 and/or share this blog post if you found it helpful! And check out the
<a href="https://github.com/alexjlockwood/adp-contextcompat-getcolor">source code for these examples on GitHub</a> as well!</p>
Postponed Shared Element Transitions (part 3b)https://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html2015-03-09T00:00:00+00:002015-03-09T00:00:00+00:00<p>This post continues our in-depth analysis of <em>shared element transitions</em> by discussing an important feature of the Lollipop Transition API: postponed shared element transitions. It is the fourth of a series of posts I will be writing on the topic:</p>
<p>This post continues our in-depth analysis of <em>shared element transitions</em> by discussing an important feature of the Lollipop Transition API: postponed shared element transitions. It is the fourth of a series of posts I will be writing on the topic:</p>
<ul>
<li><strong>Part 1:</strong> <a href="/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html">Getting Started with Activity & Fragment Transitions</a></li>
<li><strong>Part 2:</strong> <a href="/2014/12/activity-fragment-content-transitions-in-depth-part2.html">Content Transitions In-Depth</a></li>
<li><strong>Part 3a:</strong> <a href="/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html">Shared Element Transitions In-Depth</a></li>
<li><strong>Part 3b:</strong> <a href="/2015/03/activity-postponed-shared-element-transitions-part3b.html">Postponed Shared Element Transitions</a></li>
<li><strong>Part 3c:</strong> Implementing Shared Element Callbacks (<em>coming soon!</em>)</li>
<li><strong>Part 4:</strong> Activity & Fragment Transition Examples (<em>coming soon!</em>)</li>
</ul>
<p>Until I write part 4, an example application demonstrating some advanced activity transitions is available <a href="https://github.com/alexjlockwood/activity-transitions">here</a>.</p>
<p>We begin by discussing the need to postpone certain shared element transitions due to a common problem.</p>
<h3 id="understanding-the-problem">Understanding the Problem</h3>
<!--morestart-->
<p>A common source of problems when dealing with shared element transitions stems from the fact that they are started by the framework very early in the Activity lifecycle. Recall from <a href="/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html">part 1</a> that <code class="highlighter-rouge">Transition</code>s must capture both the start and end state of its target views in order to build a properly functioning animation. Thus, if the framework starts the shared element transition before its shared elements are given their final size and position and size within the called Activity, the transition will capture the incorrect end values for its shared elements and the resulting animation will fail completely (see <strong>Video 3.3</strong> for an example of what the failed enter transition might look like).</p>
<!--more-->
<div class="responsive-figure nexus6-figure">
<div class="framed-nexus6-port">
<video id="figure33" onclick="playPause('figure33')" poster="/assets/videos/posts/2015/03/09/postpone-bug-opt.png" preload="none">
<source src="/assets/videos/posts/2015/03/09/postpone-bug-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2015/03/09/postpone-bug-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2015/03/09/postpone-bug-opt.ogv" type="video/ogg" />
</video>
</div>
<div style="font-size:10pt;margin-left:20px;margin-bottom:30px">
<p class="img-caption" style="margin-top:3px;margin-bottom:10px;text-align: center;"><strong>Video 3.3</strong> - Fixing a broken shared element enter animation by postponing the transition. Click to play.</p>
</div>
</div>
<p>Whether or not the shared elements’ end values will be calculated before the transition begins depends mainly on two factors: (1) the complexity and depth of the called activity’s layout and (2) the amount of time it takes for the called activity to load its required data. The more complex the layout, the longer it will take to determine the shared elements’ position and size on the screen. Similarly, if the shared elements’ final appearance within the activity depends on asynchronously loaded data, there is a chance that the framework might automatically start the shared element transition before that data is delivered back to the main thread. Listed below are some of the common cases in which you might encounter these issues:</p>
<ul>
<li>
<p><strong>The shared element lives in a <code class="highlighter-rouge">Fragment</code> hosted by the called activity.</strong> <a href="https://developer.android.com/reference/android/app/FragmentTransaction.html#commit()"><code class="highlighter-rouge">FragmentTransaction</code>s are not executed immediately after they are committed</a>; they are scheduled as work on the main thread to be done at a later time. Thus, if the shared element lives inside the <code class="highlighter-rouge">Fragment</code>’s view hierarchy and the <code class="highlighter-rouge">FragmentTransaction</code> is not executed quickly enough, it is possible that the framework will start the shared element transition before the shared element is properly measured and laid out on the screen.<sup><a href="#footnote1" id="ref1">1</a></sup></p>
</li>
<li>
<p><strong>The shared element is a high-resolution image.</strong> Setting a high resolution image that exceeds the <code class="highlighter-rouge">ImageView</code>’s initial bounds might end up triggering <a href="https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/java/android/widget/ImageView.java#L453-L455">an additional layout pass</a> on the view hierarchy, therefore increasing the chances that the transition will begin before the shared element is ready. The asynchronous nature of popular bitmap loading/scaling libraries, such as <a href="https://android.googlesource.com/platform/frameworks/volley">Volley</a> and <a href="http://square.github.io/picasso/">Picasso</a>, will not reliably fix this problem: the framework has no prior knowledge that images are being downloaded, scaled, and/or fetched from disk on a background thread and will start the shared element transition whether or not images are still being processed.</p>
</li>
<li>
<p><strong>The shared element depends on asynchronously loaded data.</strong> If the shared elements require data loaded by an <code class="highlighter-rouge">AsyncTask</code>, an <code class="highlighter-rouge">AsyncQueryHandler</code>, a <code class="highlighter-rouge">Loader</code>, or something similar before their final appearance within the called activity can be determined, the framework might start the transition before that data is delivered back to the main thread.</p>
</li>
</ul>
<h3 id="postponeentertransition-and-startpostponedentertransition"><code class="highlighter-rouge">postponeEnterTransition()</code> and <code class="highlighter-rouge">startPostponedEnterTransition()</code></h3>
<p>At this point you might be thinking, <em>“If only there was a way to temporarily delay the transition until we know for sure that the shared elements have been properly measured and laid out.”</em> Well, you’re in luck, because the Activity Transitions API<sup><a href="#footnote2" id="ref2">2</a></sup> gives us a way to do just that!</p>
<p>To temporarily prevent the shared element transition from starting, call <a href="https://developer.android.com/reference/android/app/Activity.html#postponeEnterTransition()"><code class="highlighter-rouge">postponeEnterTransition()</code></a> in your called activity’s <code class="highlighter-rouge">onCreate()</code> method. Later, when you know for certain that all of your shared elements have been properly positioned and sized, call <a href="https://developer.android.com/reference/android/app/Activity.html#startPostponedEnterTransition()"><code class="highlighter-rouge">startPostponedEnterTransition()</code></a> to resume the transition. A common pattern you’ll find useful is to start the postponed transition in an <a href="http://developer.android.com/reference/android/view/ViewTreeObserver.OnPreDrawListener.html"><code class="highlighter-rouge">OnPreDrawListener</code></a>, which will be called after the shared element has been measured and laid out:<sup><a href="#footnote3" id="ref3">3</a></sup></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">setContentView</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">activity_main</span><span class="o">);</span>
<span class="c1">// Postpone the shared element enter transition.</span>
<span class="n">postponeEnterTransition</span><span class="o">();</span>
<span class="c1">// TODO: Call the "scheduleStartPostponedTransition()" method</span>
<span class="c1">// below when you know for certain that the shared element is</span>
<span class="c1">// ready for the transition to begin.</span>
<span class="o">}</span>
<span class="cm">/**
* Schedules the shared element transition to be started immediately
* after the shared element has been measured and laid out within the
* activity's view hierarchy. Some common places where it might make
* sense to call this method are:
*
* (1) Inside a Fragment's onCreateView() method (if the shared element
* lives inside a Fragment hosted by the called Activity).
*
* (2) Inside a Picasso Callback object (if you need to wait for Picasso to
* asynchronously load/scale a bitmap before the transition can begin).
*
* (3) Inside a LoaderCallback's onLoadFinished() method (if the shared
* element depends on data queried by a Loader).
*/</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">scheduleStartPostponedTransition</span><span class="o">(</span><span class="kd">final</span> <span class="n">View</span> <span class="n">sharedElement</span><span class="o">)</span> <span class="o">{</span>
<span class="n">sharedElement</span><span class="o">.</span><span class="na">getViewTreeObserver</span><span class="o">().</span><span class="na">addOnPreDrawListener</span><span class="o">(</span>
<span class="k">new</span> <span class="n">ViewTreeObserver</span><span class="o">.</span><span class="na">OnPreDrawListener</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">onPreDraw</span><span class="o">()</span> <span class="o">{</span>
<span class="n">sharedElement</span><span class="o">.</span><span class="na">getViewTreeObserver</span><span class="o">().</span><span class="na">removeOnPreDrawListener</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="n">startPostponedEnterTransition</span><span class="o">();</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Despite their names, these two methods can also be used to postpone shared element return transitions as well. Simply postpone the return transition within the calling Activity’s <a href="https://developer.android.com/reference/android/app/Activity.html#onActivityReenter(int,%20android.content.Intent)">onActivityReenter()</a> method instead:<sup><a href="#footnote4" id="ref4">4</a></sup></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Don't forget to call setResult(Activity.RESULT_OK) in the returning
* activity or else this method won't be called!
*/</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onActivityReenter</span><span class="o">(</span><span class="kt">int</span> <span class="n">resultCode</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onActivityReenter</span><span class="o">(</span><span class="n">resultCode</span><span class="o">,</span> <span class="n">data</span><span class="o">);</span>
<span class="c1">// Postpone the shared element return transition.</span>
<span class="n">postponeEnterTransition</span><span class="o">();</span>
<span class="c1">// TODO: Call the "scheduleStartPostponedTransition()" method</span>
<span class="c1">// above when you know for certain that the shared element is</span>
<span class="c1">// ready for the transition to begin.</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Despite making your shared element transitions smoother and more reliable, it’s important to also be aware that introducing postponed shared element transitions into your application could also have some potentially harmful side-effects:</p>
<ul>
<li>
<p><strong>Never forget to call <code class="highlighter-rouge">startPostponedEnterTransition()</code> after calling <code class="highlighter-rouge">postponeEnterTransition</code>.</strong> Forgetting to do so will leave your application in a state of deadlock, preventing the user from ever being able to reach the next Activity screen.</p>
</li>
<li>
<p><strong>Never postpone a transition for longer than a fraction of a second.</strong> Postponing a transition for even a fraction of a second could introduce unwanted lag into your application, annoying the user and slowing down the user experience.</p>
</li>
</ul>
<p>As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful!</p>
<hr class="footnote-divider" />
<p><sup id="footnote1">1</sup> Of course, most applications can usually workaround this issue by calling <a href="https://developer.android.com/reference/android/app/FragmentManager.html#executePendingTransactions()"><code class="highlighter-rouge">FragmentManager#executePendingTransactions()</code></a>, which will force any pending <code class="highlighter-rouge">FragmentTransaction</code>s to execute immediately instead of asynchronously. <a href="#ref1" title="Jump to footnote 1.">↩</a></p>
<p><sup id="footnote2">2</sup> Note that the <code class="highlighter-rouge">postponeEnterTransition()</code> and <code class="highlighter-rouge">startPostponedEnterTransition()</code> methods only work for Activity Transitions and not for Fragment Transitions. For an explanation and possible workaround, see <a href="http://stackoverflow.com/q/26977303/844882">this StackOverflow answer</a> and <a href="https://plus.google.com/+AlexLockwood/posts/3DxHT42rmmY">this Google+ post</a>. <a href="#ref2" title="Jump to footnote 2.">↩</a></p>
<p><sup id="footnote3">3</sup> Pro tip: you can verify whether or not allocating the <code class="highlighter-rouge">OnPreDrawListener</code> is needed by calling <a href="http://developer.android.com/reference/android/view/View.html#isLayoutRequested()"><code class="highlighter-rouge">View#isLayoutRequested()</code></a> beforehand, if necessary. <a href="http://developer.android.com/reference/android/view/View.html#isLaidOut()"><code class="highlighter-rouge">View#isLaidOut()</code></a> may come in handy in some cases as well. <a href="#ref3" title="Jump to footnote 3.">↩</a></p>
<p><sup id="footnote3">4</sup> A good way to test the behavior of your shared element return/reenter transitions is by going into the Developer Options and enabling the “Don’t keep activities” setting. This will help test the worst case scenario in which the calling activity will need to recreate its layout, requery any necessary data, etc. before the return transition begins. <a href="#ref4" title="Jump to footnote 4.">↩</a></p>
Shared Element Transitions In-Depth (part 3a)https://www.androiddesignpatterns.com/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html2015-01-12T00:00:00+00:002015-01-12T00:00:00+00:00<p>This post will give an in-depth analysis of <em>shared element transitions</em> and their role in the Activity and Fragment Transitions API. This is the third of a series of posts I will be writing on the topic:</p>
<p>This post will give an in-depth analysis of <em>shared element transitions</em> and their role in the Activity and Fragment Transitions API. This is the third of a series of posts I will be writing on the topic:</p>
<ul>
<li><strong>Part 1:</strong> <a href="/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html">Getting Started with Activity & Fragment Transitions</a></li>
<li><strong>Part 2:</strong> <a href="/2014/12/activity-fragment-content-transitions-in-depth-part2.html">Content Transitions In-Depth</a></li>
<li><strong>Part 3a:</strong> <a href="/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html">Shared Element Transitions In-Depth</a></li>
<li><strong>Part 3b:</strong> <a href="/2015/03/activity-postponed-shared-element-transitions-part3b.html">Postponed Shared Element Transitions</a></li>
<li><strong>Part 3c:</strong> Implementing Shared Element Callbacks (<em>coming soon!</em>)</li>
<li><strong>Part 4:</strong> Activity & Fragment Transition Examples (<em>coming soon!</em>)</li>
</ul>
<p>Until I write part 4, an example application demonstrating some advanced activity transitions is available <a href="https://github.com/alexjlockwood/activity-transitions">here</a>.</p>
<p>Part 3 of this series will be broken up into three parts: part 3a will focus on how shared elements operate under-the-hood and part 3b and part 3c will focus more on the implementation-specific details of the API, such as the importance of postponing certain shared element transitions and implementing <code class="highlighter-rouge">SharedElementCallback</code>s.</p>
<p>We begin by summarizing what we learned about shared element transitions in <a href="/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html">part 1</a> and illustrating how they can be used to achieve smooth, seamless animations in Android Lollipop.</p>
<h3 id="what-is-a-shared-element-transition">What is a Shared Element Transition?</h3>
<!--morestart-->
<p>A <em>shared element transition</em> determines how shared element views—also called <em>hero views</em>—are animated from one Activity/Fragment to another during a scene transition. Shared elements are animated by the called Activity/Fragment’s enter and return shared element transitions,<sup><a href="#footnote1" id="ref1">1</a></sup> each of which can be specified using the following <a href="http://developer.android.com/reference/android/view/Window.html"><code class="highlighter-rouge">Window</code></a> and <a href="http://developer.android.com/reference/android/app/Fragment.html"><code class="highlighter-rouge">Fragment</code></a> methods:</p>
<ul>
<li><code class="highlighter-rouge">setSharedElementEnterTransition()</code> - <code class="highlighter-rouge">B</code>’s enter shared element transition animates shared element views from their starting positions in <code class="highlighter-rouge">A</code> to their final positions in <code class="highlighter-rouge">B</code>.</li>
<li><code class="highlighter-rouge">setSharedElementReturnTransition()</code> - <code class="highlighter-rouge">B</code>’s return shared element transition animates shared element views from their starting positions in <code class="highlighter-rouge">B</code> to their final positions in <code class="highlighter-rouge">A</code>.</li>
</ul>
<!--more-->
<div class="responsive-figure nexus6-figure">
<div class="framed-nexus6-port">
<video id="figure31" onclick="playPause('figure31')" poster="/assets/videos/posts/2015/01/12/music-opt.png" preload="none">
<source src="/assets/videos/posts/2015/01/12/music-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2015/01/12/music-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2015/01/12/music-opt.ogv" type="video/ogg" />
</video>
</div>
<div style="font-size:10pt;margin-left:20px;margin-bottom:30px">
<p class="img-caption" style="margin-top:3px;margin-bottom:10px;text-align: center;"><strong>Video 3.1</strong> - Shared element transitions in action in the Google Play Music app (as of v5.6). Click to play.</p>
</div>
</div>
<p><strong>Video 3.1</strong> illustrates how shared element transitions are used in the Google Play Music app. The transition consists of two shared elements: an <code class="highlighter-rouge">ImageView</code> and its parent <code class="highlighter-rouge">CardView</code>. During the transition, the <code class="highlighter-rouge">ImageView</code> seamlessly animates between the two activities while the <code class="highlighter-rouge">CardView</code> gradually expands/contracts into place.</p>
<p>Whereas <a href="/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html">part 1</a> only briefly introduced the subject, this blog post aims to give a much more in-depth analysis of shared element transitions. How are shared element transitions triggered under-the-hood? Which types of <code class="highlighter-rouge">Transition</code> objects can be used? How and where are shared element views drawn during the transition? In the next couple sections, we’ll tackle these questions one-by-one.</p>
<h3 id="shared-element-transitions-under-the-hood">Shared Element Transitions Under-The-Hood</h3>
<p>Recall from the previous two posts that a <code class="highlighter-rouge">Transition</code> has two main responsibilities: capturing the start and end state of its target views and creating an <code class="highlighter-rouge">Animator</code> that will animate the views between the two states. Shared element transitions operate no differently: before a shared element transition can create its animation, it must first capture each shared element’s start and end state—namely its position, size, and appearance in both the calling and called Activities/Fragments. With this information, the transition can determine how each shared element view should animate into place.</p>
<p>Similar to how <a href="/2014/12/activity-fragment-content-transitions-in-depth-part2.html">content transitions operate under-the-hood</a>, the framework feeds the shared element transition this state information by directly modifying each shared element’s view properties at runtime. More specifically, when Activity <code class="highlighter-rouge">A</code> starts Activity <code class="highlighter-rouge">B</code> the following sequence of events occurs:<sup><a href="#footnote2" id="ref2">2</a></sup></p>
<ol>
<li>Activity <code class="highlighter-rouge">A</code> calls <code class="highlighter-rouge">startActivity()</code> and Activity <code class="highlighter-rouge">B</code> is created, measured, and laid out with an initially translucent window and transparent window background color.</li>
<li>The framework repositions each shared element view in <code class="highlighter-rouge">B</code> to match its exact size and location in <code class="highlighter-rouge">A</code>. Shortly after, <code class="highlighter-rouge">B</code>’s enter transition captures the start state of all the shared elements in <code class="highlighter-rouge">B</code>.</li>
<li>The framework repositions each shared element view in <code class="highlighter-rouge">B</code> to match its final size and location in <code class="highlighter-rouge">B</code>. Shortly after, <code class="highlighter-rouge">B</code>’s enter transition captures the end state of all the shared elements in <code class="highlighter-rouge">B</code>.</li>
<li><code class="highlighter-rouge">B</code>’s enter transition compares the start and end state of its shared element views and creates an <code class="highlighter-rouge">Animator</code> based on the differences.</li>
<li>The framework instructs <code class="highlighter-rouge">A</code> to hide its shared element views from sight and the resulting <code class="highlighter-rouge">Animator</code> is run. As <code class="highlighter-rouge">B</code>’s shared element views animate into place, <code class="highlighter-rouge">B</code>’s window background gradually fades in on top <code class="highlighter-rouge">A</code> until <code class="highlighter-rouge">B</code> is entirely opaque and the transition completes.</li>
</ol>
<p>Whereas content transitions are governed by changes to each transitioning view’s visibility, <strong>shared element transitions are governed by changes to each shared element view’s position, size, and appearance</strong>. As of API 21, the framework provides several different <code class="highlighter-rouge">Transition</code> implementations that can be used to customize how shared elements are animated during a scene change:</p>
<ul>
<li><a href="https://developer.android.com/reference/android/transition/ChangeBounds.html"><code class="highlighter-rouge">ChangeBounds</code></a> - Captures the <em>layout bounds</em> of shared element views and animates the differences. <code class="highlighter-rouge">ChangeBounds</code> is frequently used in shared element transitions, as most shared elements will differ in size and/or location within either of the two Activities/Fragments.</li>
<li><a href="https://developer.android.com/reference/android/transition/ChangeTransform.html"><code class="highlighter-rouge">ChangeTransform</code></a> - Captures the <em>scale and rotation</em> of shared element views and animates the differences.<sup><a href="#footnote3" id="ref3">3</a></sup></li>
<li><a href="https://developer.android.com/reference/android/transition/ChangeClipBounds.html"><code class="highlighter-rouge">ChangeClipBounds</code></a> - Captures the <a href="https://developer.android.com/reference/android/view/View.html#getClipBounds()"><em>clip bounds</em></a> of shared element views and animates the differences.</li>
<li><a href="https://developer.android.com/reference/android/transition/ChangeImageTransform.html"><code class="highlighter-rouge">ChangeImageTransform</code></a> - Captures the <em>transform matrices</em> of shared element <code class="highlighter-rouge">ImageView</code>s and animates the differences. In combination with <code class="highlighter-rouge">ChangeBounds</code>, this transition allows <code class="highlighter-rouge">ImageView</code>s that change in size, shape, and/or <a href="https://developer.android.com/reference/android/widget/ImageView.ScaleType.html">ImageView.ScaleType</a> to animate smoothly and efficiently.</li>
<li><a href="https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/res/res/transition/move.xml"><code class="highlighter-rouge">@android:transition/move</code></a> - A <code class="highlighter-rouge">TransitionSet</code> that plays all four transition types above in parallel. As discussed in <a href="/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html">part 1</a>, if an enter/return shared element transition is not explicitly specified, the framework will run this transition by default.</li>
</ul>
<p>In the example above, we also can see that <strong>shared element view instances are not actually “shared” across Activities/Fragments</strong>. In fact, almost everything the user sees during both enter and return shared element transitions is drawn directly inside <code class="highlighter-rouge">B</code>’s content view. Instead of somehow transferring the shared element view instance from <code class="highlighter-rouge">A</code> to <code class="highlighter-rouge">B</code>, the framework uses a different means of achieving the same visual effect. When <code class="highlighter-rouge">A</code> starts <code class="highlighter-rouge">B</code>, the framework collects all of the relevant state information about the shared elements in <code class="highlighter-rouge">A</code> and passes it to <code class="highlighter-rouge">B</code>. <code class="highlighter-rouge">B</code> then uses this information to initialize the start state of its shared elements views, each of which will initially match the exact position, size, and appearance they had in <code class="highlighter-rouge">A</code>. When the transition begins, everything in <code class="highlighter-rouge">B</code> except the shared elements are initially invisible to the user. As the transition progresses, however, the framework gradually fades in <code class="highlighter-rouge">B</code>’s Activity window until the shared elements in <code class="highlighter-rouge">B</code> finish animating and <code class="highlighter-rouge">B</code>’s window background is opaque.</p>
<h3 id="using-the-shared-element-overlay4">Using the Shared Element Overlay<sup><a href="#footnote4" id="ref4">4</a></sup></h3>
<div class="responsive-figure nexus6-figure">
<div class="framed-nexus6-port">
<video id="figure32" onclick="playPause('figure32')" poster="/assets/videos/posts/2015/01/12/overlay-opt.png" preload="none">
<source src="/assets/videos/posts/2015/01/12/overlay-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2015/01/12/overlay-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2015/01/12/overlay-opt.ogv" type="video/ogg" />
</video>
</div>
<div style="font-size:10pt;margin-left:20px;margin-bottom:30px">
<p class="img-caption" style="margin-top:3px;margin-bottom:10px;text-align: center;"><strong>Video 3.2</strong> - A simple app that illustrates a potential bug that can result when the shared element overlay is disabled. Click to play.</p>
</div>
</div>
<p>Finally, before we can gain a complete understanding of how shared element transitions are drawn by the framework, we must discuss the <em>shared element overlay</em>. Although not immediately obvious, <strong>shared elements are drawn on top of the entire view hierarchy in the window’s <a href="https://developer.android.com/reference/android/view/ViewOverlay.html"><code class="highlighter-rouge">ViewOverlay</code></a> by default</strong>. In case you haven’t heard of it before, the <code class="highlighter-rouge">ViewOverlay</code> class was introduced in API 18 as a way to easily draw on top of a <code class="highlighter-rouge">View</code>. Drawables and views that are added to a view’s <code class="highlighter-rouge">ViewOverlay</code> are guaranteed to be drawn on top of everything else—even a <code class="highlighter-rouge">ViewGroup</code>’s children. With this in mind, it makes sense why the framework would choose to draw shared elements in the window’s <code class="highlighter-rouge">ViewOverlay</code> on top of everything else in the view hierarchy by default. Shared elements views should be the focus throughout the entire transition; the possibility of transitioning views accidentally drawing on top of the shared elements would immediately ruin the effect.<sup><a href="#footnote5" id="ref5">5</a></sup></p>
<p>Although shared elements are drawn in the shared element <code class="highlighter-rouge">ViewOverlay</code> by default, the framework does give us the ability to disable the overlay by calling the <a href="https://developer.android.com/reference/android/view/Window.html#setSharedElementsUseOverlay(boolean)"><code class="highlighter-rouge">Window#setSharedElementsUseOverlay(false)</code></a> method, just in case you ever find it necessary. If you ever do choose to disable the overlay, be wary of the undesired side-effects it might cause. As an example, <strong>Video 3.2</strong> runs a simple shared element transition twice, with and without the shared element overlay enabled respectively. The first time the transition is run, the shared element <code class="highlighter-rouge">ImageView</code> animates as expected in the shared element overlay, on top of all other views in the hierarchy. The second time the transition is run, however, we can clearly see that disabling the overlay has introduced a problem. As the bottom transitioning view slides up into the called Activity’s content view, the shared element <code class="highlighter-rouge">ImageView</code> is partially covered as and is drawn below the transitioning view for nearly the first half of the transition. Although there is a chance that this could be fixed by altering the order in which views are drawn in the layout and/or by setting <code class="highlighter-rouge">setClipChildren(false)</code> on the shared element’s parent, these sort of “hacky” modifications can easily become unmanagable and more trouble than they are worth. In short, try not to disable the shared element overlay unless you find it absolutely necessary, and you’ll likely benefit from simpler and more dramatic shared element transitions as a result.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Overall, this post presented three important points:</p>
<ol>
<li>A shared element transition determines how shared element views—also called hero views—are animated from one Activity/Fragment to another during a scene transition.</li>
<li>Shared element transitions are governed by changes to each shared element view’s position, size, and appearance.</li>
<li>Shared elements are drawn on top of the entire view hierarchy in the window’s <code class="highlighter-rouge">ViewOverlay</code> by default.</li>
</ol>
<p>As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful!</p>
<hr class="footnote-divider" />
<p><sup id="footnote1">1</sup> Note that the Activity Transition API gives you the ability to also specify exit and reenter shared element transitions using the <code class="highlighter-rouge">setSharedElementExitTransition()</code> and <code class="highlighter-rouge">setSharedElementReenterTransition()</code> methods, although doing so is usually not necessary. For an example illustrating one possible use case, check out <a href="https://halfthought.wordpress.com/2014/12/08/what-are-all-these-dang-transitions/">this blog post</a>. For an explanation why exit and reenter shared element transitions are not available for Fragment Transitions, see George Mount’s answer and comments in <a href="http://stackoverflow.com/q/27346020/844882">this StackOverflow post</a>. <a href="#ref1" title="Jump to footnote 1.">↩</a></p>
<p><sup id="footnote2">2</sup> A similar sequence of events occurs during the exit/return/reenter transitions for both Activities and Fragments. <a href="#ref2" title="Jump to footnote 2.">↩</a></p>
<p><sup id="footnote3">3</sup> One other subtle feature of <code class="highlighter-rouge">ChangeTransform</code> is that it can detect and handle changes made to a shared element view’s parent during a transition. This comes in handy when, for example, the shared element’s parent has an opaque background and is by default selected to be a transitioning view during the scene change. In this case, the <code class="highlighter-rouge">ChangeTransform</code> will detect that the shared element’s parent is being actively modified by the content transition, pull out the shared element from its parent, and animate the shared element separately. See George Mount’s <a href="http://stackoverflow.com/q/26899779/844882">StackOverflow answer</a> for more information. <a href="#ref3" title="Jump to footnote 3.">↩</a></p>
<p><sup id="footnote4">4</sup> Note that this section only pertains to Activity Transitions. Unlike Activity Transitions, shared elements are <strong>not</strong> drawn in a <code class="highlighter-rouge">ViewOverlay</code> by default during Fragment Transitions. That said, you can achieve a similar effect by applying a <code class="highlighter-rouge">ChangeTransform</code> transition, which will have the shared element drawn on top of the hierarchy in a <code class="highlighter-rouge">ViewOverlay</code> if it detects that its parent has changed. See <a href="http://stackoverflow.com/q/27892033/844882">this StackOverflow post</a> for more information. <a href="#ref4" title="Jump to footnote 4.">↩</a></p>
<p><sup id="footnote5">5</sup> Note that one negative side-effect of having shared elements drawn on top of the entire view hierarchy is that this means it will become possible for shared elements to draw on top of the System UI (such as the status bar, navigation bar, and action bar). For more information on how you can prevent this from happening, see <a href="https://plus.google.com/+AlexLockwood/posts/RPtwZ5nNebb">this Google+ post</a>. <a href="#ref5" title="Jump to footnote 5.">↩</a></p>
Content Transitions In-Depth (part 2)https://www.androiddesignpatterns.com/2014/12/activity-fragment-content-transitions-in-depth-part2.html2014-12-15T00:00:00+00:002014-12-15T00:00:00+00:00<p>This post will give an in-depth analysis of <em>content transitions</em> and their role in the Activity and Fragment Transitions API. This is the second of a series of posts I will be writing on the topic:</p>
<p>This post will give an in-depth analysis of <em>content transitions</em> and their role in the Activity and Fragment Transitions API. This is the second of a series of posts I will be writing on the topic:</p>
<ul>
<li><strong>Part 1:</strong> <a href="/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html">Getting Started with Activity & Fragment Transitions</a></li>
<li><strong>Part 2:</strong> <a href="/2014/12/activity-fragment-content-transitions-in-depth-part2.html">Content Transitions In-Depth</a></li>
<li><strong>Part 3a:</strong> <a href="/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html">Shared Element Transitions In-Depth</a></li>
<li><strong>Part 3b:</strong> <a href="/2015/03/activity-postponed-shared-element-transitions-part3b.html">Postponed Shared Element Transitions</a></li>
<li><strong>Part 3c:</strong> Implementing Shared Element Callbacks (<em>coming soon!</em>)</li>
<li><strong>Part 4:</strong> Activity & Fragment Transition Examples (<em>coming soon!</em>)</li>
</ul>
<p>Until I write part 4, an example application demonstrating some advanced activity transitions is available <a href="https://github.com/alexjlockwood/activity-transitions">here</a>.</p>
<p>We begin by summarizing what we learned about content transitions in <a href="/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html">part 1</a> and illustrating how they can be used to achieve smooth, seamless animations in Android Lollipop.</p>
<h3 id="what-is-a-content-transition">What is a Content Transition?</h3>
<!--morestart-->
<p>A <em>content transition</em> determines how the non-shared views—called <em>transitioning views</em>—enter or exit the scene during an Activity or Fragment transition. Motivated by Google’s new <a href="http://www.google.com/design/spec/animation/meaningful-transitions.html">Material Design</a> language, content transitions allow us to coordinate the entrance and exit of each Activity/Fragment’s views, making the act of switching between screens smooth and effortless. Beginning with Android Lollipop, content transitions can be set programatically by calling the following <a href="http://developer.android.com/reference/android/view/Window.html"><code class="highlighter-rouge">Window</code></a> and <a href="http://developer.android.com/reference/android/app/Fragment.html"><code class="highlighter-rouge">Fragment</code></a> methods:</p>
<ul>
<li><code class="highlighter-rouge">setExitTransition()</code> - <code class="highlighter-rouge">A</code>’s exit transition animates transitioning views <strong>out</strong> of the scene when <code class="highlighter-rouge">A</code> <strong>starts</strong> <code class="highlighter-rouge">B</code>.</li>
<li><code class="highlighter-rouge">setEnterTransition()</code> - <code class="highlighter-rouge">B</code>’s enter transition animates transitioning views <strong>into</strong> the scene when <code class="highlighter-rouge">A</code> <strong>starts</strong> <code class="highlighter-rouge">B</code>.</li>
<li><code class="highlighter-rouge">setReturnTransition()</code> - <code class="highlighter-rouge">B</code>’s return transition animates transitioning views <strong>out</strong> of the scene when <code class="highlighter-rouge">B</code> <strong>returns</strong> to <code class="highlighter-rouge">A</code>.</li>
<li><code class="highlighter-rouge">setReenterTransition()</code> - <code class="highlighter-rouge">A</code>’s reenter transition animates transitioning views <strong>into</strong> the scene when <code class="highlighter-rouge">B</code> <strong>returns</strong> to <code class="highlighter-rouge">A</code>.</li>
</ul>
<!--more-->
<div class="responsive-figure nexus6-figure">
<div class="framed-nexus6-port">
<video id="figure21" onclick="playPause('figure21')" poster="/assets/videos/posts/2014/12/15/games-opt.png" preload="none">
<source src="/assets/videos/posts/2014/12/15/games-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2014/12/15/games-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2014/12/15/games-opt.ogv" type="video/ogg" />
</video>
</div>
<div style="font-size:10pt;margin-left:20px;margin-bottom:30px">
<p class="img-caption" style="margin-top:3px;margin-bottom:10px;text-align: center;"><strong>Video 2.1</strong> - Content transitions in the Google Play Games app (as of v2.2). Click to play.</p>
</div>
</div>
<p>As an example, <strong>Video 2.1</strong> illustrates how content transitions are used in the Google Play Games app to achieve smooth animations between activities. When the second activity starts, its enter content transition gently shuffles the user avatar views into the scene from the bottom edge of the screen. When the back button is pressed, the second activity’s return content transition splits the view hierarchy into two and animates each half off the top and bottom of the screen.</p>
<p>So far our analysis of content transitions has only scratched the surface; several important questions still remain. How are content transitions triggered under-the-hood? Which types of <code class="highlighter-rouge">Transition</code> objects can be used? How does the framework determine the set of transitioning views? Can a <code class="highlighter-rouge">ViewGroup</code> and its children be animated together as a single entity during a content transition? In the next couple sections, we’ll tackle these questions one-by-one.</p>
<h3 id="content-transitions-under-the-hood">Content Transitions Under-The-Hood</h3>
<p>Recall from the <a href="/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html">previous post</a> that a <code class="highlighter-rouge">Transition</code> has two main responsibilities: capturing the start and end state of its target views and creating an <code class="highlighter-rouge">Animator</code> that will animate the views between the two states. Content transitions are no different: before a content transition’s animation can be created, the framework must give it the state information it needs by altering each transitioning view’s <em>visibility</em>. More specifically, when Activity <code class="highlighter-rouge">A</code> starts Activity <code class="highlighter-rouge">B</code> the following sequence of events occurs:<sup><a href="#footnote1" id="ref1">1</a></sup></p>
<ol>
<li>Activity <code>A</code> calls <code>startActivity()</code>.
<ol style="list-style-type: lower-alpha;">
<li>The framework traverses <code>A</code>'s view hierarchy and determines the set of transitioning views that will exit the scene when <code>A</code>'s exit transition is run.</li>
<li><code>A</code>'s exit transition captures the start state for the transitioning views in <code>A</code>.</li>
<li>The framework sets all transitioning views in <code>A</code> to <code>INVISIBLE</code>.</li>
<li>On the next display frame, <code>A</code>'s exit transition captures the end state for the transitioning views in <code>A</code>.</li>
<li><code>A</code>'s exit transition compares the start and end state of each transitioning view and creates an <code>Animator</code> based on the differences. The <code>Animator</code> is run and the transitioning views exit the scene.</li>
</ol>
</li>
<li>Activity <code>B</code> is started.
<ol style="list-style-type: lower-alpha;">
<li>The framework traverses <code>B</code>'s view hierarchy and determines the set of transitioning views that will enter the scene when <code>B</code>'s enter transition is run. The transitioning views are initially set to <code>INVISIBLE</code>.</li>
<li><code>B</code>'s enter transition captures the start state for the transitioning views in <code>B</code>.</li>
<li>The framework sets all transitioning views in <code>B</code> to <code>VISIBLE</code>.</li>
<li>On the next display frame, <code>B</code>'s enter transition captures the end state for the transitioning views in <code>B</code>.</li>
<li><code>B</code>'s enter transition compares the start and end state of each transitioning view and creates an <code>Animator</code> based on the differences. The <code>Animator</code> is run and the transitioning views enter the scene.</li>
</ol>
</li>
</ol>
<p>By toggling each transitioning view’s visibility between <code class="highlighter-rouge">INVISIBLE</code> and <code class="highlighter-rouge">VISIBLE</code>, the framework ensures that the content transition is given the state information it needs to create the desired animation. Clearly all content <code class="highlighter-rouge">Transition</code> objects then must at the very least be able to capture and record each transitioning view’s visibility in both its start and end states. Fortunately, the abstract <a href="https://developer.android.com/reference/android/transition/Visibility.html"><code class="highlighter-rouge">Visibility</code></a> class already does this work for you: subclasses of <code class="highlighter-rouge">Visibility</code> need only implement the <a href="https://developer.android.com/reference/android/transition/Visibility.html#onAppear(android.view.ViewGroup,%20android.transition.TransitionValues,%20int,%20android.transition.TransitionValues,%20int)"><code class="highlighter-rouge">onAppear()</code></a> and <a href="https://developer.android.com/reference/android/transition/Visibility.html#onDisappear(android.view.ViewGroup,%20android.transition.TransitionValues,%20int,%20android.transition.TransitionValues,%20int)"><code class="highlighter-rouge">onDisappear()</code></a> factory methods, in which they must create and return an <code class="highlighter-rouge">Animator</code> that will either animate the views into or out of the scene. As of API 21, three concrete <code class="highlighter-rouge">Visibility</code> implementations exist—<a href="https://developer.android.com/reference/android/transition/Fade.html"><code class="highlighter-rouge">Fade</code></a>, <a href="https://developer.android.com/reference/android/transition/Slide.html"><code class="highlighter-rouge">Slide</code></a>, and <a href="https://developer.android.com/reference/android/transition/Explode.html"><code class="highlighter-rouge">Explode</code></a>—all of which can be used to create Activity and Fragment content transitions. If necessary, custom <code class="highlighter-rouge">Visibility</code> classes may be implemented as well; doing so will be covered in a future blog post.</p>
<h3 id="transitioning-views--transition-groups">Transitioning Views & Transition Groups</h3>
<p>Up until now, we have assumed that content transitions operate on a set of non-shared views called <em>transitioning views</em>. In this section, we will discuss how the framework determines this set of views and how it can be further customized using <em>transition groups</em>.</p>
<p>Before the transition starts, the framework constructs the set of transitioning views by performing a recursive search on the Activity window’s (or Fragment’s) entire view hierarchy. The search begins by calling the overridden recursive <code class="highlighter-rouge">ViewGroup#captureTransitioningViews</code> method on the hierarchy’s root view, the <a href="https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/java/android/view/ViewGroup.java#L6243-L6258">source code</a> of which is given below:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** @hide */</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">captureTransitioningViews</span><span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">View</span><span class="o">></span> <span class="n">transitioningViews</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">getVisibility</span><span class="o">()</span> <span class="o">!=</span> <span class="n">View</span><span class="o">.</span><span class="na">VISIBLE</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isTransitionGroup</span><span class="o">())</span> <span class="o">{</span>
<span class="n">transitioningViews</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">getChildCount</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">count</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">View</span> <span class="n">child</span> <span class="o">=</span> <span class="n">getChildAt</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="n">child</span><span class="o">.</span><span class="na">captureTransitioningViews</span><span class="o">(</span><span class="n">transitioningViews</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="responsive-figure nexus6-figure">
<div class="framed-nexus6-port">
<video id="figure22" onclick="playPause('figure22')" poster="/assets/videos/posts/2014/12/15/webview-opt.png" preload="none">
<source src="/assets/videos/posts/2014/12/15/webview-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2014/12/15/webview-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2014/12/15/webview-opt.ogv" type="video/ogg" />
</video>
</div>
<div style="font-size:10pt;margin-left:20px;margin-bottom:30px">
<p class="img-caption" style="margin-top:3px;margin-bottom:10px;text-align: center;"><strong>Video 2.2</strong> - A simple Radiohead app that illustrates a potential bug involving transition groups and <code>WebView</code>s. Click to play.</p>
</div>
</div>
<p>The recursion is relatively straightforward: the framework traverses each level of the tree until it either finds a <code class="highlighter-rouge">VISIBLE</code> <a href="https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/java/android/view/View.java#L19351-L19362">leaf view</a> or a <em>transition group</em>. Transition groups essentially allow us to animate entire <code class="highlighter-rouge">ViewGroup</code>s as single entities during an Activity/Fragment transition. If a <code class="highlighter-rouge">ViewGroup</code>’s <a href="https://developer.android.com/reference/android/view/ViewGroup.html#isTransitionGroup()"><code class="highlighter-rouge">isTransitionGroup()</code></a><sup><a href="#footnote2" id="ref2">2</a></sup> method returns <code class="highlighter-rouge">true</code>, then it and all of its children views will be animated together as one. Otherwise, the recursion will continue and the <code class="highlighter-rouge">ViewGroup</code>’s transitioning children views will be treated independently during the animation. The final result of the search is the complete set of transitioning views that will be animated by the content transition.<sup><a href="#footnote3" id="ref3">3</a></sup></p>
<p>An example illustrating transition groups in action can be seen in <strong>Video 2.1</strong> above. During the enter transition, the user avatars shuffle into the screen independently of the others, whereas during the return transition the parent <code class="highlighter-rouge">ViewGroup</code> containing the user avatars is animated as one. The Google Play Games app likely uses a transition group to achieve this effect, making it look as if the current scene splits in half when the user returns to the previous activity.</p>
<p>Sometimes transition groups must also be used to fix mysterious bugs in your Activity/Fragment transitions. For example, consider the sample application in <strong>Video 2.2</strong>: the calling Activity displays a grid of Radiohead album covers and the called Activity shows a background header image, the shared element album cover, and a <code class="highlighter-rouge">WebView</code>. The app uses a return transition similar to the Google Play Games app, sliding the top background image and bottom <code class="highlighter-rouge">WebView</code> off the top and bottom of the screen respectively. However, as you can see in the video, a glitch occurs and the <code class="highlighter-rouge">WebView</code> fails to slide smoothly off the screen.</p>
<p>So what went wrong? Well, the problem stems from the fact that <code class="highlighter-rouge">WebView</code> is a <code class="highlighter-rouge">ViewGroup</code> and as a result is <em>not</em> selected to be a transitioning view by default. Thus, when the return content transition is run, the <code class="highlighter-rouge">WebView</code> will be ignored entirely and will remain drawn on the screen before being abruptly removed when the transition ends. Fortunately, we can easily fix this by calling <code class="highlighter-rouge">webView.setTransitionGroup(true)</code> at some point before the return transition begins.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Overall, this post presented three important points:</p>
<ol>
<li>A content transition determines how an Activity or Fragment’s non-shared views—called transitioning views—enter or exit the scene during an Activity or Fragment transition.</li>
<li>Content transitions are triggered by changes made to its transitioning views’ visibility and should almost always extend the abstract <code class="highlighter-rouge">Visibility</code> class as a result.</li>
<li>Transition groups enable us to animate entire <code class="highlighter-rouge">ViewGroup</code>s as single entities during a content transition.</li>
</ol>
<p>As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful!</p>
<hr class="footnote-divider" />
<p><sup id="footnote1">1</sup> A similar sequence of events occurs during return/reenter transitions for both Activities and Fragments. <a href="#ref1" title="Jump to footnote 1.">↩</a></p>
<p><sup id="footnote2">2</sup> Note that <code class="highlighter-rouge">isTransitionGroup()</code> will return <code class="highlighter-rouge">true</code> if the <code class="highlighter-rouge">ViewGroup</code> has a non-<code class="highlighter-rouge">null</code> background drawable and/or non-<code class="highlighter-rouge">null</code> transition name by default (as stated in the method’s <a href="https://developer.android.com/reference/android/view/ViewGroup.html#isTransitionGroup()">documentation</a>). <a href="#ref2" title="Jump to footnote 2.">↩</a></p>
<p><sup id="footnote3">3</sup> Note that any views that were explicitly <a href="https://developer.android.com/reference/android/transition/Transition.html#addTarget(android.view.View)">added</a> or <a href="https://developer.android.com/reference/android/transition/Transition.html#excludeTarget(android.view.View,%20boolean)">excluded</a> in the content <code class="highlighter-rouge">Transition</code> object will also be taken into account when the transition is run. <a href="#ref3" title="Jump to footnote 3.">↩</a></p>
Getting Started with Activity & Fragment Transitions (part 1)https://www.androiddesignpatterns.com/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html2014-12-04T00:00:00+00:002014-03-11T00:00:00+00:00<p>This post gives a brief overview of <code class="highlighter-rouge">Transition</code>s and introduces the new <a href="https://developer.android.com/training/material/animations.html#Transitions">Activity & Fragment transition APIs</a> that were added in Android 5.0 Lollipop. This is the first of a series of posts I will be writing on the topic:</p>
<p>This post gives a brief overview of <code class="highlighter-rouge">Transition</code>s and introduces the new <a href="https://developer.android.com/training/material/animations.html#Transitions">Activity & Fragment transition APIs</a> that were added in Android 5.0 Lollipop. This is the first of a series of posts I will be writing on the topic:</p>
<ul>
<li><strong>Part 1:</strong> <a href="/2014/12/activity-fragment-transitions-in-android-lollipop-part1.html">Getting Started with Activity & Fragment Transitions</a></li>
<li><strong>Part 2:</strong> <a href="/2014/12/activity-fragment-content-transitions-in-depth-part2.html">Content Transitions In-Depth</a></li>
<li><strong>Part 3a:</strong> <a href="/2015/01/activity-fragment-shared-element-transitions-in-depth-part3a.html">Shared Element Transitions In-Depth</a></li>
<li><strong>Part 3b:</strong> <a href="/2015/03/activity-postponed-shared-element-transitions-part3b.html">Postponed Shared Element Transitions</a></li>
<li><strong>Part 3c:</strong> Implementing Shared Element Callbacks (<em>coming soon!</em>)</li>
<li><strong>Part 4:</strong> Activity & Fragment Transition Examples (<em>coming soon!</em>)</li>
</ul>
<p>Until I write part 4, an example application demonstrating some advanced activity transitions is available <a href="https://github.com/alexjlockwood/activity-transitions">here</a>.</p>
<p>We begin by answering the following question: what is a <code class="highlighter-rouge">Transition</code>?</p>
<h3 id="what-is-a-transition">What is a <code class="highlighter-rouge">Transition</code>?</h3>
<!--morestart-->
<p>Activity and Fragment transitions in Lollipop are built on top of a relatively new feature in Android called <code class="highlighter-rouge">Transition</code>s. Introduced in KitKat, the transition framework provides a convenient API for animating between different UI states in an application. The framework is built around two key concepts: <em>scenes</em> and <em>transitions</em>. A scene defines a given state of an application’s UI, whereas a transition defines the animated change between two scenes.</p>
<p>When a scene changes, a <a href="https://developer.android.com/reference/android/transition/Transition.html"><code class="highlighter-rouge">Transition</code></a> has two main responsibilities:</p>
<ol>
<li>Capture the state of each view in both the start and end scenes, and</li>
<li>Create an <code class="highlighter-rouge">Animator</code> based on the differences that will animate the views from one scene to the other.</li>
</ol>
<!--more-->
<p>As an example, consider an <code class="highlighter-rouge">Activity</code> which fades its views in or out when the user taps the screen. We can achieve this effect with only a few lines using Android’s transition framework, as shown in the code<sup><a href="#footnote1" id="ref1">1</a></sup> below:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ExampleActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="kd">implements</span> <span class="n">View</span><span class="o">.</span><span class="na">OnClickListener</span> <span class="o">{</span>
<span class="kd">private</span> <span class="n">ViewGroup</span> <span class="n">mRootView</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">View</span> <span class="n">mRedBox</span><span class="o">,</span> <span class="n">mGreenBox</span><span class="o">,</span> <span class="n">mBlueBox</span><span class="o">,</span> <span class="n">mBlackBox</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">setContentView</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">activity_main</span><span class="o">);</span>
<span class="n">mRootView</span> <span class="o">=</span> <span class="o">(</span><span class="n">ViewGroup</span><span class="o">)</span> <span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">layout_root_view</span><span class="o">);</span>
<span class="n">mRootView</span><span class="o">.</span><span class="na">setOnClickListener</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="n">mRedBox</span> <span class="o">=</span> <span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">red_box</span><span class="o">);</span>
<span class="n">mGreenBox</span> <span class="o">=</span> <span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">green_box</span><span class="o">);</span>
<span class="n">mBlueBox</span> <span class="o">=</span> <span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">blue_box</span><span class="o">);</span>
<span class="n">mBlackBox</span> <span class="o">=</span> <span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">black_box</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onClick</span><span class="o">(</span><span class="n">View</span> <span class="n">v</span><span class="o">)</span> <span class="o">{</span>
<span class="n">TransitionManager</span><span class="o">.</span><span class="na">beginDelayedTransition</span><span class="o">(</span><span class="n">mRootView</span><span class="o">,</span> <span class="k">new</span> <span class="n">Fade</span><span class="o">());</span>
<span class="n">toggleVisibility</span><span class="o">(</span><span class="n">mRedBox</span><span class="o">,</span> <span class="n">mGreenBox</span><span class="o">,</span> <span class="n">mBlueBox</span><span class="o">,</span> <span class="n">mBlackBox</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">toggleVisibility</span><span class="o">(</span><span class="n">View</span><span class="o">...</span> <span class="n">views</span><span class="o">)</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="n">View</span> <span class="n">view</span> <span class="o">:</span> <span class="n">views</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">isVisible</span> <span class="o">=</span> <span class="n">view</span><span class="o">.</span><span class="na">getVisibility</span><span class="o">()</span> <span class="o">==</span> <span class="n">View</span><span class="o">.</span><span class="na">VISIBLE</span><span class="o">;</span>
<span class="n">view</span><span class="o">.</span><span class="na">setVisibility</span><span class="o">(</span><span class="n">isVisible</span> <span class="o">?</span> <span class="n">View</span><span class="o">.</span><span class="na">INVISIBLE</span> <span class="o">:</span> <span class="n">View</span><span class="o">.</span><span class="na">VISIBLE</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>To better understand what happens under-the-hood in this example, let’s analyze the process step-by-step assuming that each view is initially <code class="highlighter-rouge">VISIBLE</code> on screen:</p>
<div class="responsive-figure nexus6-figure">
<div class="framed-nexus6-port">
<video id="figure11" onclick="playPause('figure11')" poster="/assets/videos/posts/2014/12/04/trivial-opt.png" preload="none">
<source src="/assets/videos/posts/2014/12/04/trivial-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2014/12/04/trivial-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2014/12/04/trivial-opt.ogv" type="video/ogg" />
</video>
</div>
<div style="font-size:10pt;margin-left:20px;margin-bottom:30px">
<p class="img-caption" style="margin-top:3px;margin-bottom:10px;text-align: center;"><strong>Video 1.1</strong> - Running the example transition above using a <code>Fade</code>, <code>Slide</code>, and <code>Explode</code>. Click to play.</p>
</div>
</div>
<ol>
<li>A click is detected and the developer calls <a href="https://developer.android.com/reference/android/transition/TransitionManager.html#beginDelayedTransition(android.view.ViewGroup,%20android.transition.Transition)"><code class="highlighter-rouge">beginDelayedTransition()</code></a>, passing the scene root and a <code class="highlighter-rouge">Fade</code> transition as the arguments. The framework immediately calls the transition’s <a href="https://developer.android.com/reference/android/transition/Transition.html#captureStartValues(android.transition.TransitionValues)"><code class="highlighter-rouge">captureStartValues()</code></a> method for each view in the scene and the transition records each view’s visibility.</li>
<li>When the call returns, the developer sets each view in the scene to <code class="highlighter-rouge">INVISIBLE</code>.</li>
<li>On the next display frame, the framework calls the transition’s <a href="https://developer.android.com/reference/android/transition/Transition.html#captureEndValues(android.transition.TransitionValues)"><code class="highlighter-rouge">captureEndValues()</code></a> method for each view in the scene and the transition records each view’s (recently updated) visibility.</li>
<li>The framework calls the transition’s <a href="https://developer.android.com/reference/android/transition/Transition.html#createAnimator(android.view.ViewGroup,%20android.transition.TransitionValues,%20android.transition.TransitionValues)"><code class="highlighter-rouge">createAnimator()</code></a> method. The transition analyzes the start and end values of each view and notices a difference: <em>the views are <code class="highlighter-rouge">VISIBLE</code> in the start scene but <code class="highlighter-rouge">INVISIBLE</code> in the end scene.</em> The <code class="highlighter-rouge">Fade</code> transition uses this information to create and return an <code class="highlighter-rouge">AnimatorSet</code> that will fade each view’s <code class="highlighter-rouge">alpha</code> property to <code class="highlighter-rouge">0f</code>.</li>
<li>The framework runs the returned <code class="highlighter-rouge">Animator</code>, causing all views to gradually fade out of the screen.</li>
</ol>
<p>This simple example highlights two main advantages that the transition framework has to offer. First, <code class="highlighter-rouge">Transition</code>s abstract the idea of <code class="highlighter-rouge">Animator</code>s from the developer. As a result, <code class="highlighter-rouge">Transition</code>s can significantly reduce the amount of code you write in your activities and fragments: all the developer must do is set the views’ start and end values and the <code class="highlighter-rouge">Transition</code> will automatically construct an animation based on the differences. Second, animations between scenes can be easily changed by using different <code class="highlighter-rouge">Transition</code> objects. <strong>Video 1.1</strong>, for example, illustrates the dramatically different effects we can achieve by replacing the <code class="highlighter-rouge">Fade</code> transition with a <code class="highlighter-rouge">Slide</code> or <code class="highlighter-rouge">Explode</code>. As we will see moving forward, these advantages will allow us to build complex Activity and Fragment transition animations with a relatively small amount of code. In the next few sections, we will see for ourselves how this can be done using Lollipop’s new Activity and Fragment transition APIs.</p>
<h3 id="activity--fragment-transitions-in-android-lollipop">Activity & Fragment Transitions in Android Lollipop</h3>
<p>As of Android 5.0, <code class="highlighter-rouge">Transition</code>s can now be used to perform elaborate animations when switching between different <code class="highlighter-rouge">Activity</code>s or <code class="highlighter-rouge">Fragment</code>s. Although Activity and Fragment animations could already be specified in previous platform versions using the <a href="http://developer.android.com/reference/android/app/Activity.html#overridePendingTransition(int,%20int)"><code class="highlighter-rouge">Activity#overridePendingTransition()</code></a> and <a href="http://developer.android.com/reference/android/app/FragmentTransaction.html#setCustomAnimations(int,%20int,%20int,%20int)"><code class="highlighter-rouge">FragmentTransaction#setCustomAnimation()</code></a> methods, they were limited in that they could only animate the entire Activity/Fragment container as a whole. The new Lollipop APIs take this a step further, making it possible to animate individual views as they enter or exit their containers and even allowing us to animate shared views from one Activity/Fragment container to the other.</p>
<p>Let’s begin by discussing the terminology that will be used in this series of posts. Note that although the terminology below is defined in terms of Activity transitions, the exact same terminology will be used for Fragment transitions as well:</p>
<blockquote>
<p>Let <code class="highlighter-rouge">A</code> and <code class="highlighter-rouge">B</code> be activities and assume activity <code class="highlighter-rouge">A</code> starts activity <code class="highlighter-rouge">B</code>. We refer to <code class="highlighter-rouge">A</code> as the “<em>calling Activity</em>” (the activity that “calls” <code class="highlighter-rouge">startActivity()</code>) and <code class="highlighter-rouge">B</code> as the “<em>called Activity</em>”.</p>
</blockquote>
<p>The Activity transition APIs are built around the idea of <em>exit, enter, return, and reenter transitions</em>. In the context of activities <code class="highlighter-rouge">A</code> and <code class="highlighter-rouge">B</code> defined above, we can describe each as follows:</p>
<blockquote>
<p>Activity <code class="highlighter-rouge">A</code>’s <em>exit transition</em> determines how views in <code class="highlighter-rouge">A</code> are animated when <code class="highlighter-rouge">A</code> starts <code class="highlighter-rouge">B</code>.</p>
<p>Activity <code class="highlighter-rouge">B</code>’s <em>enter transition</em> determines how views in <code class="highlighter-rouge">B</code> are animated when <code class="highlighter-rouge">A</code> starts <code class="highlighter-rouge">B</code>.</p>
<p>Activity <code class="highlighter-rouge">B</code>’s <em>return transition</em> determines how views in <code class="highlighter-rouge">B</code> are animated when <code class="highlighter-rouge">B</code> returns to <code class="highlighter-rouge">A</code>.</p>
<p>Activity <code class="highlighter-rouge">A</code>’s <em>reenter transition</em> determines how views in <code class="highlighter-rouge">A</code> are animated when <code class="highlighter-rouge">B</code> returns to <code class="highlighter-rouge">A</code>.</p>
</blockquote>
<div class="responsive-figure nexus6-figure">
<div class="framed-nexus6-port">
<video id="figure12" onclick="playPause('figure12')" poster="/assets/videos/posts/2014/12/04/news-opt.png" preload="none">
<source src="/assets/videos/posts/2014/12/04/news-opt.mp4" type="video/mp4" />
<source src="/assets/videos/posts/2014/12/04/news-opt.webm" type="video/webm" />
<source src="/assets/videos/posts/2014/12/04/news-opt.ogv" type="video/ogg" />
</video>
</div>
<div style="font-size:10pt;margin-left:20px;margin-bottom:30px">
<p class="img-caption" style="margin-top:3px;margin-bottom:10px;text-align: center;"><strong>Video 1.2</strong> - Content transitions and shared element transitions in action in the Google Play Newsstand app (as of v3.3). Click to play.</p>
</div>
</div>
<p>Lastly, the framework provides APIs for two types of Activity transitions—<em>content transitions</em> and <em>shared element transitions</em>—each of which allow us to customize the animations between Activities in unique ways:</p>
<blockquote>
<p>A <em>content transition</em> determines how an activity’s non-shared views (also called <em>transitioning views</em>) enter or exit the activity scene.</p>
<p>A <em>shared element transition</em> determines how an activity’s <em>shared elements</em> (also called <em>hero views</em>) are animated between two activities.</p>
</blockquote>
<p><strong>Video 1.2</strong> gives a nice illustration of content transitions and shared element transitions used in the Google Play Newsstand app. Although we can’t be sure without looking at the Newsstand source code, my best guess is that the following transitions are used:</p>
<ul>
<li>The exit and reenter content transitions for activity <code class="highlighter-rouge">A</code> (the calling activity) are both <code class="highlighter-rouge">null</code>. We can tell because the non-shared views in <code class="highlighter-rouge">A</code> are not animated when the user exits and reenters the activity.<sup><a href="#footnote2" id="ref2">2</a></sup></li>
<li>The enter content transition for activity <code class="highlighter-rouge">B</code> (the called activity) uses a custom slide-in transition that shuffles the list items into place from the bottom of the screen.</li>
<li>The return content transition for activity <code class="highlighter-rouge">B</code> is a <code class="highlighter-rouge">TransitionSet</code> that plays two child transitions in parallel: a <code class="highlighter-rouge">Slide(Gravity.TOP)</code> transition targeting the views in the top half of the activity and a <code class="highlighter-rouge">Slide(Gravity.BOTTOM)</code> transition targeting the views in the bottom half of the activity. The result is that the activity appears to “break in half” when the user clicks the back button and returns to activity <code class="highlighter-rouge">A</code>.</li>
<li>The enter and return shared element transitions both use a <code class="highlighter-rouge">ChangeImageTransform</code>, causing the <code class="highlighter-rouge">ImageView</code> to be animated seamlessly between the two activities.</li>
</ul>
<p>You’ve probably also noticed the cool circular reveal animation that plays under the shared element during the transition. We will cover how this can be done in a future blog post. For now, let’s keep things simple and familiarize ourselves with the Activity and Fragment transition APIs.</p>
<h3 id="introducing-the-activity-transition-api">Introducing the Activity Transition API</h3>
<p>Creating a basic Activity transition is relatively easy using the new Lollipop APIs. Summarized below are the steps you must take in order to implement one in your application. In the posts that follow, we will go through much more advanced use-cases and examples, but for now the next two sections will serve as a good introduction:</p>
<ul>
<li>Enable the new transition APIs by requesting the <a href="http://developer.android.com/reference/android/view/Window.html#FEATURE_ACTIVITY_TRANSITIONS"><code class="highlighter-rouge">Window.FEATURE_ACTIVITY_TRANSITIONS</code></a> window feature in your called and calling Activities, either programatically or in your theme’s XML.<sup><a href="#footnote3" id="ref3">3</a></sup> Material-themed applications have this flag enabled by default.</li>
<li>Set <a href="https://developer.android.com/reference/android/view/Window.html#setExitTransition(android.transition.Transition)">exit</a> and <a href="https://developer.android.com/reference/android/view/Window.html#setEnterTransition(android.transition.Transition)">enter</a> content transitions for your calling and called activities respectively. Material-themed applications have their exit and enter content transitions set to <code class="highlighter-rouge">null</code> and <a href="https://developer.android.com/reference/android/transition/Fade.html"><code class="highlighter-rouge">Fade</code></a> respectively by default. If the <a href="https://developer.android.com/reference/android/view/Window.html#setReenterTransition(android.transition.Transition)">reenter</a> or <a href="https://developer.android.com/reference/android/view/Window.html#setReturnTransition(android.transition.Transition)">return</a> transitions are not explicitly set, the activity’s exit and enter content transitions respectively will be used in their place instead.</li>
<li>Set <a href="https://developer.android.com/reference/android/view/Window.html#setSharedElementExitTransition(android.transition.Transition)">exit</a> and <a href="https://developer.android.com/reference/android/view/Window.html#setSharedElementEnterTransition(android.transition.Transition)">enter</a> shared element transitions for your calling and called activities respectively. Material-themed applications have their shared element exit and enter transitions set to <a href="https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/res/res/transition/move.xml"><code class="highlighter-rouge">@android:transition/move</code></a> by default. If the <a href="https://developer.android.com/reference/android/view/Window.html#setSharedElementReenterTransition(android.transition.Transition)">reenter</a> or <a href="https://developer.android.com/reference/android/view/Window.html#setSharedElementReturnTransition(android.transition.Transition)">return</a> transitions are not explicitly set, the activity’s exit and enter shared element transitions respectively will be used in their place instead.</li>
<li>
<p>To start an Activity transition with content transitions and shared elements, call the <a href="http://developer.android.com/reference/android/app/Activity.html#startActivity%28android.content.Intent,%20android.os.Bundle%29"><code class="highlighter-rouge">startActivity(Context, Bundle)</code></a> method and pass the following <code class="highlighter-rouge">Bundle</code> as the second argument:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ActivityOptions.makeSceneTransitionAnimation(activity, pairs).toBundle();
</code></pre></div> </div>
<p>where <code class="highlighter-rouge">pairs</code> is an array of <code class="highlighter-rouge">Pair<View, String></code> objects listing the shared element views and names that you’d like to share between activities.<sup><a href="#footnote4" id="ref4">4</a></sup> Don’t forget to give your shared elements unique transition names, either <a href="https://developer.android.com/reference/android/view/View.html#setTransitionName(java.lang.String)">programatically</a> or in <a href="https://developer.android.com/reference/android/view/View.html#attr_android:transitionName">XML</a>. Otherwise, the transition will not work properly!</p>
</li>
<li>To programatically trigger a return transition, call <a href="https://developer.android.com/reference/android/app/Activity.html#finishAfterTransition()"><code class="highlighter-rouge">finishAfterTransition()</code></a> instead of <code class="highlighter-rouge">finish()</code>.</li>
<li>By default, material-themed applications have their enter/return content transitions started a tiny bit before their exit/reenter content transitions complete, creating a small overlap that makes the overall effect more seamless and dramatic. If you wish to explicitly disable this behavior, you can do so by calling the <a href="http://developer.android.com/reference/android/view/Window.html#setAllowEnterTransitionOverlap(boolean)"><code class="highlighter-rouge">setWindowAllowEnterTransitionOverlap()</code></a> and <a href="http://developer.android.com/reference/android/view/Window.html#setAllowReturnTransitionOverlap(boolean)"><code class="highlighter-rouge">setWindowAllowReturnTransitionOverlap()</code></a> methods or by setting the corresponding attributes in your theme’s XML.</li>
</ul>
<h3 id="introducing-the-fragment-transition-api">Introducing the Fragment Transition API</h3>
<p>If you are working with Fragment transitions, the API is similar with a few small differences:</p>
<ul>
<li>Content <a href="https://developer.android.com/reference/android/app/Fragment.html#setExitTransition(android.transition.Transition)">exit</a>, <a href="https://developer.android.com/reference/android/app/Fragment.html#setEnterTransition(android.transition.Transition)">enter</a>, <a href="https://developer.android.com/reference/android/app/Fragment.html#setReenterTransition(android.transition.Transition)">reenter</a>, and <a href="https://developer.android.com/reference/android/app/Fragment.html#setReturnTransition(android.transition.Transition)">return</a> transitions should be set by calling the corresponding methods in the <code class="highlighter-rouge">Fragment</code> class or as attributes in your Fragment’s XML tag.</li>
<li>Shared element <a href="https://developer.android.com/reference/android/app/Fragment.html#setSharedElementEnterTransition(android.transition.Transition)">enter</a> and <a href="https://developer.android.com/reference/android/app/Fragment.html#setSharedElementReturnTransition(android.transition.Transition)">return</a> transitions should be set by calling the corresponding methods in the <code class="highlighter-rouge">Fragment</code> class or as attributes in your Fragment’s XML.</li>
<li>Whereas Activity transitions are triggered by explicit calls to <code class="highlighter-rouge">startActivity()</code> and <code class="highlighter-rouge">finishAfterTransition()</code>, Fragment transitions are triggered automatically when a fragment is added, removed, attached, detached, shown, or hidden by a <code class="highlighter-rouge">FragmentTransaction</code>.</li>
<li>Shared elements should be specified as part of the <code class="highlighter-rouge">FragmentTransaction</code> by calling the <a href="https://developer.android.com/reference/android/app/FragmentTransaction.html#addSharedElement(android.view.View,%20java.lang.String)"><code class="highlighter-rouge">addSharedElement(View, String)</code></a> method before the transaction is committed.</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<p>In this post, we have only given a brief introduction to the new Activitiy and Fragment transition APIs. However, as we will see in the next few posts having a solid understanding of the basics will significantly speed up the development process in the long-run, especially when it comes to writing custom <code class="highlighter-rouge">Transition</code>s. In the posts that follow, we will cover content transitions and shared element transitions in even more depth and will obtain an even greater understanding of how Activity and Fragment transitions work under-the-hood.</p>
<p>As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful!</p>
<hr class="footnote-divider" />
<p><sup id="footnote1">1</sup> If you want to try the example out yourself, the XML layout code can be found <a href="https://gist.github.com/alexjlockwood/a96781b876138c37e88e">here</a>. <a href="#ref1" title="Jump to footnote 1.">↩</a></p>
<p><sup id="footnote2">2</sup> It might look like the views in <code class="highlighter-rouge">A</code> are fading in/out of the screen at first, but what you are really seeing is activity <code class="highlighter-rouge">B</code> fading in/out of the screen <em>on top of activity <code class="highlighter-rouge">A</code></em>. The views in activity <code class="highlighter-rouge">A</code> are not actually animating during this time. You can adjust the duration of the background fade by calling <a href="http://developer.android.com/reference/android/view/Window.html#setTransitionBackgroundFadeDuration(long)"><code class="highlighter-rouge">setTransitionBackgroundFadeDuration()</code></a> on the called activity’s <code class="highlighter-rouge">Window</code>. <a href="#ref2" title="Jump to footnote 2.">↩</a></p>
<p><sup id="footnote3">3</sup> For an explanation describing the differences between the <code class="highlighter-rouge">FEATURE_ACTIVITY_TRANSITIONS</code> and <code class="highlighter-rouge">FEATURE_CONTENT_TRANSITIONS</code> window feature flags, see <a href="http://stackoverflow.com/q/28975840/844882">this StackOverflow post</a>. <a href="#ref3" title="Jump to footnote 3.">↩</a></p>
<p><sup id="footnote4">4</sup> To start an Activity transition with content transitions <em>but no shared elements</em>, you can create the <code class="highlighter-rouge">Bundle</code> by calling <code class="highlighter-rouge">ActivityOptions.makeSceneTransitionAnimation(activity).toBundle()</code>. To disable content transitions and shared element transitions entirely, don’t create a <code class="highlighter-rouge">Bundle</code> object at all—just pass <code class="highlighter-rouge">null</code> instead. <a href="#ref4" title="Jump to footnote 4.">↩</a></p>
Thread Scheduling in Androidhttps://www.androiddesignpatterns.com/2014/01/thread-scheduling-in-android.html2014-01-13T00:00:00+00:002014-01-13T00:00:00+00:00<p>This post will give an overview of how thread scheduling works in Android, and will briefly
demonstrate how to explicitly set
<a href="http://developer.android.com/reference/android/os/Process.html">thread priorities</a>
yourself to ensure that your application remains responsive even as multiple threads
run in the background.</p>
<p>This post will give an overview of how thread scheduling works in Android, and will briefly
demonstrate how to explicitly set
<a href="http://developer.android.com/reference/android/os/Process.html">thread priorities</a>
yourself to ensure that your application remains responsive even as multiple threads
run in the background.</p>
<p>For those who are unfamiliar with the term, a <em>thread scheduler</em> is the part of the operating system
in charge of deciding which threads in the system should run, when, and for how long. Android’s thread
scheduler uses two main factors to determine how threads are scheduled across the entire
system: <em>nice values</em> and <em>cgroups</em>.</p>
<!--more-->
<h3 id="nice-values">Nice values</h3>
<p>Similar to how they are used in Linux’s completely fair scheduling policy, <em>nice values</em> in Android
are used as a measure of a thread’s priority. Threads with a higher nice value (i.e., lower priority,
as in they are being “nice” to other threads in the system) will run less often than
those with lower nice values (i.e., higher priority). The two most important of these are the
<a href="http://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_DEFAULT">default</a>
and <a href="http://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_BACKGROUND">background</a>
thread priorities. Intuitively, thread priorities should be chosen
inverse-proportionally to the amount of work the thread is expected to do: the more work the
thread will do, the less favorable thread priority it should get so that it doesn’t
starve the system. For this reason, user interface threads (such as the main thread of a foreground <code class="highlighter-rouge">Activity</code>)
are typically given a default priority, whereas background threads (such as a thread executing an <code class="highlighter-rouge">AsyncTask</code>)
are typically given a background priority.</p>
<p>Nice values are theoretically important because they help reduce background work that might otherwise
interrupt the user interface. In practice, however, they alone are not sufficient. For example, consider
twenty background threads and a single foreground thread driving the UI. Despite their low
individual priorities, collectively the twenty background threads will still likely impact the performance
of the single foreground thread, resulting in lag and hurting the user experience. Since at any given moment
several applications could potentially have background threads waiting to run, the Android OS
must somehow address these scenarios. Enter <em>cgroups</em>.</p>
<h3 id="cgroups">Cgroups</h3>
<p>To address this problem, Android enforces an even stricter foreground vs. background scheduling policy
using Linux <a href="http://en.wikipedia.org/wiki/Cgroups"><em>cgroups</em></a> (control groups). Threads with
background priorities are implicitly moved into a background cgroup, where they are
limited to only a small percentage<sup><a href="#footnote1" id="ref1">1</a></sup> of the available
CPU if threads in other groups are busy. This separation allows background threads to make some
forward progress, without having enough of an impact on the foreground threads to be visible
to the user.</p>
<p>In addition to automatically assigning low-priority threads to the background cgroup, Android ensures that all
threads belonging to applications not currently running in the foreground are implicitly moved to the background cgroup as well. This automatic assignment of application threads to cgroups ensures that the current foreground
application thread will always be the priority, regardless of how many applications are running in the background.</p>
<h3 id="setting-priorities-with-processsetthreadpriorityint">Setting Priorities with <code class="highlighter-rouge">Process#setThreadPriority(int)</code></h3>
<p>For the most part, the Android APIs already assign worker threads a background priority for you
(for example, see the source code for
<a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/HandlerThread.java"><code class="highlighter-rouge">HandlerThread</code></a>
and <a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/AsyncTask.java"><code class="highlighter-rouge">AsyncTask</code></a>). It is important to remember, however, that this will not always be the case.
<code class="highlighter-rouge">Thread</code>s and <code class="highlighter-rouge">ExecutorService</code>s that are instantiated on the main UI thread, for example,
will inherit a default, foreground priority, making lag more likely and possibly hurting
the application’s performance. In these cases, you should <em>always</em> remember to set the thread’s
priority by calling
<a href="https://developer.android.com/reference/android/os/Process.html#setThreadPriority(int)"><code class="highlighter-rouge">Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)</code></a>
before the <code class="highlighter-rouge">Thread</code> is run. Doing so is straightforward, as shown in the example below:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="n">Thread</span><span class="o">(</span><span class="k">new</span> <span class="n">Runnable</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
<span class="n">Process</span><span class="o">.</span><span class="na">setThreadPriority</span><span class="o">(</span><span class="n">Process</span><span class="o">.</span><span class="na">THREAD_PRIORITY_BACKGROUND</span><span class="o">);</span>
<span class="c1">// ...</span>
<span class="o">}</span>
<span class="o">}).</span><span class="na">start</span><span class="o">();</span>
</code></pre></div></div>
<p>As always, thanks for reading, and leave a comment if you have any questions. Don’t forget to +1 this blog post too!</p>
<hr class="footnote-divider" />
<p><sup id="footnote1">1</sup> This percentage was 5% at the time of this writing, though it is possible that this value could change in the future. As of Android 4.4.2, cgroup mount points are created and initialized at boot-up in <a href="https://android.googlesource.com/platform/system/core/+/android-sdk-4.4.2_r1/rootdir/init.rc"><code class="highlighter-rouge">/system/core/rootdir/init.rc</code></a> (see lines 100-123). <a href="#ref1" title="Jump to footnote 1.">↩</a></p>
Redesigning Android Design Patternshttps://www.androiddesignpatterns.com/2014/01/redesigning-android-design-patterns.html2014-01-08T00:00:00+00:002014-03-11T00:00:00+00:00<p>A couple weeks ago, I began the ambitious task of rewriting this blog from scratch.
Today, I’m happy to introduce a brand new look: one that is <em>cleaner</em>, <em>faster</em>, and more
<em>responsive</em>.</p>
<p>A couple weeks ago, I began the ambitious task of rewriting this blog from scratch.
Today, I’m happy to introduce a brand new look: one that is <em>cleaner</em>, <em>faster</em>, and more
<em>responsive</em>.</p>
<p>Several of the major changes are listed below. If this is your first time visiting this blog, you can find the old
version of the site <a href="http://androiddesignpatterns.blogspot.com">here</a> to use as a reference.</p>
<!--more-->
<ul>
<li>
<p><strong>Goodbye Blogger, hello Jekyll.</strong> I’ve never been a huge fan of Blogger and was eager to
try something new. After a bit of research I decided to give <a href="http://jekyllrb.com/">Jekyll</a>
a shot. Unlike Blogger,
which dynamically parses content and templates on each request, Jekyll generates the entire
website once beforehand to serve statically and is much more efficient as a result. It’s a bit
under-documented and I’m not a huge fan of <a href="https://github.com/Shopify/liquid">Liquid</a>
(the templating language Jekyll uses to process templates), but other than that I have no complaints.
I’d take Jekyll over Blogger any day.</p>
</li>
<li>
<p><strong>100% open-source.</strong> The source code powering this blog can
be found on <a href="https://github.com/alexjlockwood/alexjlockwood.github.io">GitHub</a>, and may be used
by others as the basis of their own blogging templates under the
<a href="https://github.com/alexjlockwood/alexjlockwood.github.io/blob/master/README.md#license-and-copyright">MIT license</a>
(with the exception of the contents of the actual posts, of course :P).
Given that Android Design Patterns wouldn’t even exist without Android—one of the largest open-source
projects in the world—making this blog 100% open-source seemed fitting. Another cool implication of an entirely
open-source blog is that readers can propose corrections themselves by submitting pull requests
to GitHub.</p>
</li>
<li>
<p><strong>Faster page load times.</strong> Check out the <a href="http://androiddesignpatterns.blogspot.com">old version</a> of this blog
and see for yourself! <a href="https://developers.google.com/speed/pagespeed/">Page Speed</a> reports an increase from 65/100 to 89/100 for mobile
devices and 86/100 to 95/100 for desktop computers.</p>
</li>
<li>
<p><strong>Clean, responsive, and mobile-friendly.</strong> First of all, I’d like to thank <a href="https://plus.google.com/116871425473751000037">+Shannon Lee</a>
for coming up with most of the new design. I consider myself a beginner at web design, so this couldn’t have been done without her!
That said, I definitely recommend checking out the site on your phone or tablet, as it’s one of my favorite
aspects of the new design. Below is a comparison of the old vs. new versions of the site on a Nexus 7:</p>
<div style="max-width:600px;margin:0 auto;">
<div style="overflow:hidden;width:100%;display:block;">
<a href="/assets/images/posts/2014/01/08/adp-n7-screenshot-before.png">
<img style="display:block;float:left;max-width:300px;width:50%;position:relative;" src="/assets/images/posts/2014/01/08/adp-n7-screenshot-before-resized.png" alt="Website design before." /></a>
<a href="/assets/images/posts/2014/01/08/adp-n7-screenshot-after.png">
<img style="display:block;float:left;max-width:300px;width:50%;position:relative;" src="/assets/images/posts/2014/01/08/adp-n7-screenshot-after-resized.png" alt="Website design after." /></a>
</div>
</div>
</li>
<li>
<p><strong>Disqus comments.</strong> Picking a third-party commenting platform to use was difficult, as there weren’t
many options to choose from. I ended up choosing <a href="http://disqus.com/">Disqus</a>, as it was one of the few commenting systems that I could find
that would correctly and reliably import my old comments from Blogger (spam detection is also a plus). One of the consequences of
the migration, however, is that all old threaded comments are now unthreaded, meaning that most of the old
comments are a bit of a mess right now. I plan on manually cleaning up these at some point in
the future. Going forward, all new comments will thread normally.</p>
</li>
</ul>
<p>Let me know what you think of the new design in the comments below! Make sure to also leave any
suggestions, criticisms, or feature requests too. The design will continue to be refined over time until
it’s perfect. :)</p>
Fragment Transactions & Activity State Losshttps://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html2013-08-20T00:00:00+00:002014-01-08T00:00:00+00:00<p>The following stack trace and exception message has plagued StackOverflow
ever since Honeycomb’s initial release:</p>
<p>The following stack trace and exception message has plagued StackOverflow
ever since Honeycomb’s initial release:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
</code></pre></div></div>
<p>This post will explain <em>why</em> and <em>when</em> this exception is thrown, and will
conclude with several suggestions that will help ensure it never crashes your
application again.</p>
<!--more-->
<h3 id="why-was-the-exception-thrown">Why was the exception thrown?</h3>
<p>The exception was thrown because you attempted to commit a <code class="highlighter-rouge">FragmentTransaction</code>
after the activity’s state had been saved, resulting in a phenomenon known as <em>Activity
state loss</em>. Before we get into the details of what this actually means, however, let’s
first take a look at what happens under-the-hood when <code class="highlighter-rouge">onSaveInstanceState()</code> is
called. As I discussed in my last post about
<a href="/2013/08/binders-death-recipients.html">Binders
& Death Recipients</a>, Android applications have very little control over their destiny
within the Android runtime environment. The Android system has the power to terminate processes
at any time to free up memory, and background activities may be killed with little to no warning
as a result. To ensure that this sometimes erratic behavior remains hidden from the user, the
framework gives each Activity a chance to save its state by calling its <code class="highlighter-rouge">onSaveInstanceState()</code>
method before making the Activity vulnerable to destruction. When the saved state is later
restored, the user will be given the perception that they are seamlessly switching between
foreground and background activities, regardless of whether or not the Activity had been
killed by the system.</p>
<p>When the framework calls <code class="highlighter-rouge">onSaveInstanceState()</code>, it passes the method a
<code class="highlighter-rouge">Bundle</code> object for the Activity to use to save its state, and the Activity
records in it the state of its dialogs, fragments, and views. When the method returns,
the system parcels the <code class="highlighter-rouge">Bundle</code> object across a Binder interface to the
System Server process, where it is safely stored away. When the system later decides
to recreate the Activity, it sends this same <code class="highlighter-rouge">Bundle</code> object back to the
application, for it to use to restore the Activity’s old state.</p>
<p>So why then is the exception thrown? Well, the problem stems from the fact that
these <code class="highlighter-rouge">Bundle</code> objects represent a snapshot of an Activity at the moment
<code class="highlighter-rouge">onSaveInstanceState()</code> was called, and nothing more. That means when you call
<code class="highlighter-rouge">FragmentTransaction#commit()</code> after <code class="highlighter-rouge">onSaveInstanceState()</code> is
called, the transaction won’t be remembered because it was never recorded as part of the
Activity’s state in the first place. From the user’s point of view, the transaction will
appear to be lost, resulting in accidental UI state loss. In order to protect the user
experience, Android avoids state loss at all costs, and simply throws an
<code class="highlighter-rouge">IllegalStateException</code> whenever it occurs.</p>
<h3 id="when-is-the-exception-thrown">When is the exception thrown?</h3>
<p>If you’ve encountered this exception before, you’ve probably noticed that the moment
when it is thrown is slightly inconsistent across different platform versions. For
example, you probably found that older devices tended to throw the exception less
frequently, or that your application was more likely to crash when using the support
library than when using the official framework classes. These slight inconsistencies
have led many to assume that the support library is buggy and can’t be trusted.
These assumptions, however, are generally not true.</p>
<p>The reason why these slight inconsistencies exist stems from a significant change to
the Activity lifecycle that was made in Honeycomb. Prior to Honeycomb, activities
were not considered killable until after they had been paused, meaning that
<code class="highlighter-rouge">onSaveInstanceState()</code> was called immediately before <code class="highlighter-rouge">onPause()</code>.
Beginning with Honeycomb, however, Activities are considered to be killable only
after they have been <em>stopped</em>, meaning that <code class="highlighter-rouge">onSaveInstanceState()</code>
will now be called before <code class="highlighter-rouge">onStop()</code> instead of immediately before
<code class="highlighter-rouge">onPause()</code>. These differences are summarized in the table below:</p>
<table>
<thead>
<tr>
<th> </th>
<th>pre-Honeycomb</th>
<th>post-Honeycomb</th>
</tr>
</thead>
<tbody>
<tr>
<td>Activities can be killed before <code class="highlighter-rouge">onPause()</code>?</td>
<td>NO</td>
<td>NO</td>
</tr>
<tr>
<td>Activities can be killed before <code class="highlighter-rouge">onStop()</code>?</td>
<td>YES</td>
<td>NO</td>
</tr>
<tr>
<td><code class="highlighter-rouge">onSaveInstanceState(Bundle)</code> is guaranteed to be called before…</td>
<td><code class="highlighter-rouge">onPause()</code></td>
<td><code class="highlighter-rouge">onStop()</code></td>
</tr>
</tbody>
</table>
<p>As a result of the slight changes that were made to the Activity lifecycle, the support
library sometimes needs to alter its behavior depending on the platform version. For
example, on Honeycomb devices and above, an exception will be thrown each and every
time a <code class="highlighter-rouge">commit()</code> is called after <code class="highlighter-rouge">onSaveInstanceState()</code>
to warn the developer that state loss has occurred. However, throwing an exception
every time this happened would be too restrictive on pre-Honeycomb devices, which
have their <code class="highlighter-rouge">onSaveInstanceState()</code> method called much earlier in the
Activity lifecycle and are more vulnerable to accidental state loss as a result.
The Android team was forced to make a compromise: for better inter-operation with
older versions of the platform, older devices would have to live with the accidental
state loss that might result between <code class="highlighter-rouge">onPause()</code> and <code class="highlighter-rouge">onStop()</code>.
The support library’s behavior across the two platforms is summarized in the table below:</p>
<table>
<thead>
<tr>
<th> </th>
<th>pre-Honeycomb</th>
<th>post-Honeycomb</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="highlighter-rouge">commit()</code> before <code class="highlighter-rouge">onPause()</code></td>
<td>OK</td>
<td>OK</td>
</tr>
<tr>
<td><code class="highlighter-rouge">commit()</code> between <code class="highlighter-rouge">onPause()</code> and <code class="highlighter-rouge">onStop()</code></td>
<td>STATE LOSS</td>
<td>OK</td>
</tr>
<tr>
<td><code class="highlighter-rouge">commit()</code> after <code class="highlighter-rouge">onStop()</code></td>
<td>EXCEPTION</td>
<td>EXCEPTION</td>
</tr>
</tbody>
</table>
<h3 id="how-to-avoid-the-exception">How to avoid the exception?</h3>
<p>Avoiding Activity state loss becomes a whole lot easier once you understand what is actually
going on. If you’ve made it this far in the post, hopefully you understand a little better
how the support library works and why it is so important to avoid state loss in your applications.
In case you’ve referred to this post in search of a quick fix, however, here are some suggestions
to keep in the back of your mind as you work with <code class="highlighter-rouge">FragmentTransaction</code>s in your applications:</p>
<ul>
<li>
<p><strong>Be careful when committing transactions inside Activity lifecycle methods.</strong>
A large majority of applications will only ever commit transactions the very first
time <code class="highlighter-rouge">onCreate()</code> is called and/or in response to user input, and will
never face any problems as a result. However, as your transactions begin to venture
out into the other Activity lifecycle methods, such as <code class="highlighter-rouge">onActivityResult()</code>,
<code class="highlighter-rouge">onStart()</code>, and <code class="highlighter-rouge">onResume()</code>, things can get a little tricky.
For example, you should not commit transactions inside the <code class="highlighter-rouge">FragmentActivity#onResume()</code>
method, as there are some cases in which the method can be called before the
activity’s state has been restored (see the
<a href="http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume()">documentation</a>
for more information). If your application requires committing a transaction in
an Activity lifecycle method other than <code class="highlighter-rouge">onCreate()</code>, do it in either
<code class="highlighter-rouge">FragmentActivity#onResumeFragments()</code> or <code class="highlighter-rouge">Activity#onPostResume()</code>.
These two methods are guaranteed to be called after the Activity has been restored to its
original state, and therefore avoid the possibility of state loss all together.
(As an example of how this can be done, check out my answer to
<a href="http://stackoverflow.com/q/16265733/844882">this StackOverflow question</a> for
some ideas on how to commit <code class="highlighter-rouge">FragmentTransaction</code>s in response to calls
made to the <code class="highlighter-rouge">Activity#onActivityResult()</code> method).</p>
</li>
<li><strong>Avoid performing transactions inside asynchronous callback methods.</strong> This
includes commonly used methods such as <code class="highlighter-rouge">AsyncTask#onPostExecute()</code> and
<code class="highlighter-rouge">LoaderManager.LoaderCallbacks#onLoadFinished()</code>. The problem with
performing transactions in these methods is that they have no knowledge of the
current state of the Activity lifecycle when they are called. For example,
consider the following sequence of events:
<ol>
<li>An activity executes an <code class="highlighter-rouge">AsyncTask</code>.</li>
<li>The user presses the “Home” key, causing the activity’s <code class="highlighter-rouge">onSaveInstanceState()</code>
and <code class="highlighter-rouge">onStop()</code> methods to be called.</li>
<li>The <code class="highlighter-rouge">AsyncTask</code> completes and <code class="highlighter-rouge">onPostExecute()</code> is called, unaware that the
Activity has since been stopped.</li>
<li>A <code class="highlighter-rouge">FragmentTransaction</code> is committed inside the <code class="highlighter-rouge">onPostExecute()</code> method, causing
an exception to be thrown.</li>
</ol>
<p>In general, the best way to avoid the exception in these cases is to simply avoid
committing transactions in asynchronous callback methods all together. Google
engineers seem to agree with this belief as well. According to
<a href="https://groups.google.com/d/msg/android-developers/dXZZjhRjkMk/QybqCW5ukDwJ">this post</a>
on the Android Developers group, the Android team considers the major shifts in UI
that can result from committing <code class="highlighter-rouge">FragmentTransaction</code>s from within
asynchronous callback methods to be bad for the user experience. If your application
requires performing the transaction inside these callback methods and there is no
easy way to guarantee that the callback won’t be invoked after <code class="highlighter-rouge">onSaveInstanceState()</code>,
you may have to resort to using <code class="highlighter-rouge">commitAllowingStateLoss()</code> and
dealing with the state loss that might occur. (See also these two StackOverflow
posts for additional hints, <a href="http://stackoverflow.com/q/8040280/844882">here</a>
and <a href="http://stackoverflow.com/q/7992496/844882">here</a>).</p>
</li>
<li><strong>Use <code class="highlighter-rouge">commitAllowingStateLoss()</code> only as a last resort.</strong> The only
difference between calling <code class="highlighter-rouge">commit()</code> and <code class="highlighter-rouge">commitAllowingStateLoss()</code>
is that the latter will not throw an exception if state loss occurs. Usually you don’t
want to use this method because it implies that there is a possibility that state loss
could happen. The better solution, of course, is to write your application so that
<code class="highlighter-rouge">commit()</code> is guaranteed to be called before the activity’s state has been
saved, as this will result in a better user experience. Unless the possibility of
state loss can’t be avoided, <code class="highlighter-rouge">commitAllowingStateLoss()</code> should not be used.</li>
</ul>
<p>Hopefully these tips will help you resolve any issues you have had with this exception
in the past. If you are still having trouble, post a question on
<a href="http://stackoverflow.com">StackOverflow</a> and post a link in a comment below
and I can take a look. :)</p>
<p>As always, thanks for reading, and leave a comment if you have any questions.
Don’t forget to +1 this blog and share this post on Google+ if you found it interesting!</p>
Binders & Death Recipientshttps://www.androiddesignpatterns.com/2013/08/binders-death-recipients.html2013-08-05T00:00:00+00:002014-12-04T00:00:00+00:00<blockquote>
<p>Note: before you begin, make sure you’ve read my <a href="/2013/07/binders-window-tokens.html">previous post</a>
about Binder tokens!</p>
</blockquote>
<blockquote>
<p>Note: before you begin, make sure you’ve read my <a href="/2013/07/binders-window-tokens.html">previous post</a>
about Binder tokens!</p>
</blockquote>
<p>Since the very beginning, Android’s central focus has been the ability to multitask. In order to achieve it,
Android takes a unique approach by allowing multiple applications to run at the same time. Applications are
never explicitly closed by the user, but are instead left running at a low priority to be killed by the system
when memory is low. This ability to keep processes waiting in the background speeds up app-switching later
down the line.</p>
<p>Developers learn early on that the key to how Android handles applications in this way is that <strong>processes
aren’t shut down cleanly</strong>. Android doesn’t rely on applications being well-written and responsive to
polite requests to exit. Rather, it brutally force-kills them without warning, allowing the kernel to
immediately reclaim resources associated with the process. This helps prevent serious out of memory situations
and gives Android total control over misbehaving apps that are negatively impacting the system. For this reason,
there is no guarantee that any user-space code (such as an Activity’s <code class="highlighter-rouge">onDestroy()</code> method) will
ever be executed when an application’s process goes away.</p>
<!--more-->
<p>Considering the limited memory available in mobile environments, this approach seems promising. However, there
is still one issue that needs to be addressed: <em>how should the system detect an application’s death so that
it can quickly clean up its state</em>? When an application dies, its state will be spread over dozens of system
services (the Activity Manager, Window Manager, Power Manager, etc.) and several different processes. These
system services need to be notified immediately when an application dies so that they can clean up its state
and maintain an accurate snapshot of the system. Enter death recipients.</p>
<h3 id="death-recipients">Death Recipients</h3>
<p>As it turns out, this task is made easy using the <code class="highlighter-rouge">Binder</code>’s “link-to-death” facility, which allows a process to get a callback when another process hosting a binder object goes away. In Android, any process can receive a notification when another process dies by taking the following steps:</p>
<ol>
<li>
<p>First, the process creates a <a href="http://developer.android.com/reference/android/os/IBinder.DeathRecipient.html"><code class="highlighter-rouge">DeathRecipient</code></a>
callback object containing the code to be executed when the death notification arrives.</p>
</li>
<li>
<p>Next, it obtains a reference to a <code class="highlighter-rouge">Binder</code> object that lives in another process and calls its
<a href="http://developer.android.com/reference/android/os/Binder.html#linkToDeath(android.os.IBinder.DeathRecipient, int)"><code class="highlighter-rouge">linkToDeath(IBinder.DeathRecipient recipient, int flags)</code></a>,
passing the <code class="highlighter-rouge">DeathRecipient</code> callback object as the first argument.</p>
</li>
<li>
<p>Finally, it waits for the process hosting the <code class="highlighter-rouge">Binder</code> object to die. When the Binder kernel
driver detects that the process hosting the <code class="highlighter-rouge">Binder</code> is gone, it will notify the registered
<code class="highlighter-rouge">DeathRecipient</code> callback object by calling its
<a href="http://developer.android.com/reference/android/os/IBinder.DeathRecipient.html#binderDied()"><code class="highlighter-rouge">binderDied()</code></a>
method.</p>
</li>
</ol>
<p>Analyzing the source code once again gives some insight into how this pattern is used inside the framework.
Consider an example application that (similar to the example given in my <a href="/2013/07/binders-window-tokens.html">previous post</a>)
acquires a wake lock in <code class="highlighter-rouge">onCreate()</code>, but is abruptly killed by the system before it is
able to release the wake lock in <code class="highlighter-rouge">onDestroy()</code>. How and when will the
<a href="https://android.googlesource.com/platform/frameworks/base/+/android-4.3_r2.1/services/java/com/android/server/power/PowerManagerService.java"><code class="highlighter-rouge">PowerManagerService</code></a>
be notified so that it can quickly release the wake lock before wasting the device’s battery? As you might
expect, the <code class="highlighter-rouge">PowerManagerService</code> achieves this by registering a <code class="highlighter-rouge">DeathRecipient</code>
(note that some of the source code has been cleaned up for the sake of brevity):</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* The global power manager system service. Application processes
* interact with this class remotely through the PowerManager class.
*
* @see frameworks/base/services/java/com/android/server/power/PowerManagerService.java
*/</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">PowerManagerService</span> <span class="kd">extends</span> <span class="n">IPowerManager</span><span class="o">.</span><span class="na">Stub</span> <span class="o">{</span>
<span class="c1">// List of all wake locks acquired by applications.</span>
<span class="kd">private</span> <span class="n">List</span><span class="o"><</span><span class="n">WakeLock</span><span class="o">></span> <span class="n">mWakeLocks</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">WakeLock</span><span class="o">>();</span>
<span class="nd">@Override</span> <span class="c1">// Binder call</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">acquireWakeLock</span><span class="o">(</span><span class="n">IBinder</span> <span class="n">token</span><span class="o">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="o">,</span> <span class="n">String</span> <span class="n">tag</span><span class="o">)</span> <span class="o">{</span>
<span class="n">WakeLock</span> <span class="n">wakeLock</span> <span class="o">=</span> <span class="k">new</span> <span class="n">WakeLock</span><span class="o">(</span><span class="n">token</span><span class="o">,</span> <span class="n">flags</span><span class="o">,</span> <span class="n">tag</span><span class="o">);</span>
<span class="c1">// Register to receive a notification when the process hosting </span>
<span class="c1">// the token goes away.</span>
<span class="n">token</span><span class="o">.</span><span class="na">linkToDeath</span><span class="o">(</span><span class="n">wakeLock</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="c1">// Acquire the wake lock by adding it as an entry to the list.</span>
<span class="n">mWakeLocks</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">wakeLock</span><span class="o">);</span>
<span class="n">updatePowerState</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Override</span> <span class="c1">// Binder call</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">releaseWakeLock</span><span class="o">(</span><span class="n">IBinder</span> <span class="n">token</span><span class="o">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="n">findWakeLockIndex</span><span class="o">(</span><span class="n">token</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">index</span> <span class="o"><</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// The client app has sent us an invalid token, so ignore</span>
<span class="c1">// the request.</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// Release the wake lock by removing its entry from the list.</span>
<span class="n">WakeLock</span> <span class="n">wakeLock</span> <span class="o">=</span> <span class="n">mWakeLocks</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">index</span><span class="o">);</span>
<span class="n">mWakeLocks</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">index</span><span class="o">);</span>
<span class="c1">// We no longer care about receiving death notifications.</span>
<span class="n">wakeLock</span><span class="o">.</span><span class="na">mToken</span><span class="o">.</span><span class="na">unlinkToDeath</span><span class="o">(</span><span class="n">wakeLock</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="n">updatePowerState</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="nf">findWakeLockIndex</span><span class="o">(</span><span class="n">IBinder</span> <span class="n">token</span><span class="o">)</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">mWakeLocks</span><span class="o">.</span><span class="na">size</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mWakeLocks</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">).</span><span class="na">mToken</span> <span class="o">==</span> <span class="n">token</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">i</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/**
* Represents a wake lock that has been acquired by an application.
*/</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">WakeLock</span> <span class="kd">implements</span> <span class="n">IBinder</span><span class="o">.</span><span class="na">DeathRecipient</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="n">IBinder</span> <span class="n">mToken</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">mFlags</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">mTag</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">WakeLock</span><span class="o">(</span><span class="n">IBinder</span> <span class="n">token</span><span class="o">,</span> <span class="n">inf</span> <span class="n">flags</span><span class="o">,</span> <span class="n">String</span> <span class="n">tag</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mToken</span> <span class="o">=</span> <span class="n">token</span><span class="o">;</span>
<span class="n">mFlags</span> <span class="o">=</span> <span class="n">flags</span><span class="o">;</span>
<span class="n">mTag</span> <span class="o">=</span> <span class="n">tag</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">binderDied</span><span class="o">()</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="n">mWakeLocks</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">index</span> <span class="o"><</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// The token's hosting process was killed before it was</span>
<span class="c1">// able to explicitly release the wake lock, so release </span>
<span class="c1">// it for them.</span>
<span class="n">mWakeLocks</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">index</span><span class="o">);</span>
<span class="n">updatePowerState</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="cm">/**
* Updates the global power state of the device.
*/</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">updatePowerState</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// ...</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The code might seem a little dense at first, but the concept is simple:</p>
<ul>
<li>
<p>When the application requests to acquire a wake lock, the power manager service’s
<code class="highlighter-rouge">acquireWakeLock()</code> method is called. The power manager service registers
the wake lock for the application, and also links to the death of the wake lock’s
unique <code class="highlighter-rouge">Binder</code> token so that it can get notified if the application process
is abruptly killed.</p>
</li>
<li>
<p>When the application requests to release a wake lock, the power manager service’s
<code class="highlighter-rouge">releaseWakeLock()</code> method is called. The power manager service releases
the wake lock for the application, and also unlinks to the death of the wake lock’s
unique <code class="highlighter-rouge">Binder</code> token (as it no longer cares about getting notified when
the application process dies).</p>
</li>
<li>
<p>When the application is abruptly killed before the wake lock is explicitly released,
the Binder kernel driver notices that the wake lock’s Binder token has been linked
to the death of the application process. The Binder kernel driver quickly dispatches
a death notification to the registered death recipient’s <code class="highlighter-rouge">binderDied()</code>
method, which quickly releases the wake lock and updates the device’s power state.</p>
</li>
</ul>
<h3 id="examples-in-the-framework">Examples in the Framework</h3>
<p>The <code class="highlighter-rouge">Binder</code>’s link-to-death feature is an incredibly useful tool that is
used extensively by the framework’s system services. Here are some of the more
interesting examples:</p>
<ul>
<li>
<p>The window manager links to the death of the window’s
<a href="https://developer.android.com/reference/android/view/Window.Callback.html">callback interface</a>.
In the rare case that the application’s process is killed while its windows are still showing,
the window manager will receive a death notification callback, at which point it can clean up after
the application by closing its windows.</p>
</li>
<li>
<p>When an application binds to a remote service, the application links to the death of the binder
stub returned by the remote service’s <code class="highlighter-rouge">onBind()</code> method. If the remote service suddenly
dies, the registered death recipient’s <code class="highlighter-rouge">binderDied()</code> method is called, which contains
some clean up code, as well as the code that calls the
<a href="https://developer.android.com/reference/android/content/ServiceConnection.html#onServiceDisconnected(android.content.ComponentName)"><code class="highlighter-rouge">onServiceDisconnected(ComponentName)</code></a>
method (the source code illustrating how this is done is located
<a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/app/LoadedApk.java">here</a>).</p>
</li>
<li>
<p>Many other system services depend on the Binder’s link-to-death facility in order to ensure that
expensive resources aren’t leaked when an application process is forcefully killed. Some other examples are the
<a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/java/com/android/server/VibratorService.java"><code class="highlighter-rouge">VibratorService</code></a>,
<a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/java/com/android/server/LocationManagerService.java"><code class="highlighter-rouge">LocationManagerService</code></a>,
<a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/java/com/android/server/location/GpsLocationProvider.java"><code class="highlighter-rouge">GpsLocationProvider</code></a>,
and the <a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/java/com/android/server/wifi/WifiService.java"><code class="highlighter-rouge">WifiService</code></a>.</p>
</li>
</ul>
<h3 id="additional-reading">Additional Reading</h3>
<p>If you would like to learn more about <code class="highlighter-rouge">Binder</code>s and how they work at a deeper level, I’ve included
some links below. These articles were extremely helpful to me as I was writing these last two posts about
<code class="highlighter-rouge">Binder</code> tokens and <code class="highlighter-rouge">DeathRecipient</code>s, and I would strongly recommend reading them
if you get a chance!</p>
<ul>
<li><a href="https://lkml.org/lkml/2009/6/25/3">This post</a> is what initially got me interested in this
topic. Special thanks to <a class="g-profile" href="http://plus.google.com/105051985738280261832" target="_blank">+Dianne Hackborn</a>
for explaining this!</li>
<li><a href="http://www.nds.rub.de/media/attachments/files/2012/03/binder.pdf">A great paper</a> which
explains almost everything you need to know about Binders.</li>
<li><a href="http://events.linuxfoundation.org/images/stories/slides/abs2013_gargentas.pdf">These slides</a> are
another great Binder resource.</li>
<li><a href="https://plus.google.com/105051985738280261832/posts/ACaCokiLfqK">This Google+ post</a> talks about
how/why live wallpapers are given their own window.</li>
<li><a href="http://android-developers.blogspot.com/2010/04/multitasking-android-way.html">A great blog post</a>
discussing multitasking in Android.</li>
<li><a href="https://plus.google.com/105051985738280261832/posts/XAZ4CeVP6DC">This Google+ post</a> talks about
how windows are crucial in achieving secure and efficient graphics performance.</li>
<li><a href="http://shop.oreilly.com/product/0636920021094.do">This book</a> taught me a lot about how the
application framework works from an embedded systems point of view… and it taught me a couple of really cool
<code class="highlighter-rouge">adb</code> commands too!</li>
</ul>
<p>As always, thanks for reading, and leave a comment if you have any questions. Don’t forget to +1
this blog and share this post on Google+ if you found it interesting!</p>
Binders & Window Tokenshttps://www.androiddesignpatterns.com/2013/07/binders-window-tokens.html2013-07-31T00:00:00+00:002014-09-04T00:00:00+00:00<blockquote>
<p>Note: if you liked this post, be sure to read my second blog post about
<a href="/2013/08/binders-death-recipients.html">Binders & Death Recipients</a>
as well!</p>
</blockquote>
<blockquote>
<p>Note: if you liked this post, be sure to read my second blog post about
<a href="/2013/08/binders-death-recipients.html">Binders & Death Recipients</a>
as well!</p>
</blockquote>
<p>One of Android’s key design goals was to provide an open platform that doesn’t rely on a
central authority to verify that applications do what they claim. To achieve this, Android
uses application sandboxes and Linux process isolation to prevent applications from being
able to access the system or other applications in ways that are not controlled and secure.
This architecture was chosen with both developers and device users in mind: neither should
have to take extra steps to protect the device from malicious applications. Everything
should be taken care of automatically by the system.</p>
<p>For a long time I took this security for granted, not completely understanding how it was
actually enforced. But recently I became curious. What mechanism prevents me from, for
example, tricking the system into releasing a wake lock acquired by another application,
or from hiding another application’s windows from the screen? More generally, how do
Android’s core system services respond to requests made by third-party applications
in a way that is both efficient and secure?</p>
<!--more-->
<p>To my surprise, the answer to nearly all of my questions was pretty simple: <em>the Binder</em>.
Binders are the cornerstone of Android’s architecture; they abstract the low-level details
of IPC from the developer, allowing applications to easily talk to both the System Server
and others’ remote service components. But Binders also have a number of other cool features
that are used extensively throughout the system in a mix of clever ways, making it much
easier for the framework to address security issues. This blog post will cover one of
these features in detail, known as <em>Binder tokens</em>.</p>
<h3 id="binder-tokens">Binder Tokens</h3>
<p>An interesting property of <code class="highlighter-rouge">Binder</code> objects is that each instance maintains <strong>a unique
identity across all processes in the system</strong>, no matter how many process boundaries
it crosses or where it goes. This facility is provided by the Binder kernel driver, which
analyzes the contents of each Binder transaction and assigns a unique 32-bit integer
value to each <code class="highlighter-rouge">Binder</code> object it sees. To ensure that Java’s <code class="highlighter-rouge">==</code> operator adheres to
the Binder’s unique, cross-process object identity contract, a <code class="highlighter-rouge">Binder</code>’s object reference
is treated a little differently than those of other objects. Specifically, each <code class="highlighter-rouge">Binder</code>’s
object reference is assigned either,</p>
<ol>
<li>A <em>virtual memory address</em> pointing to a <code class="highlighter-rouge">Binder</code> object in the <em>same</em> process, or</li>
<li>A <em>unique 32-bit handle</em> (as assigned by the Binder kernel driver) pointing to the
<code class="highlighter-rouge">Binder</code>’s virtual memory address in a <em>different</em> process.</li>
</ol>
<p>The Binder kernel driver maintains a mapping of local addresses to remote Binder handles
(and vice versa) for each <code class="highlighter-rouge">Binder</code> it sees, and assigns each <code class="highlighter-rouge">Binder</code>’s object reference
its appropriate value to ensure that equality will behave as expected even in remote
processes.</p>
<p>The Binder’s unique object identity rules allow them to be used for a special purpose: as
<em>shared, security access tokens</em>.<sup><a href="#footnote1" id="ref1">1</a></sup> Binders are globally
unique, which means if you create one, nobody else can create one that appears equal to it.
For this reason, the application framework uses Binder tokens extensively in order to ensure
secure interaction between cooperating processes: a client can create a <code class="highlighter-rouge">Binder</code> object to
use as a token that can be shared with a server process, and the server can use it to validate
the client’s requests without there being anyway for others to spoof it.</p>
<p>Let’s see how this works through a simple example. Consider an application which makes a
request to the <code class="highlighter-rouge">PowerManager</code> to acquire (and later release) a wake lock:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* An example activity that acquires a wake lock in onCreate()
* and releases it in onDestroy().
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
<span class="kd">private</span> <span class="n">PowerManager</span><span class="o">.</span><span class="na">WakeLock</span> <span class="n">wakeLock</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">PowerManager</span> <span class="n">pm</span> <span class="o">=</span> <span class="o">(</span><span class="n">PowerManager</span><span class="o">)</span> <span class="n">getSystemService</span><span class="o">(</span><span class="n">Context</span><span class="o">.</span><span class="na">POWER_SERVICE</span><span class="o">);</span>
<span class="n">wakeLock</span> <span class="o">=</span> <span class="n">pm</span><span class="o">.</span><span class="na">newWakeLock</span><span class="o">(</span><span class="n">PowerManager</span><span class="o">.</span><span class="na">PARTIAL_WAKE_LOCK</span><span class="o">,</span> <span class="s">"My Tag"</span><span class="o">);</span>
<span class="n">wakeLock</span><span class="o">.</span><span class="na">acquire</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onDestroy</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onDestroy</span><span class="o">();</span>
<span class="n">wakeLock</span><span class="o">.</span><span class="na">release</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Inspecting the <code class="highlighter-rouge">PowerManager</code>
<a href="https://android.googlesource.com/platform/frameworks/base/+/android-4.3_r2.1/core/java/android/os/PowerManager.java">source code</a>
helps us understand what’s happening under the hood (note that some of the source code has been
cleaned-up for the sake of brevity):</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* The interface that applications use to talk to the global power manager
* system service.
*
* @see frameworks/base/core/java/android/os/PowerManager.java
*/</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">PowerManager</span> <span class="o">{</span>
<span class="c1">// Our handle on the global power manager service.</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">IPowerManager</span> <span class="n">mService</span><span class="o">;</span>
<span class="kd">public</span> <span class="n">WakeLock</span> <span class="nf">newWakeLock</span><span class="o">(</span><span class="kt">int</span> <span class="n">levelAndFlags</span><span class="o">,</span> <span class="n">String</span> <span class="n">tag</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">WakeLock</span><span class="o">(</span><span class="n">levelAndFlags</span><span class="o">,</span> <span class="n">tag</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">WakeLock</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">IBinder</span> <span class="n">mToken</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">mFlags</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">mTag</span><span class="o">;</span>
<span class="n">WakeLock</span><span class="o">(</span><span class="kt">int</span> <span class="n">flags</span><span class="o">,</span> <span class="n">String</span> <span class="n">tag</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Create a token that uniquely identifies this wake lock.</span>
<span class="n">mToken</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Binder</span><span class="o">();</span>
<span class="n">mFlags</span> <span class="o">=</span> <span class="n">flags</span><span class="o">;</span>
<span class="n">mTag</span> <span class="o">=</span> <span class="n">tag</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">acquire</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// Send the power manager service a request to acquire a wake</span>
<span class="c1">// lock for the application. Include the token as part of the</span>
<span class="c1">// request so that the power manager service can validate the</span>
<span class="c1">// application's identity when it requests to release the wake</span>
<span class="c1">// lock later on.</span>
<span class="n">mService</span><span class="o">.</span><span class="na">acquireWakeLock</span><span class="o">(</span><span class="n">mToken</span><span class="o">,</span> <span class="n">mFlags</span><span class="o">,</span> <span class="n">mTag</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">release</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// Send the power manager service a request to release the</span>
<span class="c1">// wake lock associated with 'mToken'.</span>
<span class="n">mService</span><span class="o">.</span><span class="na">releaseWakeLock</span><span class="o">(</span><span class="n">mToken</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>So what’s going on? Let’s walk through the code step-by-step:</p>
<ol>
<li>
<p>The client application requests an instance of the <code class="highlighter-rouge">PowerManager</code> class in <code class="highlighter-rouge">onCreate()</code>.
The <code class="highlighter-rouge">PowerManager</code> class provides an interface for the client application to talk to the global
<a href="https://android.googlesource.com/platform/frameworks/base/+/android-4.3_r2.1/services/java/com/android/server/power/PowerManagerService.java"><code class="highlighter-rouge">PowerManagerService</code></a>,
which runs in the System Server process and is in charge of managing the device’s power
state (i.e. determining the screen’s brightness, starting Daydreams, detecting when the
device is plugged into a dock, etc.).</p>
</li>
<li>
<p>The client application creates and acquires a wake lock in <code class="highlighter-rouge">onCreate()</code>. The <code class="highlighter-rouge">PowerManager</code>
sends the <code class="highlighter-rouge">WakeLock</code>’s unique <code class="highlighter-rouge">Binder</code> token as part of the <code class="highlighter-rouge">acquire()</code> request. When the
<code class="highlighter-rouge">PowerManagerService</code> receives the request, it holds onto the token for safe-keeping and
forces the device to remain awake, until…</p>
</li>
<li>
<p>The client application releases the wake lock in <code class="highlighter-rouge">onDestroy()</code>. The <code class="highlighter-rouge">PowerManager</code> sends
the <code class="highlighter-rouge">WakeLock</code>’s unique <code class="highlighter-rouge">Binder</code> token as part of the request. When the <code class="highlighter-rouge">PowerManagerService</code>
receives the request, it compares the token against all other <code class="highlighter-rouge">WakeLock</code> tokens it has stored,
and only releases the <code class="highlighter-rouge">WakeLock</code> if it finds a match. This additional “validation step” is an
important security measure put in place to guarantee that other applications cannot trick the
<code class="highlighter-rouge">PowerManagerService</code> into releasing a <code class="highlighter-rouge">WakeLock</code> held by a different application.</p>
</li>
</ol>
<p>Because of their unique object-identity capabilities, Binder tokens are used
extensively<sup><a href="#footnote2" id="ref2">2</a></sup> in the system for security. Perhaps the most
interesting example of how they are used in the framework is the “window token,” which we will now
discuss below.</p>
<h3 id="window-tokens">Window Tokens</h3>
<p>If you’ve ever scrolled through the official documentation for Android’s <code class="highlighter-rouge">View</code> class, chances
are you’ve stumbled across the
<a href="http://developer.android.com/reference/android/view/View.html#getWindowToken()"><code class="highlighter-rouge">getWindowToken()</code></a>
method and wondered what it meant. As its name implies, a window token is a special type of
Binder token that the window manager uses to uniquely identify a window in the system. Window
tokens are important for security because they make it impossible for malicious applications
to draw on top of the windows of other applications. The window manager protects against this
by requiring applications to pass their application’s window token as part of each request to
add or remove a window.<sup><a href="#footnote3" id="ref3">3</a></sup> If the tokens don’t match, the
window manager rejects the request and throws a
<a href="http://developer.android.com/reference/android/view/WindowManager.BadTokenException.html"><code class="highlighter-rouge">BadTokenException</code></a>.
Without window tokens, this necessary identification step wouldn’t be possible and the window
manager wouldn’t be able to protect itself from malicious applications.</p>
<p>By this point you might be wondering about the real-world scenarios in which you would need to
obtain a window token. Here are some examples:</p>
<ul>
<li>
<p>When an application starts up for the first time, the
<code class="highlighter-rouge">ActivityManagerService</code><sup><a href="#footnote4" id="ref4">4</a></sup>
creates a special kind of window token called an <strong>application window token</strong>, which
uniquely identifies the application’s
top-level container window.<sup><a href="#footnote5" id="ref5">5</a></sup> The activity
manager gives this token to both the
application and the window manager, and the application sends the token to the window
manager each time it wants to
add a new window to the screen. This ensures secure interaction between the application and the window manager
(by making it impossible to add windows on top of other applications), and also makes it easy for the activity
manager to make direct requests to the window manager. For example, the activity manager can say, “hide all of
this token’s windows”, and the window manager will be able to correctly identify the set of windows which
should be closed.<sup><a href="#footnote6" id="ref6">6</a></sup></p>
</li>
<li>
<p>Developers implementing their own custom Launchers can interact with the live wallpaper window that sits directly behind them by calling the
<a href="https://developer.android.com/reference/android/app/WallpaperManager.html#sendWallpaperCommand(android.os.IBinder, java.lang.String, int, int, int, android.os.Bundle)"><code class="highlighter-rouge">sendWallpaperCommand(IBinder windowToken, String action, int x, int y, int z, Bundle extras)</code></a>
method. To ensure that no other application other than the Launcher is able to interact with the live wallpaper, the
framework requires developers to pass a window token as the first argument to the method. If the window token does not
identify the current foreground activity window sitting on top of the wallpaper, the command is ignored and a warning is logged.</p>
</li>
<li>
<p>Applications can ask the
<a href="http://developer.android.com/reference/android/view/inputmethod/InputMethodManager.html"><code class="highlighter-rouge">InputMethodManager</code></a>
to hide the soft keyboard by calling the
<a href="http://developer.android.com/reference/android/view/inputmethod/InputMethodManager.html#hideSoftInputFromWindow(android.os.IBinder, int)"><code class="highlighter-rouge">hideSoftInputFromWindow(IBinder windowToken, int flags)</code></a>
method, but must provide a window token as part of the request. If the token doesn’t match the window token belonging to the
window currently accepting input, the <code class="highlighter-rouge">InputMethodManager</code> will reject the request. This makes it impossible for malicious
applications to force-close a soft keyboard opened by another application.</p>
</li>
<li>
<p>Applications which manually add new windows to the screen (i.e. using the
<a href="https://developer.android.com/reference/android/view/WindowManager.html"><code class="highlighter-rouge">addView(View, WindowManager.LayoutParams)</code></a>
method) may need to specify their application’s window token by setting the
<a href="http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#token"><code class="highlighter-rouge">WindowManager.LayoutParams.token</code></a>
field. It is very unlikely that any normal application would ever have to do this, since the
<a href="http://developer.android.com/reference/android/app/Activity.html#getWindowManager()"><code class="highlighter-rouge">getWindowManager()</code></a>
method returns a <code class="highlighter-rouge">WindowManager</code> which will automatically set the token’s value for you. That said, if at some point
in the future you encounter a situation in which you need to add a panel window to the screen from a background
service, know that you would need to manually sign the request with your application window token in order to achieve it. :P</p>
</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<p>Though their existence is for the most part hidden from developers, Binder tokens are used
extensively in the system for security. Android is a massively distributed system of cooperating
processes reliant on the fact that Binder objects are unique across all processes on the device.
Binder tokens are the driving force behind interaction in the framework, and without them secure
communication between application processes and the system would be difficult to achieve.</p>
<p>As always, thanks for reading, and leave a comment if you have any questions. Don’t forget to +1
this blog in the top right corner!</p>
<hr class="footnote-divider" />
<p><sup id="footnote1">1</sup> The
<a href="http://developer.android.com/reference/android/os/Binder.html">documentation</a> actually
hints that <code class="highlighter-rouge">Binder</code>s can be used for this purpose: “You can… simply instantiate a raw Binder
object directly to use as a token that can be shared across processes.” <a href="#ref1" title="Jump to footnote 1.">↩</a></p>
<p><sup id="footnote2">2</sup> Pick a random file in
<a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/services/java/com/android/server"><code class="highlighter-rouge">frameworks/base/services/java/com/android/server</code></a>
and chances are it makes use of Binder tokens in some shape or form. Another cool example involves
the status bar, notification manager, and the system UI. Specifically, the
<a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/services/java/com/android/server/StatusBarManagerService.java"><code class="highlighter-rouge">StatusBarManagerService</code></a>
maintains a global mapping of Binder tokens to notifications. When the
<a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/services/java/com/android/server/NotificationManagerService.java"><code class="highlighter-rouge">NotificationManagerService</code></a>
makes a request to the status bar manager to add a notification to the status bar, the status bar
manager creates a binder token uniquely identifying the notification and passes it to both the
notification manager and the
<a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/packages/SystemUI/"><code class="highlighter-rouge">SystemUI</code></a>.
Since all three parties know the notification’s Binder token, any changes to the notification from
that point forward (i.e. the notification manager cancels the notification, or the SystemUI detects
that the user has swiped a notification off screen) will go through the status bar manager first.
This makes it easier for the three system services to stay in sync: the status bar manager can be
in charge of centralizing all of the information about which notifications should currently be shown
without the SystemUI and notification manager ever having to interact with each other directly. <a href="#ref2" title="Jump to footnote 2.">↩</a></p>
<p><sup id="footnote3">3</sup> Applications that hold the <code class="highlighter-rouge">android.permission.SYSTEM_ALERT_WINDOW</code>
permission (a.k.a. the “draw over other apps” permission) are notable exceptions to this rule.
<a href="https://play.google.com/store/apps/details?id=com.facebook.orca">Facebook Messenger</a>
and <a href="https://play.google.com/store/apps/details?id=com.inisoft.mediaplayer.a">DicePlayer</a>
are two popular applications which require this permission, and use it to add windows on top of
other applications from a background service. <a href="#ref3" title="Jump to footnote 3.">↩</a></p>
<p><sup id="footnote4">4</sup> The
<a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/services/java/com/android/server/am/ActivityManagerService.java"><code class="highlighter-rouge">ActivityManagerService</code></a>
is the global system service (running in the System Server process) that is in charge of starting
(and managing) new components, such as Activities and Services. It’s also involved in the maintenance
of OOM adjustments used by the in-kernel low-memory handler, permissions, task management, etc. <a href="#ref4" title="Jump to footnote 4.">↩</a></p>
<p><sup id="footnote5">5</sup> You can obtain a reference by calling
<a href="http://developer.android.com/reference/android/view/View.html#getApplicationWindowToken()"><code class="highlighter-rouge">getApplicationWindowToken()</code></a>. <a href="#ref5" title="Jump to footnote 5.">↩</a></p>
<p><sup id="footnote6">6</sup> This explanation barely scratches the surface. For a more
detailed explanation of how the <code class="highlighter-rouge">SurfaceFlinger</code>, <code class="highlighter-rouge">WindowManager</code>, and application interact
with each other, see this <a href="http://source.android.com/devices/graphics/architecture.html">article</a>.
The third paragraph of the “SurfaceFlinger and Hardware Composer” section briefly mentions
the <code class="highlighter-rouge">Binder</code> application window token that is passed to the application as I discussed above.
<a href="#ref6" title="Jump to footnote 6.">↩</a></p>
Handling Configuration Changes with Fragmentshttps://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html2013-04-29T00:00:00+00:002014-01-14T00:00:00+00:00<p>This post addresses a common question that is frequently asked on <a href="http://stackoverflow.com/q/3821423/844882">StackOverflow</a>:</p>
<p>This post addresses a common question that is frequently asked on <a href="http://stackoverflow.com/q/3821423/844882">StackOverflow</a>:</p>
<blockquote>
<p>What is the best way to retain active objects—such as
running <code class="highlighter-rouge">Thread</code>s, <code class="highlighter-rouge">Socket</code>s, and <code class="highlighter-rouge">AsyncTask</code>s—across
device configuration changes?</p>
</blockquote>
<p>To answer this question, we will first discuss some of the common
difficulties developers face when using long-running background tasks
in conjunction with the Activity lifecycle. Then, we will describe
the flaws of two common approaches to solving the problem. Finally,
we will conclude with sample code illustrating the recommended
solution, which uses retained Fragments to achieve our goal.</p>
<!--more-->
<h3 id="configuration-changes--background-tasks">Configuration Changes & Background Tasks</h3>
<p>One problem with configuration changes and the destroy-and-create cycle
that Activitys go through as a result stems from the fact that these events
are unpredictable and may occur at any time. Concurrent background tasks
only add to this problem. Assume, for example, that an Activity starts
an <code class="highlighter-rouge">AsyncTask</code> and soon after the user rotates the screen, causing the
Activity to be destroyed and recreated. When the <code class="highlighter-rouge">AsyncTask</code> eventually
finishes its work, it will incorrectly report its results back to the
old Activity instance, completely unaware that a new Activity has been
created. As if this wasn’t already an issue, the new Activity instance
might waste valuable resources by firing up the background work <em>again</em>,
unaware that the old <code class="highlighter-rouge">AsyncTask</code> is still running. For these reasons,
it is vital that we correctly and efficiently retain active objects
across Activity instances when configuration changes occur.</p>
<h3 id="bad-practice-retain-the-activity">Bad Practice: Retain the Activity</h3>
<p>Perhaps the hackiest and most widely abused workaround is to disable
the default destroy-and-recreate behavior by setting the <code class="highlighter-rouge">android:configChanges</code>
attribute in your Android manifest. The apparent simplicity of this
approach makes it extremely attractive to developers;
<a href="http://stackoverflow.com/a/5336057/844882">Google engineers</a>,
however, discourage its use. The primary concern is that it requires you
to handle device configuration changes manually in code. Handling
configuration changes requires you to take many additional steps to
ensure that each and every string, layout, drawable, dimension, etc.
remains in sync with the device’s current configuration, and if you
aren’t careful, your application can easily have a whole series of
resource-specific bugs as a result.</p>
<p>Another reason why Google discourages its use is because many
developers incorrectly assume that setting <code class="highlighter-rouge">android:configChanges="orientation"</code>
(for example) will magically protect their application from
unpredictable scenarios in which the underlying Activity will be
destroyed and recreated. <em>This is not the case.</em> Configuration
changes can occur for a number of reasons—not just screen
orientation changes. Inserting your device into a display dock,
changing the default language, and modifying the device’s default
font scaling factor are just three examples of events that can
trigger a device configuration change, all of which signal the
system to destroy and recreate all currently running Activitys
the next time they are resumed. As a result, setting the
<code class="highlighter-rouge">android:configChanges</code> attribute is generally not good practice.</p>
<h3 id="deprecated-override-onretainnonconfigurationinstance">Deprecated: Override <code class="highlighter-rouge">onRetainNonConfigurationInstance()</code></h3>
<p>Prior to Honeycomb’s release, the recommended means of transferring
active objects across Activity instances was to override the
<code class="highlighter-rouge">onRetainNonConfigurationInstance()</code> and <code class="highlighter-rouge">getLastNonConfigurationInstance()</code>
methods. Using this approach, transferring an active object
across Activity instances was merely a matter of returning the
active object in <code class="highlighter-rouge">onRetainNonConfigurationInstance()</code> and retrieving
it in <code class="highlighter-rouge">getLastNonConfigurationInstance()</code>. As of API 13, these methods
have been deprecated in favor of the more Fragment’s <code class="highlighter-rouge">setRetainInstance(boolean)</code>
capability, which provides a much cleaner and modular means of
retaining objects during configuration changes. We discuss this
Fragment-based approach in the next section.</p>
<h3 id="recommended-manage-the-object-inside-a-retained-fragment">Recommended: Manage the Object Inside a Retained <code class="highlighter-rouge">Fragment</code></h3>
<p>Ever since the introduction of Fragments in Android 3.0, the recommended
means of retaining active objects across Activity instances is to wrap
and manage them inside of a retained “worker” Fragment. By default,
Fragments are destroyed and recreated along with their parent Activitys
when a configuration change occurs. Calling <code class="highlighter-rouge">Fragment#setRetainInstance(true)</code>
allows us to bypass this destroy-and-recreate cycle, signaling the system to
retain the current instance of the fragment when the activity is recreated.
As we will see, this will prove to be extremely useful with Fragments that
hold objects like running <code class="highlighter-rouge">Thread</code>s, <code class="highlighter-rouge">AsyncTask</code>s, <code class="highlighter-rouge">Socket</code>s, etc.</p>
<p>The sample code below serves as a basic example of how to retain an
<code class="highlighter-rouge">AsyncTask</code> across a configuration change using retained Fragments.
The code guarantees that progress updates and results are delivered
back to the currently displayed Activity instance and ensures that
we never accidentally leak an <code class="highlighter-rouge">AsyncTask</code> during a configuration change.
The design consists of two classes, a <code class="highlighter-rouge">MainActivity</code>…</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* This Activity displays the screen's UI, creates a TaskFragment
* to manage the task, and receives progress updates and results
* from the TaskFragment when they occur.
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="kd">implements</span> <span class="n">TaskFragment</span><span class="o">.</span><span class="na">TaskCallbacks</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">TAG_TASK_FRAGMENT</span> <span class="o">=</span> <span class="s">"task_fragment"</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">TaskFragment</span> <span class="n">mTaskFragment</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">setContentView</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">main</span><span class="o">);</span>
<span class="n">FragmentManager</span> <span class="n">fm</span> <span class="o">=</span> <span class="n">getFragmentManager</span><span class="o">();</span>
<span class="n">mTaskFragment</span> <span class="o">=</span> <span class="o">(</span><span class="n">TaskFragment</span><span class="o">)</span> <span class="n">fm</span><span class="o">.</span><span class="na">findFragmentByTag</span><span class="o">(</span><span class="n">TAG_TASK_FRAGMENT</span><span class="o">);</span>
<span class="c1">// If the Fragment is non-null, then it is currently being</span>
<span class="c1">// retained across a configuration change.</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mTaskFragment</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mTaskFragment</span> <span class="o">=</span> <span class="k">new</span> <span class="n">TaskFragment</span><span class="o">();</span>
<span class="n">fm</span><span class="o">.</span><span class="na">beginTransaction</span><span class="o">().</span><span class="na">add</span><span class="o">(</span><span class="n">mTaskFragment</span><span class="o">,</span> <span class="n">TAG_TASK_FRAGMENT</span><span class="o">).</span><span class="na">commit</span><span class="o">();</span>
<span class="o">}</span>
<span class="c1">// TODO: initialize views, restore saved state, etc.</span>
<span class="o">}</span>
<span class="c1">// The four methods below are called by the TaskFragment when new</span>
<span class="c1">// progress updates or results are available. The MainActivity </span>
<span class="c1">// should respond by updating its UI to indicate the change.</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onPreExecute</span><span class="o">()</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onProgressUpdate</span><span class="o">(</span><span class="kt">int</span> <span class="n">percent</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onCancelled</span><span class="o">()</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onPostExecute</span><span class="o">()</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>…and a <code class="highlighter-rouge">TaskFragment</code>…</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* This Fragment manages a single background task and retains
* itself across configuration changes.
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TaskFragment</span> <span class="kd">extends</span> <span class="n">Fragment</span> <span class="o">{</span>
<span class="cm">/**
* Callback interface through which the fragment will report the
* task's progress and results back to the Activity.
*/</span>
<span class="kd">interface</span> <span class="nc">TaskCallbacks</span> <span class="o">{</span>
<span class="kt">void</span> <span class="nf">onPreExecute</span><span class="o">();</span>
<span class="kt">void</span> <span class="nf">onProgressUpdate</span><span class="o">(</span><span class="kt">int</span> <span class="n">percent</span><span class="o">);</span>
<span class="kt">void</span> <span class="nf">onCancelled</span><span class="o">();</span>
<span class="kt">void</span> <span class="nf">onPostExecute</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="n">TaskCallbacks</span> <span class="n">mCallbacks</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">DummyTask</span> <span class="n">mTask</span><span class="o">;</span>
<span class="cm">/**
* Hold a reference to the parent Activity so we can report the
* task's current progress and results. The Android framework
* will pass us a reference to the newly created Activity after
* each configuration change.
*/</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onAttach</span><span class="o">(</span><span class="n">Activity</span> <span class="n">activity</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onAttach</span><span class="o">(</span><span class="n">activity</span><span class="o">);</span>
<span class="n">mCallbacks</span> <span class="o">=</span> <span class="o">(</span><span class="n">TaskCallbacks</span><span class="o">)</span> <span class="n">activity</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/**
* This method will only be called once when the retained
* Fragment is first created.
*/</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="c1">// Retain this fragment across configuration changes.</span>
<span class="n">setRetainInstance</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="c1">// Create and execute the background task.</span>
<span class="n">mTask</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DummyTask</span><span class="o">();</span>
<span class="n">mTask</span><span class="o">.</span><span class="na">execute</span><span class="o">();</span>
<span class="o">}</span>
<span class="cm">/**
* Set the callback to null so we don't accidentally leak the
* Activity instance.
*/</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onDetach</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onDetach</span><span class="o">();</span>
<span class="n">mCallbacks</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/**
* A dummy task that performs some (dumb) background work and
* proxies progress updates and results back to the Activity.
*
* Note that we need to check if the callbacks are null in each
* method in case they are invoked after the Activity's and
* Fragment's onDestroy() method have been called.
*/</span>
<span class="kd">private</span> <span class="kd">class</span> <span class="nc">DummyTask</span> <span class="kd">extends</span> <span class="n">AsyncTask</span><span class="o"><</span><span class="n">Void</span><span class="o">,</span> <span class="n">Integer</span><span class="o">,</span> <span class="n">Void</span><span class="o">></span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onPreExecute</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mCallbacks</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mCallbacks</span><span class="o">.</span><span class="na">onPreExecute</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="cm">/**
* Note that we do NOT call the callback object's methods
* directly from the background thread, as this could result
* in a race condition.
*/</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="n">Void</span> <span class="nf">doInBackground</span><span class="o">(</span><span class="n">Void</span><span class="o">...</span> <span class="n">ignore</span><span class="o">)</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">!</span><span class="n">isCancelled</span><span class="o">()</span> <span class="o">&&</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">100</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">SystemClock</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">100</span><span class="o">);</span>
<span class="n">publishProgress</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onProgressUpdate</span><span class="o">(</span><span class="n">Integer</span><span class="o">...</span> <span class="n">percent</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mCallbacks</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mCallbacks</span><span class="o">.</span><span class="na">onProgressUpdate</span><span class="o">(</span><span class="n">percent</span><span class="o">[</span><span class="mi">0</span><span class="o">]);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCancelled</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mCallbacks</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mCallbacks</span><span class="o">.</span><span class="na">onCancelled</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onPostExecute</span><span class="o">(</span><span class="n">Void</span> <span class="n">ignore</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mCallbacks</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mCallbacks</span><span class="o">.</span><span class="na">onPostExecute</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="flow-of-events">Flow of Events</h3>
<p>When the <code class="highlighter-rouge">MainActivity</code> starts up for the first time, it instantiates and adds
the <code class="highlighter-rouge">TaskFragment</code> to the Activity’s state. The <code class="highlighter-rouge">TaskFragment</code> creates and
executes an <code class="highlighter-rouge">AsyncTask</code> and proxies progress updates and results back to the
<code class="highlighter-rouge">MainActivity</code> via the <code class="highlighter-rouge">TaskCallbacks</code> interface. When a configuration change
occurs, the <code class="highlighter-rouge">MainActivity</code> goes through its normal lifecycle events, and once
created the new Activity instance is passed to the <code class="highlighter-rouge">onAttach(Activity)</code> method,
thus ensuring that the <code class="highlighter-rouge">TaskFragment</code> will always hold a reference to the
currently displayed Activity instance even after the configuration change.
The resulting design is both simple and reliable; the application framework
will handle re-assigning Activity instances as they are torn down and recreated,
and the <code class="highlighter-rouge">TaskFragment</code> and its <code class="highlighter-rouge">AsyncTask</code> never need to worry about the
unpredictable occurrence of a configuration change. Note also that it is impossible
for <code class="highlighter-rouge">onPostExecute()</code> to be executed in between the calls to <code class="highlighter-rouge">onDetach()</code> and
<code class="highlighter-rouge">onAttach()</code>, as explained in <a href="http://stackoverflow.com/q/19964180/844882">this StackOverflow answer</a>
and in my reply to Doug Stevenson in
<a href="https://plus.google.com/u/0/+AlexLockwood/posts/etWuiiRiqLf">this Google+ post</a>
(there is also some discussion about this in the comments below).</p>
<h3 id="conclusion">Conclusion</h3>
<p>Synchronizing background tasks with the Activity lifecycle can be tricky and
configuration changes will only add to the confusion. Fortunately, retained
Fragments make handling these events very easy by consistently maintaining a
reference to its parent Activity, even after being destroyed and recreated.</p>
<p>A sample application illustrating how to correctly use retained Fragments to
achieve this effect is available for download on the
<a href="https://play.google.com/store/apps/details?id=com.adp.retaintask">Play Store</a>.
The source code is available on <a href="https://github.com/alexjlockwood/worker-fragments">GitHub</a>.
Download it, import it into Eclipse, and modify it all you want!</p>
<p><a href="/assets/images/posts/2013/04/29/worker-fragments-screenshot.png">
<img src="/assets/images/posts/2013/04/29/worker-fragments-screenshot.png" style="max-width:400px;height=225px;" />
</a></p>
<p>As always, leave a comment if you have any questions and don’t forget to +1 this
blog in the top right corner!</p>
Activitys, Threads, & Memory Leakshttps://www.androiddesignpatterns.com/2013/04/activitys-threads-memory-leaks.html2013-04-15T00:00:00+00:002014-01-14T00:00:00+00:00<blockquote>
<p>Note: the source code in this blog post is available on
<a href="https://github.com/alexjlockwood/leaky-threads">GitHub</a>.</p>
</blockquote>
<blockquote>
<p>Note: the source code in this blog post is available on
<a href="https://github.com/alexjlockwood/leaky-threads">GitHub</a>.</p>
</blockquote>
<p>A common difficulty in Android programming is coordinating long-running tasks
over the Activity lifecycle and avoiding the subtle memory leaks which might
result. Consider the Activity code below, which starts and loops a new thread
upon its creation:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Example illustrating how threads persist across configuration
* changes (which cause the underlying Activity instance to be
* destroyed). The Activity context also leaks because the thread
* is instantiated as an anonymous class, which holds an implicit
* reference to the outer Activity instance, therefore preventing
* it from being garbage collected.
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">exampleOne</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">exampleOne</span><span class="o">()</span> <span class="o">{</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
<span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
<span class="n">SystemClock</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">1000</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}.</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<!--more-->
<p>When a configuration change occurs, causing the entire Activity to be
destroyed and re-created, it is easy to assume that Android will clean
up after us and reclaim the memory associated with the Activity and its
running thread. However, this is not the case. Both will leak never to be
reclaimed, and the result will likely be a significant reduction in performance.</p>
<h3 id="how-to-leak-an-activity">How to Leak an Activity</h3>
<p>The first memory leak should be immediately obvious if you read my
<a href="/2013/01/inner-class-handler-memory-leak.html">previous post</a>
on Handlers and inner classes. In Java, non-static anonymous classes hold an implicit
reference to their enclosing class. If you’re not careful, storing this reference
can result in the Activity being retained when it would otherwise be eligible for
garbage collection. Activity objects hold a reference to their entire view hierarchy
and all its resources, so if you leak one, you leak a lot of memory.</p>
<p>The problem is only exacerbated by configuration changes, which signal the destruction
and re-creation of the entire underlying Activity. For example, after ten orientation
changes running the code above, we can see
(using <a href="http://www.eclipse.org/mat/">Eclipse Memory Analyzer</a>) that each
Activity object is in fact retained in memory as a result of these implicit references:</p>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: center; margin-left: 0em; text-align: left;">
<tbody>
<tr><td style="text-align: center;"><a class="no-border" href="/assets/images/posts/2013/04/15/activity-leak.png"><img border="0" height="175" src="/assets/images/posts/2013/04/15/activity-leak.png" width="400" /></a>
</td></tr>
<tr><td class="tr-caption" style="text-align: center;">Figure 1. Activity instances retained in memory after ten orientation changes.
</td></tr>
</tbody>
</table>
<p>After each configuration change, the Android system creates a new Activity and leaves
the old one behind to be garbage collected. However, the thread holds an implicit
reference to the old Activity and prevents it from ever being reclaimed. As a result,
each new Activity is leaked and all resources associated with them are never able to be
reclaimed.</p>
<p>The fix is easy once we’ve identified the source of the problem: declare the
thread as a private static inner class as shown below.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* This example avoids leaking an Activity context by declaring the
* thread as a private static inner class, but the threads still
* continue to run even across configuration changes. The DVM has a
* reference to all running threads and whether or not these threads
* are garbage collected has nothing to do with the Activity lifecycle.
* Active threads will continue to run until the kernel destroys your
* application's process.
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">exampleTwo</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">exampleTwo</span><span class="o">()</span> <span class="o">{</span>
<span class="k">new</span> <span class="nf">MyThread</span><span class="o">().</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyThread</span> <span class="kd">extends</span> <span class="n">Thread</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
<span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
<span class="n">SystemClock</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">1000</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The new thread no longer holds an implicit reference to the Activity, and the
Activity will be eligible for garbage collection after the configuration change.</p>
<h3 id="how-to-leak-a-thread">How to Leak a Thread</h3>
<p>The second issue is that for each new Activity that is created, a thread is
leaked and never able to be reclaimed. Threads in Java are GC roots; that is,
the Dalvik Virtual Machine (DVM) keeps hard references to all active threads
in the runtime system, and as a result, threads that are left running will
never be eligible for garbage collection. For this reason, you must remember
to implement cancellation policies for your background threads! One example
of how this might be done is shown below:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Same as example two, except for this time we have implemented a
* cancellation policy for our thread, ensuring that it is never
* leaked! onDestroy() is usually a good place to close your active
* threads before exiting the Activity.
*/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
<span class="kd">private</span> <span class="n">MyThread</span> <span class="n">mThread</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">exampleThree</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">exampleThree</span><span class="o">()</span> <span class="o">{</span>
<span class="n">mThread</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MyThread</span><span class="o">();</span>
<span class="n">mThread</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="cm">/**
* Static inner classes don't hold implicit references to their
* enclosing class, so the Activity instance won't be leaked across
* configuration changes.
*/</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyThread</span> <span class="kd">extends</span> <span class="n">Thread</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">mRunning</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
<span class="n">mRunning</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">while</span> <span class="o">(</span><span class="n">mRunning</span><span class="o">)</span> <span class="o">{</span>
<span class="n">SystemClock</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">1000</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">close</span><span class="o">()</span> <span class="o">{</span>
<span class="n">mRunning</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onDestroy</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onDestroy</span><span class="o">();</span>
<span class="n">mThread</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>In the code above, closing the thread in <code class="highlighter-rouge">onDestroy()</code> ensures that
you never accidentally leak the thread. If you want to persist the same thread
across configuration changes (as opposed to closing and re-creating a new thread
each time), consider using a retained, UI-less worker fragment to perform the
long-running task. Check out my blog post, titled
<a href="/2013/04/retaining-objects-across-config-changes.html">Handling Configuration Changes with Fragments</a>,
for an example explaining how this can be done. There is also a comprehensive
example available in the
<a href="https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/app/FragmentRetainInstance.java">API demos</a>
which illustrates the concept.</p>
<h3 id="conclusion">Conclusion</h3>
<p>In Android, coordinating long-running tasks over the Activity lifecycle can be
difficult and memory leaks can result if you aren’t careful. Here are some
general tips to consider when dealing with coordinating your long-running
background tasks with the Activity lifecycle:</p>
<ul>
<li>
<p><strong>Favor static inner classes over nonstatic.</strong> Each instance of a nonstatic inner
class will have an extraneous reference to its outer Activity instance. Storing
this reference can result in the Activity being retained when it would otherwise
be eligible for garbage collection. If your static inner class requires a
reference to the underlying Activity in order to function properly, make sure
you wrap the object in a <code class="highlighter-rouge">WeakReference</code> to ensure that you don’t
accidentally leak the Activity.</p>
</li>
<li>
<p><strong>Don’t assume that Java will ever clean up your running threads for you.</strong> In the
example above, it is easy to assume that when the user exits the Activity and the
Activity instance is finalized for garbage collection, any running threads associated
with that Activity will be reclaimed as well. <em>This is never the case.</em> Java
threads will persist until either they are explicitly closed or the entire process
is killed by the Android system. As a result, it is extremely important that you
remember to implement cancellation policies for your background threads, and to
take appropriate action when Activity lifecycle events occur.</p>
</li>
<li>
<p><strong>Consider whether or not you should use a Thread.</strong> The Android application framework
provides many classes designed to make background threading easier for developers.
For example, consider using a Loader instead of a thread for performing short-lived
asynchronous background queries in conjunction with the Activity lifecycle. Likewise,
if the background thread is not tied to any specific Activity, consider using a
Service and report the results back to the UI using a <code class="highlighter-rouge">BroadcastReceiver</code>.
Lastly, remember that everything discussed regarding threads in this blog post also
applies to <code class="highlighter-rouge">AsyncTask</code>s (since the <code class="highlighter-rouge">AsyncTask</code> class uses an
<code class="highlighter-rouge">ExecutorService</code> to execute its tasks). However, given that <code class="highlighter-rouge">AsyncTask</code>s
should only be used for short-lived operations (“a few seconds at most”, as per the
<a href="http://developer.android.com/reference/android/os/AsyncTask.html">documentation</a>),
leaking an Activity or a thread by these means should never be an issue.</p>
</li>
</ul>
<p>The source code for this blog post is available on
<a href="https://github.com/alexjlockwood/leaky-threads">GitHub</a>. A standalone
application (which mirrors the source code exactly) is also available for download on
<a href="https://play.google.com/store/apps/details?id=com.adp.leaky.threads">Google Play</a>.</p>
<p><a class="no-border" href="https://play.google.com/store/apps/details?id=com.adp.leaky.threads">
<img width="320px" height="180px" border="0" src="/assets/images/posts/2013/04/15/leaky-threads-screenshot.png" />
</a></p>
<p>As always, leave a comment if you have any questions and don’t forget to +1
this blog in the top right corner!</p>
How to Leak a Context: Handlers & Inner Classeshttps://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html2013-01-14T00:00:00+00:002014-12-12T00:00:00+00:00<p>Consider the following code:</p>
<p>Consider the following code:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SampleActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">Handler</span> <span class="n">mLeakyHandler</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Handler</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleMessage</span><span class="o">(</span><span class="n">Message</span> <span class="n">msg</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// ... </span>
<span class="o">}</span>
<span class="o">};</span>
<span class="o">}</span>
</code></pre></div></div>
<p>While not readily obvious, this code can cause cause a massive memory leak.
Android Lint will give the following warning:</p>
<blockquote>
<p>In Android, Handler classes should be static or leaks might occur.</p>
</blockquote>
<p>But where exactly is the leak and how might it happen? Let’s determine the
source of the problem by first documenting what we know:</p>
<!--more-->
<ol>
<li>
<p>When an Android application first starts, the framework creates a
<a href="http://developer.android.com/reference/android/os/Looper.html"><code class="highlighter-rouge">Looper</code></a>
object for the application’s main thread. A <code class="highlighter-rouge">Looper</code> implements a simple message queue,
processing <a href="http://developer.android.com/reference/android/os/Message.html"><code class="highlighter-rouge">Message</code></a>
objects in a loop one after another. All major application framework events (such
as Activity lifecycle method calls, button clicks, etc.) are contained inside
<code class="highlighter-rouge">Message</code> objects, which are added to the <code class="highlighter-rouge">Looper</code>’s message queue and are processed
one-by-one. The main thread’s <code class="highlighter-rouge">Looper</code> exists throughout the application’s lifecycle.</p>
</li>
<li>
<p>When a <a href="http://developer.android.com/reference/android/os/Handler.html"><code class="highlighter-rouge">Handler</code></a>
is instantiated on the main thread, it is associated with the <code class="highlighter-rouge">Looper</code>’s message queue.
Messages posted to the message queue will hold a reference to the <code class="highlighter-rouge">Handler</code> so that the
framework can call
<a href="http://developer.android.com/reference/android/os/Handler.html#handleMessage(android.os.Message)"><code class="highlighter-rouge">Handler#handleMessage(Message)</code></a>
when the <code class="highlighter-rouge">Looper</code> eventually processes the message.</p>
</li>
<li>
<p>In Java, non-static inner and anonymous classes hold an implicit reference to their
outer class. Static inner classes, on the other hand, do not.</p>
</li>
</ol>
<p>So where exactly is the memory leak? It’s very subtle, but consider the following code as an example:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SampleActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">Handler</span> <span class="n">mLeakyHandler</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Handler</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleMessage</span><span class="o">(</span><span class="n">Message</span> <span class="n">msg</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// ...</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="c1">// Post a message and delay its execution for 10 minutes.</span>
<span class="n">mLeakyHandler</span><span class="o">.</span><span class="na">postDelayed</span><span class="o">(</span><span class="k">new</span> <span class="n">Runnable</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span> <span class="cm">/* ... */</span> <span class="o">}</span>
<span class="o">},</span> <span class="mi">1000</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">10</span><span class="o">);</span>
<span class="c1">// Go back to the previous Activity.</span>
<span class="n">finish</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>When the activity is finished, the delayed message will continue to live in the main thread’s
message queue for 10 minutes before it is processed. The message holds a reference to the
activity’s <code class="highlighter-rouge">Handler</code>, and the <code class="highlighter-rouge">Handler</code> holds an implicit reference to its outer class (the
<code class="highlighter-rouge">SampleActivity</code>, in this case). This reference will persist until the message is processed,
thus preventing the activity context from being garbage collected and leaking all of the
application’s resources. Note that the same is true with the anonymous Runnable class on
line 15. Non-static instances of anonymous classes hold an implicit reference to their outer
class, so the context will be leaked.</p>
<p>To fix the problem, subclass the <code class="highlighter-rouge">Handler</code> in a new file or use a static inner class instead.
Static inner classes do not hold an implicit reference to their outer class, so the activity
will not be leaked. If you need to invoke the outer activity’s methods from within the
<code class="highlighter-rouge">Handler</code>, have the Handler hold a <code class="highlighter-rouge">WeakReference</code> to the activity so you don’t accidentally
leak a context. To fix the memory leak that occurs when we instantiate the anonymous Runnable
class, we make the variable a static field of the class (since static instances of anonymous
classes do not hold an implicit reference to their outer class):</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SampleActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
<span class="cm">/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyHandler</span> <span class="kd">extends</span> <span class="n">Handler</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">WeakReference</span><span class="o"><</span><span class="n">SampleActivity</span><span class="o">></span> <span class="n">mActivity</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">MyHandler</span><span class="o">(</span><span class="n">SampleActivity</span> <span class="n">activity</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mActivity</span> <span class="o">=</span> <span class="k">new</span> <span class="n">WeakReference</span><span class="o"><</span><span class="n">SampleActivity</span><span class="o">>(</span><span class="n">activity</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleMessage</span><span class="o">(</span><span class="n">Message</span> <span class="n">msg</span><span class="o">)</span> <span class="o">{</span>
<span class="n">SampleActivity</span> <span class="n">activity</span> <span class="o">=</span> <span class="n">mActivity</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">activity</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// ...</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">MyHandler</span> <span class="n">mHandler</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MyHandler</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="cm">/**
* Instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">Runnable</span> <span class="n">sRunnable</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Runnable</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span> <span class="cm">/* ... */</span> <span class="o">}</span>
<span class="o">};</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="c1">// Post a message and delay its execution for 10 minutes.</span>
<span class="n">mHandler</span><span class="o">.</span><span class="na">postDelayed</span><span class="o">(</span><span class="n">sRunnable</span><span class="o">,</span> <span class="mi">1000</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">10</span><span class="o">);</span>
<span class="c1">// Go back to the previous Activity.</span>
<span class="n">finish</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The difference between static and non-static inner classes is subtle, but is something
every Android developer should understand. What’s the bottom line? <strong>Avoid using non-static
inner classes in an activity if instances of the inner class could outlive the activity’s
lifecycle.</strong> Instead, prefer static inner classes and hold a weak reference to the activity inside.</p>
<p>As always, leave a comment if you have any questions and don’t forget to +1 this blog in
the top right corner! :)</p>
Use Go to Implement your Android Backendshttps://www.androiddesignpatterns.com/2013/01/gcm-appengine-golang-android-backends.html2013-01-12T00:00:00+00:002013-01-12T00:00:00+00:00<p>A couple weeks ago I wrote a <a href="http://github.com/alexjlockwood/gcm">library</a>
that simplifies the interaction between Go-based application servers and Google Cloud
Messaging servers. I plan on covering GCM (both the application-side and server-side
aspects) in more detail in a future blog post, but for now I will just leave a link
to the library to encourage more people to write their GCM application servers using
the Go Programming Language
(<a href="https://developers.google.com/appengine/docs/go/overview">Google App Engine</a>,
hint hint).</p>
<p>A couple weeks ago I wrote a <a href="http://github.com/alexjlockwood/gcm">library</a>
that simplifies the interaction between Go-based application servers and Google Cloud
Messaging servers. I plan on covering GCM (both the application-side and server-side
aspects) in more detail in a future blog post, but for now I will just leave a link
to the library to encourage more people to write their GCM application servers using
the Go Programming Language
(<a href="https://developers.google.com/appengine/docs/go/overview">Google App Engine</a>,
hint hint).</p>
<p><em>…but why Go?</em></p>
<p>I’m glad you asked. There are several reasons:</p>
<!--more-->
<ul>
<li>
<p><strong><em>Go is modern.</em></strong> Programming languages like C, C++, and Java
are old, designed before the advent of multicore machines, networking, and web
application development. Go was designed to be suitable for writing large Google
programs such as web servers.</p>
</li>
<li>
<p><strong><em>Go is concise, yet familiar.</em></strong> Tasks that require 40+ lines of code
in Java (i.e. setting up HTTP servers and parsing JSON responses) can be done in 1 or 2
lines. Go significantly reduces the amount of work required to write simple programs,
and yet the language’s syntax is not too radical, still resembling the most common
procedural languages.</p>
</li>
<li>
<p><strong><em>Go is easy to learn.</em></strong> Learn the language in a day:
<a href="http://tour.golang.org">A Tour of Go</a> and
<a href="http://golang.org/doc/effective_go.html">Effective Go</a>.</p>
</li>
<li>
<p><strong><em>Go was invented at Google.</em></strong> Enough said. :)</p>
</li>
</ul>
<p>That’s all for now… but expect a lot more on GCM, Google App Engine, and Golang
later! The comments are open as always, and don’t forget to +1 this post!</p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://github.com/alexjlockwood/gcm">Google Cloud Messaging for Go</a></li>
<li><a href="https://developers.google.com/appengine/">Google App Engine</a></li>
<li><a href="http://tour.golang.org">A Tour of Go</a></li>
<li><a href="http://golang.org/doc/effective_go.html">Effective Go</a></li>
<li><a href="http://golang.org">golang.org</a></li>
</ul>
Google Play Services: Setup & Verificationhttps://www.androiddesignpatterns.com/2013/01/google-play-services-setup.html2013-01-08T00:00:00+00:002013-01-08T00:00:00+00:00<p><strong>WARNING: Many of the APIs used in this code have been deprecated since I initially wrote this post.
Check out <a href="https://developer.android.com/google/play-services/index.html">the official documentation</a> for the latest instructions.</strong></p>
<p><strong>WARNING: Many of the APIs used in this code have been deprecated since I initially wrote this post.
Check out <a href="https://developer.android.com/google/play-services/index.html">the official documentation</a> for the latest instructions.</strong></p>
<p>One of the trickiest aspects of writing a robust web-based Android application
is authentication, simply due to its asynchronous nature and the many edge cases
that one must cover. Thankfully, the recently released Google Play Services API
greatly simplifies the authentication process, providing developers with a
consistent and safe way to grant and receive OAuth2 access tokens to Google
services. Even so, there are still several cases that must be covered in order
to provide the best possible user experience. A professionally built Android
application should be able to react to even the most unlikely events, for example,
if a previously logged in user uninstalls Google Play Services, or navigates to
the system settings and clears the application’s data when the foreground Activity
is in a paused state. This post focuses on how to make use of the Google Play
Services library while still accounting for edge cases such as these.</p>
<!--more-->
<h3 id="verifying-google-play-services">Verifying Google Play Services</h3>
<p>In this post, we will implement a very basic (but robust) Android application
that authenticates a user with Google services. Our implementation will consist
of a single Activity:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AuthActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">setContentView</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">main</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>As you probably already know, before we attempt authentication using Google
Play Services, we must first verify that the service is up-to-date and
installed on the device. This seems easy enough, but where should
these checks be performed? As with most edge-case checks, it makes the most
sense to verify that our device is properly configured in the Activity’s
<code class="highlighter-rouge">onResume()</code> method. Verifying in <code class="highlighter-rouge">onResume()</code> is
important because it has the application perform a check each time the
Activity is brought into the foreground, thus guaranteeing that our application
will never incorrectly assume that Google Play Services is properly configured:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onResume</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onResume</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">checkPlayServices</span><span class="o">())</span> <span class="o">{</span>
<span class="c1">// Then we're good to go!</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Now let’s implement <code class="highlighter-rouge">checkPlayServices()</code>, which will return true if and only
if Google Play Services is correctly installed and configured on the device:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">checkPlayServices</span><span class="o">()</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">status</span> <span class="o">=</span> <span class="n">GooglePlayServicesUtil</span><span class="o">.</span><span class="na">isGooglePlayServicesAvailable</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">status</span> <span class="o">!=</span> <span class="n">ConnectionResult</span><span class="o">.</span><span class="na">SUCCESS</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">GooglePlayServicesUtil</span><span class="o">.</span><span class="na">isUserRecoverableError</span><span class="o">(</span><span class="n">status</span><span class="o">))</span> <span class="o">{</span>
<span class="n">showErrorDialog</span><span class="o">(</span><span class="n">status</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">Toast</span><span class="o">.</span><span class="na">makeText</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="s">"This device is not supported."</span><span class="o">,</span>
<span class="n">Toast</span><span class="o">.</span><span class="na">LENGTH_LONG</span><span class="o">).</span><span class="na">show</span><span class="o">();</span>
<span class="n">finish</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="kt">void</span> <span class="nf">showErrorDialog</span><span class="o">(</span><span class="kt">int</span> <span class="n">code</span><span class="o">)</span> <span class="o">{</span>
<span class="n">GooglePlayServicesUtil</span><span class="o">.</span><span class="na">getErrorDialog</span><span class="o">(</span><span class="n">code</span><span class="o">,</span> <span class="k">this</span><span class="o">,</span>
<span class="n">REQUEST_CODE_RECOVER_PLAY_SERVICES</span><span class="o">).</span><span class="na">show</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>And we implement <code class="highlighter-rouge">onActivityResult</code> as follows:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">REQUEST_CODE_RECOVER_PLAY_SERVICES</span> <span class="o">=</span> <span class="mi">1001</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onActivityResult</span><span class="o">(</span><span class="kt">int</span> <span class="n">requestCode</span><span class="o">,</span> <span class="kt">int</span> <span class="n">resultCode</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
<span class="k">switch</span> <span class="o">(</span><span class="n">requestCode</span><span class="o">)</span> <span class="o">{</span>
<span class="k">case</span> <span class="nl">REQUEST_CODE_RECOVER_PLAY_SERVICES:</span>
<span class="k">if</span> <span class="o">(</span><span class="n">resultCode</span> <span class="o">==</span> <span class="n">RESULT_CANCELED</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Toast</span><span class="o">.</span><span class="na">makeText</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="s">"Google Play Services must be installed."</span><span class="o">,</span>
<span class="n">Toast</span><span class="o">.</span><span class="na">LENGTH_SHORT</span><span class="o">).</span><span class="na">show</span><span class="o">();</span>
<span class="n">finish</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onActivityResult</span><span class="o">(</span><span class="n">requestCode</span><span class="o">,</span> <span class="n">resultCode</span><span class="o">,</span> <span class="n">data</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>If Google Play Services are available, the method will return true. If
Google Play Services is not available and error is deemed “unrecoverable,”
a Toast will indicate to the user that the device is not supported. Otherwise,
an error dialog will be shown and a result will eventually propagate back to
<code class="highlighter-rouge">onActivityResult</code>. Note that <code class="highlighter-rouge">onActivityResult</code> is called <em>before</em> <code class="highlighter-rouge">onResume</code>,
so when a result is returned, we will perform one final check just to be sure
that everything has been setup correctly.</p>
<h3 id="checking-the-currently-logged-in-user">Checking the Currently Logged In User</h3>
<p>What we have so far is enough to ensure that our users will be able to use our
application if and only if Google Play Services is installed and up-to-date.
Now let’s assume that our application also stores the name of the currently
logged in user in its <code class="highlighter-rouge">SharedPreferences</code>. How should our application respond
in the case that the current user is unexpectedly logged out (i.e. the user has
clicked “Clear data” in the app’s system settings)? It turns out that we can do
something very similar. (Note that the code below makes use of a simple utility
file named <a href="https://gist.github.com/4477849"><code class="highlighter-rouge">AccountUtils.java</code></a>,
which provides some helper methods for reading/writing account information to the
app’s <code class="highlighter-rouge">SharedPreferences</code>).</p>
<p>First, define a method that will verify the existence of a single authenticated
user in the application’s shared preferences and update the <code class="highlighter-rouge">onResume()</code> method
accordingly:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onResume</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onResume</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">checkPlayServices</span><span class="o">()</span> <span class="o">&&</span> <span class="n">checkUserAccount</span><span class="o">())</span> <span class="o">{</span>
<span class="c1">// Then we're good to go!</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">checkPlayServices</span><span class="o">()</span> <span class="o">{</span>
<span class="cm">/* ... */</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">checkUserAccount</span><span class="o">()</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">accountName</span> <span class="o">=</span> <span class="n">AccountUtils</span><span class="o">.</span><span class="na">getAccountName</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">accountName</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Then the user was not found in the SharedPreferences. Either the</span>
<span class="c1">// application deliberately removed the account, or the application's</span>
<span class="c1">// data has been forcefully erased.</span>
<span class="n">showAccountPicker</span><span class="o">();</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">Account</span> <span class="n">account</span> <span class="o">=</span> <span class="n">AccountUtils</span><span class="o">.</span><span class="na">getGoogleAccountByName</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">accountName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">account</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Then the account has since been removed.</span>
<span class="n">AccountUtils</span><span class="o">.</span><span class="na">removeAccount</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="n">showAccountPicker</span><span class="o">();</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">showAccountPicker</span><span class="o">()</span> <span class="o">{</span>
<span class="n">Intent</span> <span class="n">pickAccountIntent</span> <span class="o">=</span> <span class="n">AccountPicker</span><span class="o">.</span><span class="na">newChooseAccountIntent</span><span class="o">(</span>
<span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="k">new</span> <span class="n">String</span><span class="o">[]</span> <span class="o">{</span> <span class="n">GoogleAuthUtil</span><span class="o">.</span><span class="na">GOOGLE_ACCOUNT_TYPE</span> <span class="o">},</span>
<span class="kc">true</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="n">startActivityForResult</span><span class="o">(</span><span class="n">pickAccountIntent</span><span class="o">,</span> <span class="n">REQUEST_CODE_PICK_ACCOUNT</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Note that in the case that a user is not already signed in, an <code class="highlighter-rouge">AccountPicker</code>
dialog will be launched, requesting that the user selects a Google account with
which the application will use to authenticate requests. The result will
eventually be returned back to the Activity, so we must update the <code class="highlighter-rouge">onActivityResult</code>
method accordingly:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">REQUEST_CODE_RECOVER_PLAY_SERVICES</span> <span class="o">=</span> <span class="mi">1001</span><span class="o">;</span>
<span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">REQUEST_CODE_PICK_ACCOUNT</span> <span class="o">=</span> <span class="mi">1002</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onActivityResult</span><span class="o">(</span><span class="kt">int</span> <span class="n">requestCode</span><span class="o">,</span> <span class="kt">int</span> <span class="n">resultCode</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
<span class="k">switch</span> <span class="o">(</span><span class="n">requestCode</span><span class="o">)</span> <span class="o">{</span>
<span class="k">case</span> <span class="nl">REQUEST_CODE_RECOVER_PLAY_SERVICES:</span>
<span class="cm">/* ... */</span>
<span class="k">case</span> <span class="nl">REQUEST_CODE_PICK_ACCOUNT:</span>
<span class="k">if</span> <span class="o">(</span><span class="n">resultCode</span> <span class="o">==</span> <span class="n">RESULT_OK</span><span class="o">)</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">accountName</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="na">getStringExtra</span><span class="o">(</span>
<span class="n">AccountManager</span><span class="o">.</span><span class="na">KEY_ACCOUNT_NAME</span><span class="o">);</span>
<span class="n">AccountUtils</span><span class="o">.</span><span class="na">setAccountName</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">accountName</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">resultCode</span> <span class="o">==</span> <span class="n">RESULT_CANCELED</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Toast</span><span class="o">.</span><span class="na">makeText</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="s">"This application requires a Google account."</span><span class="o">,</span>
<span class="n">Toast</span><span class="o">.</span><span class="na">LENGTH_SHORT</span><span class="o">).</span><span class="na">show</span><span class="o">();</span>
<span class="n">finish</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onActivityResult</span><span class="o">(</span><span class="n">requestCode</span><span class="o">,</span> <span class="n">resultCode</span><span class="o">,</span> <span class="n">data</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>As was the case before, <code class="highlighter-rouge">onResume</code> will be called after <code class="highlighter-rouge">onActivityResult</code>, ensuring
that Google Play Services is still installed and up-to-date, and that a Google account
has indeed been selected and saved to the disk.</p>
<h3 id="conclusion">Conclusion</h3>
<p>However unlikely they might be, covering edge cases in your Android applications is
very important. If a user deliberately tries to break your application (by, for
example, clearing the application’s data in the system settings), the app should
immediately recognize the event and act appropriately. The same concept outlined
in this post applies to many areas of Android development, not just those apps
which make use of Google Play Services.</p>
<p>The full source code for this post is provided here
<a href="https://gist.github.com/4477849"><code class="highlighter-rouge">AccountUtils.java</code></a> and
<a href="https://gist.github.com/4477939"><code class="highlighter-rouge">AuthActivity.java</code></a>. As always, leave
a comment if you have any questions and don’t forget to +1 this post!</p>
SQLite, Content Providers, & Thread Safetyhttps://www.androiddesignpatterns.com/2012/10/sqlite-contentprovider-thread-safety.html2012-10-11T00:00:00+00:002012-10-11T00:00:00+00:00<p>A common source of confusion when implementing <code class="highlighter-rouge">ContentProvider</code>s is that of thread-safety.
We all know that any potentially expensive query should be asynchronous so as not to block
the UI thread, but when, if ever, is it OK to make calls to the <code class="highlighter-rouge">ContentProvider</code> from
multiple threads?</p>
<p>A common source of confusion when implementing <code class="highlighter-rouge">ContentProvider</code>s is that of thread-safety.
We all know that any potentially expensive query should be asynchronous so as not to block
the UI thread, but when, if ever, is it OK to make calls to the <code class="highlighter-rouge">ContentProvider</code> from
multiple threads?</p>
<!--more-->
<h3 id="threads-and-content-providers">Threads and Content Providers</h3>
<p>The <a href="http://developer.android.com/reference/android/content/ContentProvider.html">documentation</a>
on ContentProviders warns that its methods may be called from multiple threads and therefore
must be thread-safe:</p>
<blockquote>
<p>Data access methods (such as <code class="highlighter-rouge">insert(Uri, ContentValues)</code> and
<code class="highlighter-rouge">update(Uri, ContentValues, String, String[]))</code> may be called from many
threads at once, and must be thread-safe.</p>
</blockquote>
<p>In other words, Android <strong>does not</strong> synchronize access to the ContentProvider for you.
If two calls to the same method are made simultaneously from separate threads, neither
call will wait for the other. Requiring the client to deal with concurrency themselves
makes sense from a framework developer’s point of view. The abstract <code class="highlighter-rouge">ContentProvider</code> class
cannot assume that its subclasses will require synchronization, as doing so would be
horribly inefficient.</p>
<h3 id="ensuring-thread-safety">Ensuring Thread Safety</h3>
<p>So now that we know that the ContentProvider is not thread safe, what do we need to
do in order to eliminate potential race conditions? Just make every method
<code class="highlighter-rouge">synchronized</code>, right?</p>
<p>Well… no, not necessarily. Consider a ContentProvider that uses a <code class="highlighter-rouge">SQLiteDatabase</code>
as its backing data source. As per the
<a href="http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#setLockingEnabled(boolean)">documentation</a>,
access to the <code class="highlighter-rouge">SQLiteDatabase</code> is synchronized by default, thus guaranteeing that
no two threads will ever touch it at the same time. In this case, synchronizing
each of the ContentProvider’s methods is both unnecessary and costly. Remember
that a <code class="highlighter-rouge">ContentProvider</code> serves as a wrapper around the underlying data source;
whether or not you must take extra measures to ensure thread safety often depends
on the data source itself.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Although the ContentProvider lacks in thread-safety, often times you will find that
no further action is required on your part with respect to preventing potential
race conditions. The canonical example is when your ContentProvider is backed by
a <code class="highlighter-rouge">SQLiteDatabase</code>; when two threads attempt to write to the database at the same
time, the <code class="highlighter-rouge">SQLiteDatabase</code> will lock itself down, ensuring that one will wait until
the other has completed. Each thread will be given mutually exclusive access to the
data source, ensuring the thread safety is met.</p>
<p>This has been a rather short post, so don’t hesitate to leave a comment if you have
any clarifying questions. Don’t forget to +1 this post below if you found it helpful!</p>
Tutorial: AppListLoader (part 4)https://www.androiddesignpatterns.com/2012/09/tutorial-loader-loadermanager.html2012-09-16T00:00:00+00:002012-09-16T00:00:00+00:00<p>This will be my fourth and final post on Loaders and the LoaderManager. Let me know in the comments if they have been helpful!
Links to my previous Loader-related posts are given below:</p>
<p>This will be my fourth and final post on Loaders and the LoaderManager. Let me know in the comments if they have been helpful!
Links to my previous Loader-related posts are given below:</p>
<ul>
<li><strong>Part 1:</strong> <a href="/2012/07/loaders-and-loadermanager-background.html">Life Before Loaders</a></li>
<li><strong>Part 2:</strong> <a href="/2012/07/understanding-loadermanager.html">Understanding the LoaderManager</a></li>
<li><strong>Part 3:</strong> <a href="/2012/08/implementing-loaders.html">Implementing Loaders</a></li>
<li><strong>Part 4:</strong> <a href="/2012/09/tutorial-loader-loadermanager.html">Tutorial: AppListLoader</a></li>
</ul>
<p>Due to public demand, I’ve written a sample application that illustrates how to correctly implement a custom Loader.
The application is named <a href="https://play.google.com/store/apps/details?id=com.adp.loadercustom">AppListLoader</a>,
and it is a simple demo application that queries and lists all installed applications on your Android device.
The application is a modified, re-thought (and bug-free) extension of the
<a href="http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/4.1.1_r1/com/example/android/apis/app/LoaderCustom.java">LoaderCustom.java</a>
sample that is provided in the API Demos. The application uses an <code class="highlighter-rouge">AppListLoader</code>
(a subclass of <code class="highlighter-rouge">AsyncTaskLoader</code>) to query its data, and the LoaderManager to
manage the Loader across the Activity/Fragment lifecycle:</p>
<!--more-->
<p><a class="no-border" href="/assets/images/posts/2012/09/16/app-screenshot.png">
<img src="/assets/images/posts/2012/09/16/app-screenshot.png" style="border:0px; width:400px; height:269px;" />
</a></p>
<p>The AppListLoader registers two <code class="highlighter-rouge">BroadcastReceiver</code>s which observe/listen for system-wide broadcasts that
impact the underlying data source. The <code class="highlighter-rouge">InstalledAppsObserver</code> listens for newly installed, updated, or
removed applications, and the <code class="highlighter-rouge">SystemLocaleObserver</code> listens for locale changes. For example, if the user
changes the language from English to Spanish, the <code class="highlighter-rouge">SystemLocaleObserver</code> will notify the AppListLoader to
re-query its data so that the application can display each application’s name in Spanish (assuming an alternate
Spanish name has been provided). Click “Change language” in the options menu and watch the Loader’s seamless
reaction to the event (it’s awesome, isn’t it? :P).</p>
<p>Log messages are written to the logcat whenever an important Loader/LoaderManager-related event occurs, so be
sure to run the application while analyzing the logcat! Hopefully it’ll give you a better understanding of how
Loaders work in conjunction with the LoaderManager and the Activity/Fragment lifecycle. Be sure to filter the
logcat by application name (“com.adp.loadercustom”) for the best results!</p>
<p><a class="no-border" href="/assets/images/posts/2012/09/16/eclipse-screenshot.png">
<img src="/assets/images/posts/2012/09/16/eclipse-screenshot.png" style="border:0px; width:400px; height:260px;" />
</a></p>
<p>You can download the application from Google Play by clicking the badge below:</p>
<p><a class="no-border" href="https://play.google.com/store/apps/details?id=com.adp.loadercustom">
<img src="/assets/images/posts/2012/09/16/google-play-badge.png" />
</a></p>
<p><a href="https://github.com/alexjlockwood/AppListLoader">The source code is available on GitHub</a>.
An excessive amount of comments flesh out the entire application-Loader workflow. Download it,
import it as an eclipse project, and modify it all you want!</p>
<p>Let me know if these posts have been helpful by leaving a comment below! As always,
don’t hesitate to ask questions either!</p>
Follow This Blog On Google Currents!https://www.androiddesignpatterns.com/2012/08/follow-this-blog-on-google-currents_8022.html2012-08-26T00:00:00+00:002012-08-26T00:00:00+00:00<p>Hi all,</p>
<p>Hi all,</p>
<p>I’ve recently made this blog available on Google Currents! Install the application and
subscribe by clicking <a href="https://www.google.com/producer/editions/CAow5Ir3AQ/android_design_patterns">this link</a>.</p>
<p>If you have never used Google Currents, I strongly recommend that you try it out. It’s a
really great way to keep up with the latest news, blogs, and your favorite Google+ streams,
and it works seamlessly offline (which I’ve found is great for long plane rides). If you’re
a long time Flipboard user, I recommend you give it a try as well… in my opinion, Currents
is easier to navigate and feels much more like a native Android application. That said,
I do tend to be a bit biased towards the native Google apps. :P</p>
<!--more-->
<p>As always, don’t hesitate to leave a comment if you find a bug or have any suggestions on
how I can improve the edition! I’m going to try really hard to keep it up-to-date for those
of you who follow this blog and can’t get enough of Google Currents!</p>
<p>Cheers,<br />
Alex</p>
Implementing Loaders (part 3)https://www.androiddesignpatterns.com/2012/08/implementing-loaders.html2012-08-21T00:00:00+00:002014-01-16T00:00:00+00:00<p>This post introduces the <code class="highlighter-rouge">Loader<D></code> class as well as custom Loader implementations.
This is the third of a series of posts I will be writing on Loaders and the LoaderManager:</p>
<p>This post introduces the <code class="highlighter-rouge">Loader<D></code> class as well as custom Loader implementations.
This is the third of a series of posts I will be writing on Loaders and the LoaderManager:</p>
<ul>
<li><strong>Part 1:</strong> <a href="/2012/07/loaders-and-loadermanager-background.html">Life Before Loaders</a></li>
<li><strong>Part 2:</strong> <a href="/2012/07/understanding-loadermanager.html">Understanding the LoaderManager</a></li>
<li><strong>Part 3:</strong> <a href="/2012/08/implementing-loaders.html">Implementing Loaders</a></li>
<li><strong>Part 4:</strong> <a href="/2012/09/tutorial-loader-loadermanager.html">Tutorial: AppListLoader</a></li>
</ul>
<p>First things first, if you haven’t read my previous two posts, I suggest you do so before continuing further.
Here is a very brief summary of what this blog has covered so far.
<a href="/2012/07/loaders-and-loadermanager-background.html">Life Before Loaders (part 1)</a> described the
flaws of the pre-Honeycomb 3.0 API and its tendency to perform lengthy queries on the main UI thread.
These UI-unfriendly APIs resulted in unresponsive applications and were the primary motivation for introducing
the Loader and the LoaderManager in Android 3.0.
<a href="/2012/07/understanding-loadermanager.html">Understanding the LoaderManager (part 2)</a> introduced
the LoaderManager class and its role in delivering asynchronously loaded data to the client. The LoaderManager
manages its Loaders across the Activity/Fragment lifecycle and can retain loaded data across configuration changes.</p>
<!--more-->
<h3 id="loader-basics">Loader Basics</h3>
<p>Loaders are responsible for performing queries on a separate thread, monitoring the data source for changes,
and delivering new results to a registered listener (usually the LoaderManager) when changes are detected.
These characteristics make Loaders a powerful addition to the Android SDK for several reasons:</p>
<ol>
<li>
<p><b>They encapsulate the actual loading of data.</b> The Activity/Fragment no longer needs to know how to load data.
Instead, the Activity/Fragment delegates the task to the Loader, which carries out the request behind the scenes
and has its results delivered back to the Activity/Fragment.</p>
</li>
<li>
<p><b>They abstract out the idea of threads from the client.</b> The Activity/Fragment does not need to worry
about offloading queries to a separate thread, as the Loader will do this automatically. This reduces
code complexity and eliminates potential thread-related bugs.</p>
</li>
<li>
<p><b>They are entirely <i>event-driven</i>.</b> Loaders monitor the underlying data source and automatically
perform new loads for up-to-date results when changes are detected. This makes working with Loaders
easy, as the client can simply trust that the Loader will auto-update its data on its own.
All the Activity/Fragment has to do is initialize the Loader and respond to any results that might
be delivered. Everything in between is done by the Loader.</p>
</li>
</ol>
<p>Loaders are a somewhat advanced topic and may take some time getting used to. We begin by analyzing
its four defining characteristics in the next section.</p>
<h3 id="what-makes-up-a-loader">What Makes Up a Loader?</h3>
<p>There are four characteristics which ultimately determine a Loader’s behavior:</p>
<ol>
<li>
<p><b>A task to perform the asynchronous load.</b> To ensure that loads are done on a separate thread,
subclasses should extend <code class="highlighter-rouge">AsyncTaskLoader<D></code> as opposed to the <code class="highlighter-rouge">Loader<D></code> class.
<code class="highlighter-rouge">AsyncTaskLoader<D></code> is an abstract Loader which provides an <code class="highlighter-rouge">AsyncTask</code> to do its work.
When subclassed, implementing the asynchronous task is as simple as implementing the abstract
<code class="highlighter-rouge">loadInBackground()</code> method, which is called on a worker thread to perform the data load.</p>
</li>
<li>
<p><b>A registered listener to receive the Loader’s results when it completes a load.</b><sup><a href="#footnote1" id="ref1">1</a></sup>
For each of its Loaders, the LoaderManager registers an <code class="highlighter-rouge">OnLoadCompleteListener<D></code> which will forward
the Loader’s delivered results to the client with a call to <code class="highlighter-rouge">onLoadFinished(Loader<D> loader, D result)</code>.
Loaders should deliver results to these registered listeners with a call to <code class="highlighter-rouge">Loader#deliverResult(D result)</code>.</p>
</li>
<li><b>One of three<sup><a href="#footnote2" id="ref2">2</a></sup> distinct states.</b> Any given Loader will either be in a
<em>started</em>, <em>stopped</em>, or <em>reset</em> state:
<ul>
<li>Loaders in a <em>started state</em> execute loads and may deliver their results to the listener at any
time. Started Loaders should monitor for changes and perform new loads when changes are detected.
Once started, the Loader will remain in a started state until it is either stopped or reset.
This is the only state in which <code class="highlighter-rouge">onLoadFinished</code> will ever be called.</li>
<li>Loaders in a <em>stopped state</em> continue to monitor for changes but should <strong>not</strong>
deliver results to the client. From a stopped state, the Loader may either be started or reset.</li>
<li>Loaders in a <em>reset state</em> should <strong>not</strong> execute new loads, should <strong>not</strong> deliver new
results, and should <strong>not</strong> monitor for changes. When a loader enters a reset state, it should
invalidate and free any data associated with it for garbage collection (likewise, the client should
make sure they remove any references to this data, since it will no longer be available). More
often than not, reset Loaders will never be called again; however, in some cases they may be started,
so they should be able to start running properly again if necessary.</li>
</ul>
</li>
<li><strong>An observer to receive notifications when the data source has changed.</strong> Loaders should implement an observer of some sort
(i.e. a <code class="highlighter-rouge">ContentObserver</code>, a <code class="highlighter-rouge">BroadcastReceiver</code>, etc.) to monitor the underlying data source for changes.
When a change is detected, the observer should call <code class="highlighter-rouge">Loader#onContentChanged()</code>, which will either (a) force a new
load if the Loader is in a started state or, (b) raise a flag indicating that a change has been made so that if the Loader
is ever started again, it will know that it should reload its data.</li>
</ol>
<p>By now you should have a basic understanding of how Loaders work. If not, I suggest you let it sink in for a bit and
come back later to read through once more (reading the
<a href="http://developer.android.com/reference/android/content/Loader.html">documentation</a> never hurts either!).
That being said, let’s get our hands dirty with the actual code!</p>
<h3 id="implementing-the-loader">Implementing the Loader</h3>
<p>As I stated earlier, there is a lot that you must keep in mind when implementing your own custom Loaders.
Subclasses must implement <code class="highlighter-rouge">loadInBackground()</code> and should override <code class="highlighter-rouge">onStartLoading()</code>,
<code class="highlighter-rouge">onStopLoading()</code>, <code class="highlighter-rouge">onReset()</code>, <code class="highlighter-rouge">onCanceled()</code>, and
<code class="highlighter-rouge">deliverResult(D results)</code> to achieve a fully functioning Loader. Overriding these methods is
very important as the LoaderManager will call them regularly depending on the state of the Activity/Fragment
lifecycle. For example, when an Activity is first started, the Activity instructs the LoaderManager to
start each of its Loaders in <code class="highlighter-rouge">Activity#onStart()</code>. If a Loader is not already started, the
LoaderManager calls <code class="highlighter-rouge">startLoading()</code>, which puts the Loader in a started state and immediately
calls the Loader’s <code class="highlighter-rouge">onStartLoading()</code> method. In other words, a lot of work that the LoaderManager
does behind the scenes <strong>relies on the Loader being correctly implemented</strong>, so don’t take the task of
implementing these methods lightly!</p>
<p>The code below serves as a template of what a Loader implementation typically looks like. The <code class="highlighter-rouge">SampleLoader</code>
queries a list of <code class="highlighter-rouge">SampleItem</code> objects and delivers a <code class="highlighter-rouge">List<SampleItem></code> to the client:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SampleLoader</span> <span class="kd">extends</span> <span class="n">AsyncTaskLoader</span><span class="o"><</span><span class="n">List</span><span class="o"><</span><span class="n">SampleItem</span><span class="o">>></span> <span class="o">{</span>
<span class="c1">// We hold a reference to the Loader’s data here.</span>
<span class="kd">private</span> <span class="n">List</span><span class="o"><</span><span class="n">SampleItem</span><span class="o">></span> <span class="n">mData</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">SampleLoader</span><span class="o">(</span><span class="n">Context</span> <span class="n">ctx</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Loaders may be used across multiple Activitys (assuming they aren't</span>
<span class="c1">// bound to the LoaderManager), so NEVER hold a reference to the context</span>
<span class="c1">// directly. Doing so will cause you to leak an entire Activity's context.</span>
<span class="c1">// The superclass constructor will store a reference to the Application</span>
<span class="c1">// Context instead, and can be retrieved with a call to getContext().</span>
<span class="kd">super</span><span class="o">(</span><span class="n">ctx</span><span class="o">);</span>
<span class="o">}</span>
<span class="cm">/****************************************************/</span>
<span class="cm">/** (1) A task that performs the asynchronous load **/</span>
<span class="cm">/****************************************************/</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">List</span><span class="o"><</span><span class="n">SampleItem</span><span class="o">></span> <span class="nf">loadInBackground</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// This method is called on a background thread and should generate a</span>
<span class="c1">// new set of data to be delivered back to the client.</span>
<span class="n">List</span><span class="o"><</span><span class="n">SampleItem</span><span class="o">></span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">SampleItem</span><span class="o">>();</span>
<span class="c1">// TODO: Perform the query here and add the results to 'data'.</span>
<span class="k">return</span> <span class="n">data</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/********************************************************/</span>
<span class="cm">/** (2) Deliver the results to the registered listener **/</span>
<span class="cm">/********************************************************/</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">deliverResult</span><span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">SampleItem</span><span class="o">></span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isReset</span><span class="o">())</span> <span class="o">{</span>
<span class="c1">// The Loader has been reset; ignore the result and invalidate the data.</span>
<span class="n">releaseResources</span><span class="o">(</span><span class="n">data</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// Hold a reference to the old data so it doesn't get garbage collected.</span>
<span class="c1">// We must protect it until the new data has been delivered.</span>
<span class="n">List</span><span class="o"><</span><span class="n">SampleItem</span><span class="o">></span> <span class="n">oldData</span> <span class="o">=</span> <span class="n">mData</span><span class="o">;</span>
<span class="n">mData</span> <span class="o">=</span> <span class="n">data</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isStarted</span><span class="o">())</span> <span class="o">{</span>
<span class="c1">// If the Loader is in a started state, deliver the results to the</span>
<span class="c1">// client. The superclass method does this for us.</span>
<span class="kd">super</span><span class="o">.</span><span class="na">deliverResult</span><span class="o">(</span><span class="n">data</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// Invalidate the old data as we don't need it any more.</span>
<span class="k">if</span> <span class="o">(</span><span class="n">oldData</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">oldData</span> <span class="o">!=</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
<span class="n">releaseResources</span><span class="o">(</span><span class="n">oldData</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="cm">/*********************************************************/</span>
<span class="cm">/** (3) Implement the Loader’s state-dependent behavior **/</span>
<span class="cm">/*********************************************************/</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onStartLoading</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mData</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Deliver any previously loaded data immediately.</span>
<span class="n">deliverResult</span><span class="o">(</span><span class="n">mData</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// Begin monitoring the underlying data source.</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mObserver</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mObserver</span> <span class="o">=</span> <span class="k">new</span> <span class="n">SampleObserver</span><span class="o">();</span>
<span class="c1">// TODO: register the observer</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">takeContentChanged</span><span class="o">()</span> <span class="o">||</span> <span class="n">mData</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// When the observer detects a change, it should call onContentChanged()</span>
<span class="c1">// on the Loader, which will cause the next call to takeContentChanged()</span>
<span class="c1">// to return true. If this is ever the case (or if the current data is</span>
<span class="c1">// null), we force a new load.</span>
<span class="n">forceLoad</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onStopLoading</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// The Loader is in a stopped state, so we should attempt to cancel the </span>
<span class="c1">// current load (if there is one).</span>
<span class="n">cancelLoad</span><span class="o">();</span>
<span class="c1">// Note that we leave the observer as is. Loaders in a stopped state</span>
<span class="c1">// should still monitor the data source for changes so that the Loader</span>
<span class="c1">// will know to force a new load if it is ever started again.</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onReset</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// Ensure the loader has been stopped.</span>
<span class="n">onStopLoading</span><span class="o">();</span>
<span class="c1">// At this point we can release the resources associated with 'mData'.</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mData</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">releaseResources</span><span class="o">(</span><span class="n">mData</span><span class="o">);</span>
<span class="n">mData</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// The Loader is being reset, so we should stop monitoring for changes.</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mObserver</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// TODO: unregister the observer</span>
<span class="n">mObserver</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onCanceled</span><span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">SampleItem</span><span class="o">></span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Attempt to cancel the current asynchronous load.</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCanceled</span><span class="o">(</span><span class="n">data</span><span class="o">);</span>
<span class="c1">// The load has been canceled, so we should release the resources</span>
<span class="c1">// associated with 'data'.</span>
<span class="n">releaseResources</span><span class="o">(</span><span class="n">data</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">releaseResources</span><span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">SampleItem</span><span class="o">></span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// For a simple List, there is nothing to do. For something like a Cursor, we </span>
<span class="c1">// would close it in this method. All resources associated with the Loader</span>
<span class="c1">// should be released here.</span>
<span class="o">}</span>
<span class="cm">/*********************************************************************/</span>
<span class="cm">/** (4) Observer which receives notifications when the data changes **/</span>
<span class="cm">/*********************************************************************/</span>
<span class="c1">// NOTE: Implementing an observer is outside the scope of this post (this example</span>
<span class="c1">// uses a made-up "SampleObserver" to illustrate when/where the observer should </span>
<span class="c1">// be initialized). </span>
<span class="c1">// The observer could be anything so long as it is able to detect content changes</span>
<span class="c1">// and report them to the loader with a call to onContentChanged(). For example,</span>
<span class="c1">// if you were writing a Loader which loads a list of all installed applications</span>
<span class="c1">// on the device, the observer could be a BroadcastReceiver that listens for the</span>
<span class="c1">// ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular </span>
<span class="c1">// Loader whenever the receiver detects that a new application has been installed.</span>
<span class="c1">// Please don’t hesitate to leave a comment if you still find this confusing! :)</span>
<span class="kd">private</span> <span class="n">SampleObserver</span> <span class="n">mObserver</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="conclusion">Conclusion</h3>
<p>I hope these posts were useful and gave you a better understanding of how Loaders and the LoaderManager work
together to perform asynchronous, auto-updating queries. Remember that Loaders are your friends… if you use
them, your app will benefit in both responsiveness and the amount of code you need to write to get everything
working properly! Hopefully I could help lessen the learning curve a bit by detailing them out!</p>
<p>As always, please don’t hesitate to leave a comment if you have any questions! And don’t
forget to +1 this blog in the top right corner if you found it helpful!</p>
<hr class="footnote-divider" />
<p><sup id="footnote1">1</sup> You don’t need to worry about registering a listener for your Loader unless you plan on using it without the LoaderManager. The LoaderManager will act as this “listener” and will forward any results that the Loader delivers to the <code class="highlighter-rouge">LoaderCallbacks#onLoadFinished</code> method. <a href="#ref1" title="Jump to footnote 1.">↩</a></p>
<p><sup id="footnote2">2</sup> Loaders may also be in an <a href="http://developer.android.com/reference/android/content/Loader.html#onAbandon()">“abandoned”</a> state. This is an optional intermediary state between “stopped” and “reset” and is not discussed here for the sake of brevity. That said, in my experience implementing <code class="highlighter-rouge">onAbandon()</code> is usually not necessary. <a href="#ref2" title="Jump to footnote 2.">↩</a></p>
'Exit Application?' Dialogs Are Evil, Don't Use Them!https://www.androiddesignpatterns.com/2012/08/exit-application-dialogs-are-evil-dont.html2012-08-07T00:00:00+00:002012-08-07T00:00:00+00:00<p>Here’s a question that is worth thinking about:</p>
<p>Here’s a question that is worth thinking about:</p>
<blockquote>
<p>Should I implement an “Exit application?” dialog in my app?</p>
</blockquote>
<p>In my experience, the answer is almost always <strong>no</strong>. Consider the official Flickr app,
as an example. At the main screen, the user clicks the back button and is immediately
prompted with a dialog, questioning whether or not the user wishes to exit the application:</p>
<!--more-->
<table>
<tbody>
<tr>
<td style="text-align: center;">
<a class="no-border" href="/assets/images/posts/2012/08/07/back-button-pressed.png"><img alt="Back button pressed." src="/assets/images/posts/2012/08/07/back-button-pressed.png" /></a>
</td>
<td style="text-align: center;">
<a class="no-border" href="/assets/images/posts/2012/08/07/dialog-showing.png"><img alt="An exit dialog is shown." src="/assets/images/posts/2012/08/07/dialog-showing.png" /></a>
</td>
</tr>
<tr>
<td style="text-align: center;">(a) Back button pressed.</td>
<td style="text-align: center;">(b) "Exit Flickr?"</td>
</tr>
</tbody>
</table>
<p>So what went wrong? Well, pretty much everything, at least in my opinion.
Here are the three major flaws I see in Flickr’s decision to include the dialog:</p>
<ol>
<li>
<p><strong>It slows down the user experience.</strong> An additional click is required to leave the application.
Sure, it doesn’t seem like much… but zero clicks is always better than one. Including the
dialog will annoy the occasional meticulous power user and will make it much more likely
that people like me will write-up angry rants about it online. To make matters worse, Flickr’s
dialog incorrectly positions the “OK” and “Cancel” buttons, which as of Android 4.0, should be
positioned on the right and left respectively. This is also not a <em>huge</em> deal, but it forces
users to think more than they should need to, and the simple action of exiting the application is
no longer seamless as a result.</p>
</li>
<li>
<p><strong>It is inconsistent.</strong> Name one native Android application that warns the user when they are
about to exit the application. If you can’t, that’s because there are none. Of all the familiar,
Google-made Android apps (Gmail, Google Drive, etc.), exactly <em>none</em> of them exhibit this
behavior. The user expects the back button to bring him or her back to the top activity on the
Activity Stack; there is no reason why it shouldn’t do otherwise in this simple situation.</p>
</li>
<li>
<p><strong>It serves absolutely no purpose.</strong> What baffles me the most, however, is that there is no
reason to confirm exit in the first place. <em>Maybe</em> the dialog would be OK if there was a
long-running operation running in the background that is specific to the Activity (i.e. an
<code class="highlighter-rouge">AsyncTask</code> that the user might not want canceled). A dialog <em>might</em> also
make sense if the application took a long time to load, for example, a fancy, video intensive FPS like
<a href="https://play.google.com/store/apps/details?id=com.madfingergames.deadtrigger">Dead Trigger</a>.
In Flickr’s case, there is no acceptable reason why the user shouldn’t be allowed to “back-out” of
the application immediately.</p>
</li>
</ol>
<p>In my opinion, dialogs are both slow and annoying, and should be used as little as possible.
Always prefer the faster “edit in place” user model (as described
<a href="http://developer.android.com/reference/android/app/Activity.html#SavingPersistentState">here</a>)
when it comes to saving persistent state, and never prompt the user when they wish to “back-out” of the
application unless you have a <em>very</em> good reason for doing so.</p>
<p>As always, let me know if you agree or disagree in the comments below!</p>
<p><strong>EDIT:</strong> For more discussion on this topic, I recommend reading through the content/comments
of <a href="https://plus.google.com/118417777153109946393/posts/EiXqUDrr6jT">this Google+ post</a> (made by
<a class="g-profile" href="http://plus.google.com/118417777153109946393" target="_blank">+Cyril Mottier</a>,
a very talented Android developer recognized by Google as a
<a href="https://developers.google.com/experts/">Android Developer Expert</a>).</p>
Understanding the LoaderManager (part 2)https://www.androiddesignpatterns.com/2012/07/understanding-loadermanager.html2012-07-22T00:00:00+00:002012-07-22T00:00:00+00:00<p>This post introduces the <code class="highlighter-rouge">LoaderManager</code> class. This is the second of a series of posts I will
be writing on Loaders and the LoaderManager:</p>
<p>This post introduces the <code class="highlighter-rouge">LoaderManager</code> class. This is the second of a series of posts I will
be writing on Loaders and the LoaderManager:</p>
<ul>
<li><strong>Part 1:</strong> <a href="/2012/07/loaders-and-loadermanager-background.html">Life Before Loaders</a></li>
<li><strong>Part 2:</strong> <a href="/2012/07/understanding-loadermanager.html">Understanding the LoaderManager</a></li>
<li><strong>Part 3:</strong> <a href="/2012/08/implementing-loaders.html">Implementing Loaders</a></li>
<li><strong>Part 4:</strong> <a href="/2012/09/tutorial-loader-loadermanager.html">Tutorial: AppListLoader</a></li>
</ul>
<p><strong>Note:</strong> Understanding the <code class="highlighter-rouge">LoaderManager</code> requires some general knowledge about how <code class="highlighter-rouge">Loader</code>s work. Their implementation will be covered extensively in my
<a href="/2012/08/implementing-loaders.html">next post</a>. For now, you should think
of Loaders as simple, self-contained objects that (1) load data on a separate thread, and (2) monitor the underlying data
source for updates, re-querying when changes are detected. This is more than enough to get you through the contents
of this post. All Loaders are assumed to be 100% correctly implemented in this post.</p>
<h3 id="what-is-the-loadermanager">What is the <code class="highlighter-rouge">LoaderManager</code>?</h3>
<p>Simply stated, the <code class="highlighter-rouge">LoaderManager</code> is responsible for managing one or more <code class="highlighter-rouge">Loader</code>s
associated with an Activity or Fragment. Each Activity and each Fragment has exactly one LoaderManager
instance that is in charge of starting, stopping, retaining, restarting, and destroying its Loaders.
These events are sometimes initiated directly by the client, by calling <code class="highlighter-rouge">initLoader()</code>,
<code class="highlighter-rouge">restartLoader()</code>, or <code class="highlighter-rouge">destroyLoader()</code>. Just as often, however, these events
are triggered by major Activity/Fragment lifecycle events. For example, when an Activity is destroyed,
the Activity instructs its LoaderManager to destroy and close its Loaders (as well as any resources
associated with them, such as a Cursor).</p>
<!--more-->
<p>The LoaderManager does not know how data is loaded, nor does it need to. Rather, the LoaderManager
instructs its Loaders when to start/stop/reset their load, retaining their state across configuration
changes and providing a simple interface for delivering results back to the client. In this way, the
LoaderManager is a much more intelligent and generic implementation of the now-deprecated
<code class="highlighter-rouge">startManagingCursor</code> method. While both manage data across the twists and turns of the
Activity lifecycle, the LoaderManager is far superior for several reasons:</p>
<ul>
<li>
<p><strong><code class="highlighter-rouge">startManagingCursor</code> manages Cursors, whereas the LoaderManager manages <code class="highlighter-rouge">Loader<D></code> objects.</strong>
The advantage here is that <code class="highlighter-rouge">Loader<D></code> is generic, where <code class="highlighter-rouge">D</code> is the container object that holds the
loaded data. In other words, the data source doesn’t have to be a Cursor; it could be a <code class="highlighter-rouge">List</code>, a
<code class="highlighter-rouge">JSONArray</code>… anything. The LoaderManager is independent of the container object that holds the data and is
much more flexible as a result.</p>
</li>
<li>
<p><strong>Calling <code class="highlighter-rouge">startManagingCursor</code> will make the Activity call <code class="highlighter-rouge">requery()</code> on the managed cursor.</strong>
As mentioned in the previous post, <code class="highlighter-rouge">requery()</code> is a potentially expensive operation that is performed on the
main UI thread. Subclasses of the <code class="highlighter-rouge">Loader<D></code> class, on the other hand, are expected to load their data
asynchronously, so using the LoaderManager will never block the UI thread.</p>
</li>
<li>
<p><strong><code class="highlighter-rouge">startManagingCursor</code> does not retain the Cursor’s state across configuration changes.</strong>
Instead, each time the Activity is destroyed due to a configuration change (a simple orientation change, for example),
the Cursor is destroyed and must be requeried. The LoaderManager is much more intelligent in that it retains its Loaders’
state across configuration changes, and thus doesn’t need to requery its data.</p>
</li>
<li>
<p><strong>The LoaderManager provides seamless monitoring of data!</strong> Whenever the Loader’s data source is modified, the LoaderManager
will receive a new asynchronous load from the corresponding Loader, and will return the updated data to the client. (Note: the
LoaderManager will only be notified of these changes if the Loader is implemented correctly. We will discuss how to implement
custom Loaders in <a href="/2012/08/implementing-loaders.html">part 3</a> of this series of posts).</p>
</li>
</ul>
<p>If you feel overwhelmed by the details above, I wouldn’t stress over it. The most important thing to take away from this is that the
<em>LoaderManager makes your life easy.</em> It initializes, manages, and destroys Loaders for you, reducing both coding complexity and
subtle lifecycle-related bugs in your Activitys and Fragments. Further, interacting with the LoaderManager involves implementing three
simple callback methods. We discuss the <code class="highlighter-rouge">LoaderManager.LoaderCallbacks<D></code> in the next section.</p>
<h3 id="implementing-the-loadermanagerloadercallbacksd-interface">Implementing the <code class="highlighter-rouge">LoaderManager.LoaderCallbacks<D></code> Interface</h3>
<p>The <code class="highlighter-rouge">LoaderManager.LoaderCallbacks<D></code> interface is a simple contract that the <code class="highlighter-rouge">LoaderManager</code>
uses to report data back to the client. Each Loader gets its own callback object that the LoaderManager will interact with.
This callback object fills in the gaps of the abstract <code class="highlighter-rouge">LoaderManager</code> implementation, telling it how to
instantiate the Loader (<code class="highlighter-rouge">onCreateLoader</code>) and providing instructions when its load is complete/reset
(<code class="highlighter-rouge">onLoadFinished</code> and <code class="highlighter-rouge">onLoadReset</code>, respectively). Most often you will implement the callbacks
as part of the component itself, by having your Activity or Fragment implement the <code class="highlighter-rouge">LoaderManager.LoaderCallbacks<D></code>
interface:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SampleActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="kd">implements</span> <span class="n">LoaderManager</span><span class="o">.</span><span class="na">LoaderCallbacks</span><span class="o"><</span><span class="n">D</span><span class="o">></span> <span class="o">{</span>
<span class="kd">public</span> <span class="n">Loader</span><span class="o"><</span><span class="n">D</span><span class="o">></span> <span class="nf">onCreateLoader</span><span class="o">(</span><span class="kt">int</span> <span class="n">id</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onLoadFinished</span><span class="o">(</span><span class="n">Loader</span><span class="o"><</span><span class="n">D</span><span class="o">></span> <span class="n">loader</span><span class="o">,</span> <span class="n">D</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onLoaderReset</span><span class="o">(</span><span class="n">Loader</span><span class="o"><</span><span class="n">D</span><span class="o">></span> <span class="n">loader</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="cm">/* ... */</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Once instantiated, the client passes the callbacks object (“<code class="highlighter-rouge">this</code>”, in this case) as the
third argument to the LoaderManager’s <code class="highlighter-rouge">initLoader</code> method, and will be bound to the Loader
as soon as it is created.</p>
<p>Overall, implementing the <a href="http://developer.android.com/reference/android/app/LoaderManager.LoaderCallbacks.html">callbacks</a>
is straightforward. Each callback method serves a specific purpose that makes interacting with the LoaderManager easy:</p>
<ul>
<li>
<p><code class="highlighter-rouge">onCreateLoader</code> is a factory method that simply returns a new <code class="highlighter-rouge">Loader</code>. The LoaderManager will
call this method when it first creates the Loader.</p>
</li>
<li>
<p><code class="highlighter-rouge">onLoadFinished</code> is called automatically when a Loader has finished its load. This method is typically
where the client will update the application’s UI with the loaded data. The client may (and should) assume that
new data will be returned to this method each time new data is made available. Remember that it is the Loader’s
job to monitor the data source and to perform the actual asynchronous loads. The LoaderManager will receive these
loads once they have completed, and then pass the result to the callback object’s <code class="highlighter-rouge">onLoadFinished</code> method
for the client (i.e. the Activity/Fragment) to use.</p>
</li>
<li>
<p>Lastly, <code class="highlighter-rouge">onLoadReset</code> is called when the Loader’s data is about to be reset. This method gives you the
opportunity to remove any references to old data that may no longer be available.</p>
</li>
</ul>
<p>In the next section, we will discuss a commonly asked question from beginning Android developers: how to
transition from outdated managed Cursors to the much more powerful LoaderManager.</p>
<h3 id="transitioning-from-managed-cursors-to-the-loadermanager">Transitioning from Managed Cursors to the <code class="highlighter-rouge">LoaderManager</code></h3>
<p>The code below is similar in behavior to the sample in my <a href="/2012/07/loaders-and-loadermanager-background.html">previous post</a>.
The difference, of course, is that it has been updated to use the LoaderManager. The <code class="highlighter-rouge">CursorLoader</code> ensures that all
queries are performed asynchronously, thus guaranteeing that we won’t block the UI thread. Further, the LoaderManager manages
the <code class="highlighter-rouge">CursorLoader</code> across the Activity lifecycle, retaining its data on configuration changes and directing each
new data load to the callback’s <code class="highlighter-rouge">onLoadFinished</code> method, where the Activity is finally free to make use of the
queried Cursor.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SampleListActivity</span> <span class="kd">extends</span> <span class="n">ListActivity</span> <span class="kd">implements</span>
<span class="n">LoaderManager</span><span class="o">.</span><span class="na">LoaderCallbacks</span><span class="o"><</span><span class="n">Cursor</span><span class="o">></span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span><span class="o">[]</span> <span class="n">PROJECTION</span> <span class="o">=</span> <span class="k">new</span> <span class="n">String</span><span class="o">[]</span> <span class="o">{</span> <span class="s">"_id"</span><span class="o">,</span> <span class="s">"text_column"</span> <span class="o">};</span>
<span class="c1">// The loader's unique id. Loader ids are specific to the Activity or</span>
<span class="c1">// Fragment in which they reside.</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">LOADER_ID</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span>
<span class="c1">// The callbacks through which we will interact with the LoaderManager.</span>
<span class="kd">private</span> <span class="n">LoaderManager</span><span class="o">.</span><span class="na">LoaderCallbacks</span><span class="o"><</span><span class="n">Cursor</span><span class="o">></span> <span class="n">mCallbacks</span><span class="o">;</span>
<span class="c1">// The adapter that binds our data to the ListView</span>
<span class="kd">private</span> <span class="n">SimpleCursorAdapter</span> <span class="n">mAdapter</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">String</span><span class="o">[]</span> <span class="n">dataColumns</span> <span class="o">=</span> <span class="o">{</span> <span class="s">"text_column"</span> <span class="o">};</span>
<span class="kt">int</span><span class="o">[]</span> <span class="n">viewIDs</span> <span class="o">=</span> <span class="o">{</span> <span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">text_view</span> <span class="o">};</span>
<span class="c1">// Initialize the adapter. Note that we pass a 'null' Cursor as the</span>
<span class="c1">// third argument. We will pass the adapter a Cursor only when the</span>
<span class="c1">// data has finished loading for the first time (i.e. when the</span>
<span class="c1">// LoaderManager delivers the data to onLoadFinished). Also note</span>
<span class="c1">// that we have passed the '0' flag as the last argument. This</span>
<span class="c1">// prevents the adapter from registering a ContentObserver for the</span>
<span class="c1">// Cursor (the CursorLoader will do this for us!).</span>
<span class="n">mAdapter</span> <span class="o">=</span> <span class="k">new</span> <span class="n">SimpleCursorAdapter</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">list_item</span><span class="o">,</span>
<span class="kc">null</span><span class="o">,</span> <span class="n">dataColumns</span><span class="o">,</span> <span class="n">viewIDs</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="c1">// Associate the (now empty) adapter with the ListView.</span>
<span class="n">setListAdapter</span><span class="o">(</span><span class="n">mAdapter</span><span class="o">);</span>
<span class="c1">// The Activity (which implements the LoaderCallbacks<Cursor></span>
<span class="c1">// interface) is the callbacks object through which we will interact</span>
<span class="c1">// with the LoaderManager. The LoaderManager uses this object to</span>
<span class="c1">// instantiate the Loader and to notify the client when data is made</span>
<span class="c1">// available/unavailable.</span>
<span class="n">mCallbacks</span> <span class="o">=</span> <span class="k">this</span><span class="o">;</span>
<span class="c1">// Initialize the Loader with id '1' and callbacks 'mCallbacks'.</span>
<span class="c1">// If the loader doesn't already exist, one is created. Otherwise,</span>
<span class="c1">// the already created Loader is reused. In either case, the</span>
<span class="c1">// LoaderManager will manage the Loader across the Activity/Fragment</span>
<span class="c1">// lifecycle, will receive any new loads once they have completed,</span>
<span class="c1">// and will report this new data back to the 'mCallbacks' object.</span>
<span class="n">LoaderManager</span> <span class="n">lm</span> <span class="o">=</span> <span class="n">getLoaderManager</span><span class="o">();</span>
<span class="n">lm</span><span class="o">.</span><span class="na">initLoader</span><span class="o">(</span><span class="n">LOADER_ID</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">mCallbacks</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">Loader</span><span class="o"><</span><span class="n">Cursor</span><span class="o">></span> <span class="nf">onCreateLoader</span><span class="o">(</span><span class="kt">int</span> <span class="n">id</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Create a new CursorLoader with the following query parameters.</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">CursorLoader</span><span class="o">(</span><span class="n">SampleListActivity</span><span class="o">.</span><span class="na">this</span><span class="o">,</span> <span class="n">CONTENT_URI</span><span class="o">,</span>
<span class="n">PROJECTION</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onLoadFinished</span><span class="o">(</span><span class="n">Loader</span><span class="o"><</span><span class="n">Cursor</span><span class="o">></span> <span class="n">loader</span><span class="o">,</span> <span class="n">Cursor</span> <span class="n">cursor</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// A switch-case is useful when dealing with multiple Loaders/IDs</span>
<span class="k">switch</span> <span class="o">(</span><span class="n">loader</span><span class="o">.</span><span class="na">getId</span><span class="o">())</span> <span class="o">{</span>
<span class="k">case</span> <span class="nl">LOADER_ID:</span>
<span class="c1">// The asynchronous load is complete and the data</span>
<span class="c1">// is now available for use. Only now can we associate</span>
<span class="c1">// the queried Cursor with the SimpleCursorAdapter.</span>
<span class="n">mAdapter</span><span class="o">.</span><span class="na">swapCursor</span><span class="o">(</span><span class="n">cursor</span><span class="o">);</span>
<span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// The listview now displays the queried data.</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onLoaderReset</span><span class="o">(</span><span class="n">Loader</span><span class="o"><</span><span class="n">Cursor</span><span class="o">></span> <span class="n">loader</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// For whatever reason, the Loader's data is now unavailable.</span>
<span class="c1">// Remove any references to the old data by replacing it with</span>
<span class="c1">// a null Cursor.</span>
<span class="n">mAdapter</span><span class="o">.</span><span class="na">swapCursor</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="conclusion">Conclusion</h3>
<p>As its name suggests, the <code class="highlighter-rouge">LoaderManager</code> is responsible for managing <code class="highlighter-rouge">Loader</code>s across the
Activity/Fragment lifecycle. The LoaderManager is simple and its implementation usually requires very little code.
The tricky part is implementing the Loaders, the topic of the next post:
<a href="/2012/08/implementing-loaders.html">Implementing Loaders (part 3)</a>.</p>
<p>Leave a comment if you have any questions, or just to let me know if this post helped or not!
Don’t forget to +1 this blog in the top right corner too! :)</p>
Life Before Loaders (part 1)https://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html2012-07-06T00:00:00+00:002014-01-18T00:00:00+00:00<p>This post gives a brief introduction to <code class="highlighter-rouge">Loader</code>s and the <code class="highlighter-rouge">LoaderManager</code>. The first
section describes how data was loaded prior to the release of Android 3.0, pointing out out some of the flaws
of the pre-Honeycomb APIs. The second section defines the purpose of each class and summarizes their powerful
ability in asynchronously loading data.</p>
<p>This post gives a brief introduction to <code class="highlighter-rouge">Loader</code>s and the <code class="highlighter-rouge">LoaderManager</code>. The first
section describes how data was loaded prior to the release of Android 3.0, pointing out out some of the flaws
of the pre-Honeycomb APIs. The second section defines the purpose of each class and summarizes their powerful
ability in asynchronously loading data.</p>
<p>This is the first of a series of posts I will be writing on Loaders and the LoaderManager:</p>
<ul>
<li><strong>Part 1:</strong> <a href="/2012/07/loaders-and-loadermanager-background.html">Life Before Loaders</a></li>
<li><strong>Part 2:</strong> <a href="/2012/07/understanding-loadermanager.html">Understanding the LoaderManager</a></li>
<li><strong>Part 3:</strong> <a href="/2012/08/implementing-loaders.html">Implementing Loaders</a></li>
<li><strong>Part 4:</strong> <a href="/2012/09/tutorial-loader-loadermanager.html">Tutorial: AppListLoader</a></li>
</ul>
<p>If you know nothing about <code class="highlighter-rouge">Loader</code>s and the <code class="highlighter-rouge">LoaderManager</code>, I strongly recommend you read the
<a href="http://developer.android.com/guide/components/loaders.html">documentation</a> before continuing forward.</p>
<h3 id="the-not-so-distant-past">The Not-So-Distant Past</h3>
<p>Before Android 3.0, many Android applications lacked in responsiveness. UI interactions glitched, transitions
between activities lagged, and ANR (Application Not Responding) dialogs rendered apps totally useless. This
lack of responsiveness stemmed mostly from the fact that developers were performing queries on the UI
thread—a very poor choice for lengthy operations like loading data.</p>
<p>While the <a href="http://developer.android.com/guide/practices/responsiveness.html">documentation</a> has always
stressed the importance of instant feedback, the pre-Honeycomb APIs simply did not encourage this behavior. Before
Loaders, cursors were primarily managed and queried for with two (now deprecated) <code class="highlighter-rouge">Activity</code> methods:</p>
<!--more-->
<ul>
<li>
<p><code class="highlighter-rouge">public void startManagingCursor(Cursor)</code></p>
<p>Tells the activity to take care of managing the cursor’s lifecycle based on the activity’s lifecycle. The
cursor will automatically be deactivated (<code class="highlighter-rouge">deactivate()</code>) when the activity is stopped, and will
automatically be closed (<code class="highlighter-rouge">close()</code>) when the activity is destroyed. When the activity is stopped
and then later restarted, the Cursor is re-queried (<code class="highlighter-rouge">requery()</code>) for the most up-to-date data.</p>
</li>
<li>
<p><code class="highlighter-rouge">public Cursor managedQuery(Uri, String, String, String, String)</code></p>
<p>A wrapper around the <code class="highlighter-rouge">ContentResolver</code>’s <code class="highlighter-rouge">query()</code> method. In addition to performing the
query, it begins management of the cursor (that is, <code class="highlighter-rouge">startManagingCursor(cursor)</code> is called before
it is returned).</p>
</li>
</ul>
<p>While convenient, these methods were deeply flawed in that they performed queries on the UI thread. What’s more,
the “managed cursors” did not retain their data across <code class="highlighter-rouge">Activity</code> configuration changes. The need to
<code class="highlighter-rouge">requery()</code> the cursor’s data in these situations was unnecessary, inefficient, and made orientation
changes clunky and sluggish as a result.</p>
<h3 id="the-problem-with-managed-cursors">The Problem with “Managed <code class="highlighter-rouge">Cursor</code>s”</h3>
<p>Let’s illustrate the problem with “managed cursors” through a simple code sample. Given below is a
<code class="highlighter-rouge">ListActivity</code> that loads data using the pre-Honeycomb APIs. The activity makes a query
to the <code class="highlighter-rouge">ContentProvider</code> and begins management of the returned cursor. The results are then bound to
a <code class="highlighter-rouge">SimpleCursorAdapter</code>, and are displayed on the screen in a <code class="highlighter-rouge">ListView</code>. The code has
been condensed for simplicity.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SampleListActivity</span> <span class="kd">extends</span> <span class="n">ListActivity</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span><span class="o">[]</span> <span class="n">PROJECTION</span> <span class="o">=</span> <span class="k">new</span> <span class="n">String</span><span class="o">[]</span> <span class="o">{</span><span class="s">"_id"</span><span class="o">,</span> <span class="s">"text_column"</span><span class="o">};</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="c1">// Performs a "managed query" to the ContentProvider. The Activity </span>
<span class="c1">// will handle closing and requerying the cursor.</span>
<span class="c1">//</span>
<span class="c1">// WARNING!! This query (and any subsequent re-queries) will be</span>
<span class="c1">// performed on the UI Thread!!</span>
<span class="n">Cursor</span> <span class="n">cursor</span> <span class="o">=</span> <span class="n">managedQuery</span><span class="o">(</span>
<span class="n">CONTENT_URI</span><span class="o">,</span> <span class="c1">// The Uri constant in your ContentProvider class</span>
<span class="n">PROJECTION</span><span class="o">,</span> <span class="c1">// The columns to return for each data row</span>
<span class="kc">null</span><span class="o">,</span> <span class="c1">// No where clause</span>
<span class="kc">null</span><span class="o">,</span> <span class="c1">// No where clause</span>
<span class="kc">null</span><span class="o">);</span> <span class="c1">// No sort order</span>
<span class="n">String</span><span class="o">[]</span> <span class="n">dataColumns</span> <span class="o">=</span> <span class="o">{</span> <span class="s">"text_column"</span> <span class="o">};</span>
<span class="kt">int</span><span class="o">[]</span> <span class="n">viewIDs</span> <span class="o">=</span> <span class="o">{</span> <span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">text_view</span> <span class="o">};</span>
<span class="c1">// Create the backing adapter for the ListView.</span>
<span class="c1">//</span>
<span class="c1">// WARNING!! While not readily obvious, using this constructor will </span>
<span class="c1">// tell the CursorAdapter to register a ContentObserver that will</span>
<span class="c1">// monitor the underlying data source. As part of the monitoring</span>
<span class="c1">// process, the ContentObserver will call requery() on the cursor </span>
<span class="c1">// each time the data is updated. Since Cursor#requery() is performed </span>
<span class="c1">// on the UI thread, this constructor should be avoided at all costs!</span>
<span class="n">SimpleCursorAdapter</span> <span class="n">adapter</span> <span class="o">=</span> <span class="k">new</span> <span class="n">SimpleCursorAdapter</span><span class="o">(</span>
<span class="k">this</span><span class="o">,</span> <span class="c1">// The Activity context</span>
<span class="n">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">list_item</span><span class="o">,</span> <span class="c1">// Points to the XML for a list item</span>
<span class="n">cursor</span><span class="o">,</span> <span class="c1">// Cursor that contains the data to display</span>
<span class="n">dataColumns</span><span class="o">,</span> <span class="c1">// Bind the data in column "text_column"...</span>
<span class="n">viewIDs</span><span class="o">);</span> <span class="c1">// ...to the TextView with id "R.id.text_view"</span>
<span class="c1">// Sets the ListView's adapter to be the cursor adapter that was </span>
<span class="c1">// just created.</span>
<span class="n">setListAdapter</span><span class="o">(</span><span class="n">adapter</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>There are three problems with the code above. If you have understood this post so far, the first two
shouldn’t be difficult to spot:</p>
<ol>
<li>
<p><code class="highlighter-rouge">managedQuery</code> performs a query on the main UI thread. This leads to unresponsive apps and
should no longer be used.</p>
</li>
<li>
<p>As seen in the <code class="highlighter-rouge">Activity.java</code>
<a href="http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/1.5_r4/android/app/Activity.java#Activity.managedQuery%28android.net.Uri%2Cjava.lang.String%5B%5D%2Cjava.lang.String%2Cjava.lang.String%29">source code</a>,
the call to <code class="highlighter-rouge">managedQuery</code> begins management of the returned cursor with a call to
<code class="highlighter-rouge">startManagingCursor(cursor)</code>. Having the activity manage the cursor seems convenient at first, as we
no longer need to worry about deactivating/closing the cursor ourselves. However, this signals the activity to call
<code class="highlighter-rouge">requery()</code> on the cursor
<a href="http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/1.5_r4/android/app/Activity.java#3503">each time the activity returns from a stopped state</a>,
and therefore puts the UI thread at risk. This cost significantly outweighs the convenience of having the activity deactivate/close the cursor for us.</p>
</li>
<li>
<p>The <code class="highlighter-rouge">SimpleCursorAdapter</code> constructor is deprecated and should not be used. The
problem with this constructor is that it will have the <code class="highlighter-rouge">SimpleCursorAdapter</code> auto-requery
its data when changes are made. More specifically, the CursorAdapter will register a ContentObserver
that monitors the underlying data source for changes, calling <code class="highlighter-rouge">requery()</code> on its bound
cursor each time the data is modified. The
<a href="http://developer.android.com/reference/android/widget/SimpleCursorAdapter.html#SimpleCursorAdapter(android.content.Context, int, android.database.Cursor, java.lang.String[], int[], int)">standard constructor</a>
should be used instead (if you intend on loading the adapter’s data with a <code class="highlighter-rouge">CursorLoader</code>,
make sure you pass <code class="highlighter-rouge">0</code> as the last argument). Don’t worry if you couldn’t spot this one…
it’s a very subtle bug.</p>
</li>
</ol>
<p>With the first Android tablet about to be released, something had to be done to encourage UI-friendly development.
The larger, 7-10” Honeycomb tablets called for more complicated, interactive, multi-paned layouts. Further, the
introduction of the <code class="highlighter-rouge">Fragment</code> meant that applications were about to become more dynamic and event-driven.
A simple, single-threaded approach to loading data could no longer be encouraged. Thus, the <code class="highlighter-rouge">Loader</code> and
the <code class="highlighter-rouge">LoaderManager</code> were born.</p>
<h3 id="android-30-loaders-and-the-loadermanager">Android 3.0, Loaders, and the LoaderManager</h3>
<p>Prior to Honeycomb, it was difficult to manage cursors, synchronize correctly with the UI thread, and ensure
all queries occurred on a background thread. Android 3.0 introduced the <code class="highlighter-rouge">Loader</code> and <code class="highlighter-rouge">LoaderManager</code> classes
to help simplify the process. Both classes are available for use in the Android Support Library, which
supports all Android platforms back to Android 1.6.</p>
<p>The new <code class="highlighter-rouge">Loader</code> API is a huge step forward, and significantly improves the user experience. <code class="highlighter-rouge">Loader</code>s ensure
that all cursor operations are done asynchronously, thus eliminating the possibility of blocking the UI thread.
Further, when managed by the <code class="highlighter-rouge">LoaderManager</code>, <code class="highlighter-rouge">Loader</code>s retain their existing cursor data across the activity
instance (for example, when it is restarted due to a configuration change), thus saving the cursor from
unnecessary, potentially expensive re-queries. As an added bonus, <code class="highlighter-rouge">Loader</code>s are intelligent enough to monitor
the underlying data source for updates, re-querying automatically when the data is changed.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Since the introduction of <code class="highlighter-rouge">Loader</code>s in Honeycomb and Compatibility Library, Android applications have
changed for the better. Making use of the now deprecated <code class="highlighter-rouge">startManagingCursor</code> and <code class="highlighter-rouge">managedQuery</code>
methods are extremely discouraged; not only do they slow down your app, but they can potentially bring it to a
screeching halt. <code class="highlighter-rouge">Loader</code>s, on the other hand, significantly speed up the user experience by offloading
the work to a separate background thread.</p>
<p>In the next post (titled <a href="/2012/07/understanding-loadermanager.html">Understanding the LoaderManager</a>),
we will go more in-depth on how to fix these problems by completing the transition from “managed cursors” to
making use of <code class="highlighter-rouge">Loader</code>s and the <code class="highlighter-rouge">LoaderManager</code>.</p>
<p>Don’t forget to +1 this blog in the top right corner if you found this helpful!</p>
Content Providers & Content Resolvershttps://www.androiddesignpatterns.com/2012/06/content-resolvers-and-content-providers.html2012-06-25T00:00:00+00:002012-06-25T00:00:00+00:00<p>Content Providers and Content Resolvers are a common source of confusion for beginning
Android developers. Further, online tutorials and sample code are not sufficient in
describing how the two classes work together to provide access to the Android data
model. This post hopes to fill in this gap by explaining their place in the
<code class="highlighter-rouge">android.content</code> package. It concludes with a walk through the life of a
simple query to the Content Resolver.</p>
<p>Content Providers and Content Resolvers are a common source of confusion for beginning
Android developers. Further, online tutorials and sample code are not sufficient in
describing how the two classes work together to provide access to the Android data
model. This post hopes to fill in this gap by explaining their place in the
<code class="highlighter-rouge">android.content</code> package. It concludes with a walk through the life of a
simple query to the Content Resolver.</p>
<!--more-->
<h3 id="the-androidcontent-package">The <code class="highlighter-rouge">android.content</code> Package</h3>
<p>The <a href="http://developer.android.com/reference/android/content/package-summary.html"><code class="highlighter-rouge">android.content</code></a>
package contains classes for accessing and publishing data. The Android framework
enforces a <strong>robust</strong> and <strong>secure</strong> data sharing model. Applications are <em>not</em>
allowed direct access to other application’s internal data. Two classes in the
package help enforce this requirement: the <code class="highlighter-rouge">ContentResolver</code> and the <code class="highlighter-rouge">ContentProvider</code>.</p>
<h3 id="what-is-the-content-resolver">What is the Content Resolver?</h3>
<p>The Content Resolver is the single, global instance in your application that provides
access to your (and other applications’) content providers. The Content Resolver
behaves exactly as its name implies: it accepts requests from clients, and <em>resolves</em>
these requests by directing them to the content provider with a distinct authority.
To do this, the Content Resolver stores a mapping from authorities to Content Providers.
This design is important, as it allows a simple and secure means of accessing other
applications’ Content Providers.</p>
<p>The Content Resolver includes the CRUD (create, read, update, delete) methods corresponding
to the abstract methods (insert, query, update, delete) in the Content Provider class.
The Content Resolver does not know the implementation of the Content Providers it is
interacting with (nor does it need to know); each method is passed an URI that specifies
the Content Provider to interact with.</p>
<h3 id="what-is-a-content-provider">What is a Content Provider?</h3>
<p>Whereas the Content Resolver provides an abstraction from the application’s Content Providers,
Content Providers provide an abstraction from the underlying data source
(i.e. a SQLite database). They provide mechanisms for defining data security (i.e. by
enforcing read/write permissions) and offer a standard interface that connects data
in one process with code running in another process.</p>
<p>Content Providers provide an interface for publishing and consuming data, based around a
simple URI addressing model using the <code class="highlighter-rouge">content://</code> schema. They enable you to decouple
your application layers from the underlying data layers, making your application
data-source agnostic by abstracting the underlying data source.</p>
<h3 id="the-life-of-a-query">The Life of a Query</h3>
<p>So what exactly is the step-by-step process behind a simple query? As described above,
when you query data from your database via the content provider, you don’t communicate
with the provider directly. Instead, you use the Content Resolver object to communicate
with the provider. The specific sequence of events that occurs when a query is made
is given below:</p>
<ol>
<li>A call to <code class="highlighter-rouge">getContentResolver().query(Uri, String, String, String, String)</code> is made.
The call invokes the Content Resolver’s <code class="highlighter-rouge">query</code> method, <em>not</em> the <code class="highlighter-rouge">ContentProvider</code>’s.</li>
<li>When the <code class="highlighter-rouge">query</code> method is invoked, the Content Resolver parses the <code class="highlighter-rouge">uri</code> argument
and extracts its authority.</li>
<li>The Content Resolver directs the request to the content provider registered with the
(unique) authority. This is done by calling the Content Provider’s <code class="highlighter-rouge">query</code> method.</li>
<li>When the Content Provider’s <code class="highlighter-rouge">query</code> method is invoked, the query is performed and
a Cursor is returned (or an exception is thrown). The resulting behavior depends
entirely on the Content Provider’s implementation.</li>
</ol>
<h3 id="conclusion">Conclusion</h3>
<p>An integral part of the
<a href="http://developer.android.com/reference/android/content/package-summary.html"><code class="highlighter-rouge">android.content</code></a>
package, the <code class="highlighter-rouge">ContentResolver</code> and <code class="highlighter-rouge">ContentProvider</code> classes work together to
ensure secure access to other applications’ data. Understanding how the underlying
system works becomes second nature once you’ve written enough Android code, but I
hope that someone finds this explanation helpful some day.</p>
<p>Let me know if you have any questions about the process!</p>
Why Ice Cream Sandwich Crashes your Apphttps://www.androiddesignpatterns.com/2012/06/app-force-close-honeycomb-ics.html2012-06-18T00:00:00+00:002012-06-18T00:00:00+00:00<p>The following question has plagued StackOverflow ever since Ice Cream
Sandwich’s initial release:</p>
<p>The following question has plagued StackOverflow ever since Ice Cream
Sandwich’s initial release:</p>
<blockquote>
<p>My application works fine on devices running Android 2.x, but
force closes on devices running Honeycomb (3.x) and Ice Cream
Sandwich (4.x). Why does this occur?</p>
</blockquote>
<p>This is a great question; after all, newer versions of Android are
released with the expectation that old apps will remain compatible
with new devices. In my experience, there are a couple reasons why
this might occur. Most of the time, however, the reason is simple:
<em>you are performing a potentially expensive operation on the UI
thread</em>.</p>
<!--more-->
<h3 id="what-is-the-ui-thread">What is the UI Thread?</h3>
<p>The concept and importance of the application’s <strong>main UI thread</strong>
is something every Android developer should understand. Each time an
application is launched, the system creates a thread called “main”
for the application. The main thread (also known as the “UI thread”)
is in charge of dispatching events to the appropriate views/widgets
and thus is very important. It’s also the thread where your application
interacts with running components of your application’s UI. For instance,
if you touch a button on the screen, the UI thread dispatches the touch
event to the view, which then sets its pressed state and posts an invalidate
request to the event queue. The UI thread dequeues this request and then
tells the view to redraw itself.</p>
<p>This single-thread model can yield poor performance unless Android
applications are implemented properly. Specifically, if the UI thread
was in charge of running everything in your entire application,
performing long operations such as network access or database queries
on the UI thread would block the entire user interface. No event would
be able to be dispatched—including drawing and touchscreen
events—while the long operation is underway. From the user’s
perspective, the application will appear to be frozen.</p>
<p>In these situations, instant feedback is vital. Studies show that
<strong>0.1 seconds</strong> is about the limit for having the user feel that
the system is reacting instantaneously. Anything slower than this
limit will probably be considered as <strong>lag</strong>
(<a href="http://www.useit.com/papers/responsetime.html">Miller 1968; Card et al. 1991</a>).
While a fraction of a second might not seem harmful, even a tenth
of a second can be the difference between a good review and a bad
review on Google Play. Even worse, if the UI thread is blocked
for more than about five seconds, the user is presented with the
notorious “application not responding” (ANR) dialog and the app is
force closed.</p>
<h3 id="why-android-crashes-your-app">Why Android Crashes Your App</h3>
<p>The reason why your application crashes on Android versions 3.0 and above,
but works fine on Android 2.x is because <strong>Honeycomb and Ice Cream Sandwich
are much stricter about abuse against the UI Thread</strong>. For example, when
an Android device running HoneyComb or above detects a network access on
the UI thread, a <code class="highlighter-rouge">NetworkOnMainThreadException</code> will be thrown:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>E/AndroidRuntime(673): java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example/com.example.ExampleActivity}: android.os.NetworkOnMainThreadException
</code></pre></div></div>
<p>The explanation as to why this occurs is well documented on the Android
developer’s site:</p>
<blockquote>
<p>A <code class="highlighter-rouge">NetworkOnMainThreadException</code> is thrown when an application
attempts to perform a networking operation on its main thread. This is
only thrown for applications targeting the Honeycomb SDK or higher.
Applications targeting earlier SDK versions are allowed to do networking
on their main event loop threads, but it’s heavily discouraged.</p>
</blockquote>
<p>Some examples of other operations that ICS and Honeycomb <em>won’t</em> allow
you to perform on the UI thread are:</p>
<ul>
<li>Opening a <code class="highlighter-rouge">Socket</code> connection (i.e. <code class="highlighter-rouge">new Socket()</code>).</li>
<li>HTTP requests (i.e. <code class="highlighter-rouge">HTTPClient</code> and <code class="highlighter-rouge">HTTPUrlConnection</code>).</li>
<li>Attempting to connect to a remote MySQL database.</li>
<li>Downloading a file (i.e. <code class="highlighter-rouge">Downloader.downloadFile()</code>).</li>
</ul>
<p>If you are attempting to perform any of these operations on the UI thread, you
<em>must</em> wrap them in a worker thread. The easiest way to do this is to use
of an <code class="highlighter-rouge">AsyncTask</code>, which allows you to perform asynchronous work on
your user interface. An <code class="highlighter-rouge">AsyncTask</code> will perform the blocking operations
in a worker thread and will publish the results on the UI thread, without
requiring you to handle threads and/or handlers yourself.</p>
<h3 id="conclusion">Conclusion</h3>
<p>The reason why I decided to write about this topic is because I have seen it
come up on StackOverflow and other forums countless times. The majority of
the time the error stems from placing expensive operations directly on the UI
thread. To ensure you don’t disrupt the user experience, it is very important
to execute Socket connections, HTTP requests, file downloads, and other
long-term operations on a separate Thread. The easiest way to do this is
to wrap your operation in an AsyncTask, which launches a new thread and
allows you to perform asynchronous work on your user interface.</p>
<p>As always, let me know if this was helpful by +1-ing the post or leaving a
comment below! Feel free to ask questions too… I respond to them quickly. :)</p>
<h3 id="helpful-links">Helpful Links</h3>
<p>Here are some helpful links that might help you get started with <code class="highlighter-rouge">AsyncTask</code>s:</p>
<ul>
<li><a href="http://developer.android.com/reference/android/os/AsyncTask.html"><code class="highlighter-rouge">AsyncTask</code></a> documentation</li>
<li><a href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading For Performance</a></li>
</ul>
Ensuring Compatibility with a Utility Classhttps://www.androiddesignpatterns.com/2012/06/compatability-manager-utility-class.html2012-06-14T00:00:00+00:002012-06-14T00:00:00+00:00<p>This post introduces the concept of a <strong>utility class</strong> and gives a simple
example of how you can use one to tidy up your code. As Android projects grow in size, it
becomes increasingly important that your code remains organized and well-structured.
Providing a utility class for commonly called methods can help tremendously in reducing
the complexity of your project, allowing you to structure your code in a readable and
easily understandable way.</p>
<p>This post introduces the concept of a <strong>utility class</strong> and gives a simple
example of how you can use one to tidy up your code. As Android projects grow in size, it
becomes increasingly important that your code remains organized and well-structured.
Providing a utility class for commonly called methods can help tremendously in reducing
the complexity of your project, allowing you to structure your code in a readable and
easily understandable way.</p>
<!--more-->
<p>Here’s a simple example. Let’s say you are building an Android application that frequently
checks the device’s SDK version code, to ensure backward compatibility. You’ll need to
use the constants provided in the <code class="highlighter-rouge">android.os.Build.VERSION_CODES</code> class,
but these constants are long and can quickly clutter up your code. In this case,
it might be a good idea to create a <code class="highlighter-rouge">CompatabilityUtil</code> utility class.
A sample implementation is given below:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CompatibilityUtil</span> <span class="o">{</span>
<span class="cm">/** Get the current Android API level. */</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">getSdkVersion</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Build</span><span class="o">.</span><span class="na">VERSION</span><span class="o">.</span><span class="na">SDK_INT</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/** Determine if the device is running API level 8 or higher. */</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isFroyo</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">getSdkVersion</span><span class="o">()</span> <span class="o">>=</span> <span class="n">Build</span><span class="o">.</span><span class="na">VERSION_CODES</span><span class="o">.</span><span class="na">FROYO</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/** Determine if the device is running API level 11 or higher. */</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isHoneycomb</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">getSdkVersion</span><span class="o">()</span> <span class="o">>=</span> <span class="n">Build</span><span class="o">.</span><span class="na">VERSION_CODES</span><span class="o">.</span><span class="na">HONEYCOMB</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/**
* Determine if the device is a tablet (i.e. it has a large screen).
*
* @param context The calling context.
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isTablet</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">getResources</span><span class="o">().</span><span class="na">getConfiguration</span><span class="o">().</span><span class="na">screenLayout</span>
<span class="o">&</span> <span class="n">Configuration</span><span class="o">.</span><span class="na">SCREENLAYOUT_SIZE_MASK</span><span class="o">)</span>
<span class="o">>=</span> <span class="n">Configuration</span><span class="o">.</span><span class="na">SCREENLAYOUT_SIZE_LARGE</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/**
* Determine if the device is a HoneyComb tablet.
*
* @param context The calling context.
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isHoneycombTablet</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">isHoneycomb</span><span class="o">()</span> <span class="o">&&</span> <span class="n">isTablet</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
<span class="o">}</span>
<span class="cm">/** This class can't be instantiated. */</span>
<span class="kd">private</span> <span class="nf">CompatibilityUtil</span><span class="o">()</span> <span class="o">{</span> <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Developers often create a separate package called <code class="highlighter-rouge">[package name].util</code> for
frequently used utility classes. So for example, if your package name is <code class="highlighter-rouge">com.example.myapp</code>,
then a nice place to put your utility classes would be in a separate package called
<code class="highlighter-rouge">com.example.myapp.util</code>. However, remember that there’s no need to <em>over-organize</em>
your project. Creating a separate package might be a good idea for a larger project,
but is completely unnecessary if your project contains only 5-10 classes. I might
write a post about package/class organization in the future. For now, check out the
(very well-designed) <a href="http://code.google.com/p/iosched/source/browse/">Google I/O 2011</a>
app’s source code. You will learn a lot!</p>
Designing for Backwards Compatibilityhttps://www.androiddesignpatterns.com/2012/06/designing-for-backwards-compatibility.html2012-06-13T00:00:00+00:002012-06-13T00:00:00+00:00<blockquote>
<p>Note: please read this <a href="/2012/06/compatability-manager-utility-class.html">short post</a>
before continuing forward.</p>
</blockquote>
<blockquote>
<p>Note: please read this <a href="/2012/06/compatability-manager-utility-class.html">short post</a>
before continuing forward.</p>
</blockquote>
<p>A common issue in Android development is backwards compatibility. How can we add cool
new features from the most recent Android API while still ensuring that it runs
correctly on devices running older versions of Android? This post discusses the
problem by means of a simple example, and proposes a scalable, well-designed solution.</p>
<!--more-->
<h3 id="the-problem">The Problem</h3>
<p>Let’s say we are writing an application that reads and writes pictures to new albums
(i.e. folders) located on external storage, and that we want our application to support
all devices running Donut (Android 1.6, SDK version 4) and above. Upon consulting the
<a href="http://developer.android.com/guide/topics/data/data-storage.html#filesExternal">documentation</a>,
we realize there is a slight problem. With the introduction of Froyo (Android 2.2,
SDK version 8) came a somewhat radical change in how external storage was laid out
and represented on Android devices, as well as several new API methods (see
<a href="http://developer.android.com/reference/android/os/Environment.html">android.os.Environment</a>)
that allow us access to the public storage directories. To ensure backwards compatibility
all the way back to Donut, we must provide two separate implementations: one for older,
pre-Froyo devices, and another for devices running Froyo and above.</p>
<h3 id="setting-up-the-manifest">Setting up the Manifest</h3>
<p>Before we dive into the implementation, we will first update our <code class="highlighter-rouge">uses-sdk</code> tag in the Android
manifest. There are two attributes we must set,</p>
<ul>
<li>
<p><code class="highlighter-rouge">android:minSdkVersion="4"</code>. This attribute defines a minimum API level required for
the application to run. We want our application to run on devices running Donut and above,
so we set its value to <code class="highlighter-rouge">"4"</code>.</p>
</li>
<li>
<p><code class="highlighter-rouge">android:targetSdkVersion="15"</code>. This attribute is a little trickier to understand
(and is incorrectly defined on blogs all over the internet). This attribute specifies
the API level on which the application is designed to run. Preferably we would want
its value to correspond to the most recently released SDK (<code class="highlighter-rouge">"15"</code>, at the time of this
posting). Strictly speaking, however, its value should be given by the largest SDK
version number that we have tested your application against (we will assume we have
done so for the remainder of this example).</p>
</li>
</ul>
<p>The resulting tag in our manifest is as follows:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code><uses-sdk
android:minSdkVersion="4"
android:targetSdkVersion="15" >
</uses-sdk>
</code></pre></div></div>
<h3 id="implementation">Implementation</h3>
<p>Our implementation will consist of an abstract class and two subclasses that extend
it. The abstract <code class="highlighter-rouge">AlbumStorageDirFactory</code> class enforces a simple contract by
requiring its subclasses to implement the <code class="highlighter-rouge">getAlbumStorageDir</code> method. The actual
implementation of this method depends on the device’s SDK version number. Specifically,
if we are using a device running Froyo or above, its implementation will make use of
new methods introduced in API level 8. Otherwise, the correct directory must be
determined using pre-Froyo method calls, to ensure that our app remains backwards compatible.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AlbumStorageDirFactory</span> <span class="o">{</span>
<span class="cm">/**
* Returns a File object that points to the folder that will store
* the album's pictures.
*/</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="n">File</span> <span class="nf">getAlbumStorageDir</span><span class="o">(</span><span class="n">String</span> <span class="n">albumName</span><span class="o">);</span>
<span class="cm">/**
* A static factory method that returns a new AlbumStorageDirFactory
* instance based on the current device's SDK version.
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">AlbumStorageDirFactory</span> <span class="nf">newInstance</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// Note: the CompatibilityUtil class is implemented </span>
<span class="c1">// and discussed in a previous post, entitled </span>
<span class="c1">// "Ensuring Compatibility with a Utility Class".</span>
<span class="k">if</span> <span class="o">(</span><span class="n">CompatabilityUtil</span><span class="o">.</span><span class="na">isFroyo</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">FroyoAlbumDirFactory</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">BaseAlbumDirFactory</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The two subclasses and their implementation are given below.The class also provides
a static factory <code class="highlighter-rouge">newInstance</code> method (note that this method makes use of the
<code class="highlighter-rouge">CompatabilityUtil</code> utility class, which was both implemented and discussed in a
<a href="/2012/06/compatability-manager-utility-class.html">previous post</a>).
We discuss this method in detail in the next section.</p>
<p>The <code class="highlighter-rouge">BaseAlbumDirFactory</code> subclass handles pre-Froyo SDK versions:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">BaseAlbumDirFactory</span> <span class="kd">extends</span> <span class="n">AlbumStorageDirFactory</span> <span class="o">{</span>
<span class="cm">/**
* For pre-Froyo devices, we must provide the name of the photo directory
* ourselves. We choose "/dcim/" as it is the widely considered to be the
* standard storage location for digital camera files.
*/</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">CAMERA_DIR</span> <span class="o">=</span> <span class="s">"/dcim/"</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">File</span> <span class="nf">getAlbumStorageDir</span><span class="o">(</span><span class="n">String</span> <span class="n">albumName</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">File</span><span class="o">(</span><span class="n">Environment</span><span class="o">.</span><span class="na">getExternalStorageDirectory</span><span class="o">()</span>
<span class="o">+</span> <span class="n">CAMERA_DIR</span> <span class="o">+</span> <span class="n">albumName</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The <code class="highlighter-rouge">FroyoAlbumDirFactory</code> subclass handles Froyo and above:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">FroyoAlbumDirFactory</span> <span class="kd">extends</span> <span class="n">AlbumStorageDirFactory</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">File</span> <span class="nf">getAlbumStorageDir</span><span class="o">(</span><span class="n">String</span> <span class="n">albumName</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">File</span><span class="o">(</span><span class="n">Environment</span><span class="o">.</span><span class="na">getExternalStoragePublicDirectory</span><span class="o">(</span>
<span class="n">Environment</span><span class="o">.</span><span class="na">DIRECTORY_PICTURES</span><span class="o">),</span> <span class="n">albumName</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="making-sense-of-the-pattern">Making Sense of the Pattern</h3>
<p>Take a second to study the structure of the code above. Our implementation ensures
compatibility with pre-Froyo devices through a simple design. To ensure compatibility,
we simply request a new <code class="highlighter-rouge">AlbumStorageDirFactory</code> and call the abstract <code class="highlighter-rouge">getAlbumStorageDir</code>
method. The subclass is determined and instantiated at runtime depending on the Android
device’s SDK version number. See the sample activity below for an example on how an ordinary
Activity might use this pattern to retrieve an album’s directory.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SampleActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
<span class="kd">private</span> <span class="n">AlbumStorageDirFactory</span> <span class="n">mAlbumFactory</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="c1">// Instantiate the AlbumStorageDirFactory. Instead of</span>
<span class="c1">// invoking the subclass' default constructors directly,</span>
<span class="c1">// we make use of the Abstract Factory design pattern,</span>
<span class="c1">// which encapsulates the inner details. As a result, the</span>
<span class="c1">// Activity does not need to know `anything` about the</span>
<span class="c1">// compatibility-specific implementation--all of this is</span>
<span class="c1">// done behind the scenes within the "mAlbumFactory" object. </span>
<span class="n">mAlbumFactory</span> <span class="o">=</span> <span class="n">AlbumStorageDirFactory</span><span class="o">.</span><span class="na">newInstance</span><span class="o">();</span>
<span class="c1">// get the album's directory</span>
<span class="n">File</span> <span class="n">sampleAlbumDir</span> <span class="o">=</span> <span class="n">getAlbumDir</span><span class="o">(</span><span class="s">"sample_album"</span><span class="o">);</span>
<span class="o">}</span>
<span class="cm">/**
* A simple helper method that returns a File corresponding
* to the album named "albumName". The helper method invokes
* the abstract "getAlbumStorageDir" method, which will return
* correct location of the directory depending on the subclass
* that was returned in "newInstance" (which depends entirely
* on the device's SDK version number).
*/</span>
<span class="kd">private</span> <span class="n">File</span> <span class="nf">getAlbumDir</span><span class="o">(</span><span class="n">String</span> <span class="n">albumName</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">mAlbumFactory</span><span class="o">.</span><span class="na">getAlbumStorageDir</span><span class="o">(</span><span class="n">albumName</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>There are a couple benefits to organizing the code the way we have:</p>
<ul>
<li><strong>It’s easily extendable.</strong> While there is certainly no need to separate our
implementations into classes for simple examples (such as the one discussed above),
doing so is important when working with large, complicated projects, as it will ensure
changes can quickly be made down the line.</li>
<li><strong>It encapsulates the implementation-specific details.</strong> Abstracting these details
from the client makes our code less cluttered and easier to read (note: in this case,
“the client” was the person who wrote the Activity class).</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<p>Android developers constantly write code to ensure backwards compatibility. As projects
expand and applications become more complex, it becomes increasingly important to ensure
your implementation is properly designed. Hopefully this post helped and will encourage
you to more elegant solutions in the future!</p>
<p>Leave a comment if you have any questions or criticisms… or just to let me know that
you managed to read through this entire post!</p>
Basic Android Debugging with Logshttps://www.androiddesignpatterns.com/2012/05/intro-to-android-debug-logging.html2012-05-30T00:00:00+00:002012-05-30T00:00:00+00:00<p>As with most areas in software engineering, debugging is a crucial aspect
of Android development. Properly setting up your application for debugging
can save you hours of work and frustration. Unfortunately, in my experience
not many beginners learn how to properly make use of the utility classes
provided in the Android SDK. Unless you are an experienced developer, it
is my personal belief that Android debugging should follow a <em>pattern</em>.
This will prove beneficial for a couple reasons:</p>
<p>As with most areas in software engineering, debugging is a crucial aspect
of Android development. Properly setting up your application for debugging
can save you hours of work and frustration. Unfortunately, in my experience
not many beginners learn how to properly make use of the utility classes
provided in the Android SDK. Unless you are an experienced developer, it
is my personal belief that Android debugging should follow a <em>pattern</em>.
This will prove beneficial for a couple reasons:</p>
<!--more-->
<ul>
<li>
<p><strong>It allows you to anticipate bugs down the line.</strong> Setting up your development
work space for debugging will give you a head start on bugs you might encounter
in the future.</p>
</li>
<li>
<p><strong>It gives you centralized control over the debugging process.</strong> Disorganized and
sparse placement of log messages in your class can clutter your logcat output, making
it difficult to interpret debugging results. The ability to toggle certain groups
of log messages on/off can make your life a whole lot easier, especially if your
application is complex.</p>
</li>
</ul>
<h3 id="the-log-class">The <code class="highlighter-rouge">Log</code> Class</h3>
<p>For those of you who don’t know, the Android SDK includes a useful logging
utility class called <code class="highlighter-rouge">android.util.Log</code>. The class allows you to
log messages categorized based severity; each type of logging message has
its own message. Here is a listing of the message types, and their respective
method calls, ordered from lowest to highest priority:</p>
<ul>
<li>The <code class="highlighter-rouge">Log.v()</code> method is used to log verbose messages.</li>
<li>The <code class="highlighter-rouge">Log.d()</code> method is used to log debug messages.</li>
<li>The <code class="highlighter-rouge">Log.i()</code> method is used to log informational messages.</li>
<li>The <code class="highlighter-rouge">Log.w()</code> method is used to log warnings.</li>
<li>The <code class="highlighter-rouge">Log.e()</code> method is used to log errors.</li>
<li>The <code class="highlighter-rouge">Log.wtf()</code> method is used to log events that should never happen
(“wtf” being an abbreviation for “What a Terrible Failure”, of course).
You can think of this method as the equivalent of Java’s <code class="highlighter-rouge">assert</code> method.</li>
</ul>
<p>One should <em>always</em> consider a message’s type when assigning log messages to
one of the six method calls, as this will allow you to
<a href="http://developer.android.com/guide/developing/debugging/debugging-log.html#filteringOutput">filter your logcat output</a>
when appropriate. It is also important to understand when it is acceptable to
compile log messages into your application:</p>
<ul>
<li><strong>Verbose logs should never be compiled into an application except during development.</strong>
When development is complete and you are ready to release your application to the world,
you should remove all verbose method calls either by commenting them out, or using
ProGuard to remove any verbose log statements directly from the bytecode of your
compiled JAR executable, as described in Christopher’s answer in this
<a href="http://stackoverflow.com/q/2018263/844882">StackOverflow post</a>.</li>
<li><strong>Debug logs</strong> are compiled in but are ignored at runtime.</li>
<li><strong>Error</strong>, <strong>warning</strong>, and <strong>informational</strong> logs are always kept.</li>
</ul>
<h3 id="a-simple-pattern">A Simple Pattern</h3>
<p>A simple way to organize debugging is with the sample pattern implemented
below. A global, static string is given to represent the specific class
(an Activity in this example, but it could be a service, an adapter,
anything), and a boolean variable to represent whether or not log
messages should be printed to the logcat.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SampleActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
<span class="cm">/**
* A string constant to use in calls to the "log" methods. Its
* value is often given by the name of the class, as this will
* allow you to easily determine where log methods are coming
* from when you analyze your logcat output.
*/</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">TAG</span> <span class="o">=</span> <span class="s">"SampleActivity"</span><span class="o">;</span>
<span class="cm">/**
* Toggle this boolean constant's value to turn on/off logging
* within the class.
*/</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">VERBOSE</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">VERBOSE</span><span class="o">)</span> <span class="n">Log</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"+++ ON CREATE +++"</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onStart</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onStart</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">VERBOSE</span><span class="o">)</span> <span class="n">Log</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"++ ON START ++"</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onResume</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onResume</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">VERBOSE</span><span class="o">)</span> <span class="n">Log</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"+ ON RESUME +"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Don’t be afraid to be creative in how you print your log messages to the logcat!
For instance, when the sample activity above is launched, the resulting logcat
is presented in an nicely formatted and human-readable way:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>V SampleActivity +++ ON CREATE +++
V SampleActivity ++ ON START++
V SampleActivity + ON RESUME +
</code></pre></div></div>
<h3 id="conclusion">Conclusion</h3>
<p>In this post, I have covered the basics in which Android debugging can (and
should) be performed. In a future post, I will go into a bit more depth by
providing some more advanced patterns that will give you more control over
how debugging is performed at runtime.</p>
<p>Leave a comment if this helped… it’ll motivate me to write more of these
blog posts in the future! :)</p>
Reaping the Benefits of the LoaderManagerhttps://www.androiddesignpatterns.com/2012/05/why-you-should-use-loadermanager.html2012-05-26T00:00:00+00:002012-05-26T00:00:00+00:00<p>With Android 3.0 came the introduction of the <code class="highlighter-rouge">LoaderManager</code> class, an abstract
class associated with an <code class="highlighter-rouge">Activity</code> or <code class="highlighter-rouge">Fragment</code> for managing one or
more <code class="highlighter-rouge">Loader</code> instances. The <code class="highlighter-rouge">LoaderManager</code> class is one of my favorite
additions to the Android framework for a number of reasons, but mostly because it <em>significantly</em>
reduces code complexity and makes your application run a lot smoother. Implementing data loaders
with the <code class="highlighter-rouge">LoaderManager</code> is simple to implement, and takes care of everything about
lifecycle management so are much less error prone.</p>
<p>With Android 3.0 came the introduction of the <code class="highlighter-rouge">LoaderManager</code> class, an abstract
class associated with an <code class="highlighter-rouge">Activity</code> or <code class="highlighter-rouge">Fragment</code> for managing one or
more <code class="highlighter-rouge">Loader</code> instances. The <code class="highlighter-rouge">LoaderManager</code> class is one of my favorite
additions to the Android framework for a number of reasons, but mostly because it <em>significantly</em>
reduces code complexity and makes your application run a lot smoother. Implementing data loaders
with the <code class="highlighter-rouge">LoaderManager</code> is simple to implement, and takes care of everything about
lifecycle management so are much less error prone.</p>
<!--more-->
<p>While applications are free to write their own loaders for loading various types of data, the
most common (and simplest) use of the <code class="highlighter-rouge">LoaderManager</code> is with a <code class="highlighter-rouge">CursorLoader</code>.
When done correctly, the <code class="highlighter-rouge">CursorLoader</code> class offloads the work of loading data on a thread,
and keeps the data persistent during short term activity refresh events, such as an orientation change.
In addition to performing the initial query, the <code class="highlighter-rouge">CursorLoader</code> registers a
<code class="highlighter-rouge">ContentObserver</code> with the dataset you requested and calls <code class="highlighter-rouge">forceLoad()</code>
on itself when the data set changes, and is thus auto-updating. This is extremely convenient for
the programmer, as he doesn’t have to worry about performing these queries himself. Further,
for bigger screens it becomes more important that you query on a separate thread since configuration
changes involve recreating the entire view layout, a complex operation that can cause disasters
when blocked.</p>
<p>As mentioned earlier, one could implement his or her class to load data on a separate
thread using an <code class="highlighter-rouge">AsyncTask</code> or even a <code class="highlighter-rouge">Thread</code>.
The point, however, is that the <code class="highlighter-rouge">LoaderManager</code> does this all for you, so
it’s convenient for the developer, less error prone, and simple to implement. Of course
it is possible to use an <code class="highlighter-rouge">AsyncTask</code> to keep your application UI thread friendly,
but it will involve a lot more code, and implementing your class so that it will retain the
loaded <code class="highlighter-rouge">Cursor</code> over the twists and turns of the <code class="highlighter-rouge">Activity</code> lifecycle
won’t be simple. The bottom line is that <code class="highlighter-rouge">LoaderManager</code> will do this automatically
for you, as well as taking care of correctly creating and closing the <code class="highlighter-rouge">Cursor</code>
based on the <code class="highlighter-rouge">Activity</code> lifecycle.</p>
<p>To use <code class="highlighter-rouge">LoaderManager</code> with (or without) the <code class="highlighter-rouge">CursorLoader</code>
in an app targeting pre-Honeycomb devices, you should make use of the classes provided
in the Android Support Package, including the <code class="highlighter-rouge">FragmentActivity</code> class. A
<code class="highlighter-rouge">FragmentActivity</code> is just an <code class="highlighter-rouge">Activity</code> that has been created
for Android compatibility support, and does not require the use of fragments in your
application. When transitioning from an <code class="highlighter-rouge">Activity</code>s to <code class="highlighter-rouge">FragmentActivity</code>s,
be extremely careful that you use the <code class="highlighter-rouge">getSupportLoaderManager()</code> instead of
<code class="highlighter-rouge">getLoaderManager()</code>. <code class="highlighter-rouge">FragmentActivity</code> extends <code class="highlighter-rouge">Activity</code>,
thus inheriting all of its methods, and as a result the compiler will not complain if you
accidentally mix up these methods, so be very careful!</p>
<p>Leave a comment if you have any questions or criticisms... or just to let me know
that you managed to read through this entire post without getting distracted! I'm also
open to providing more explicit code samples if anyone asks.
</p>
Using newInstance() to Instantiate a Fragmenthttps://www.androiddesignpatterns.com/2012/05/using-newinstance-to-instantiate.html2012-05-24T00:00:00+00:002012-05-24T00:00:00+00:00<p>I recently came across an interesting question on StackOverflow regarding Fragment instantiation:</p>
<p>I recently came across an interesting question on StackOverflow regarding Fragment instantiation:</p>
<blockquote>
<p>What is the difference between <code class="highlighter-rouge">new MyFragment()</code> and <code class="highlighter-rouge">MyFragment.newInstance()</code>?
Should I prefer one over the other?</p>
</blockquote>
<p>Good question. The answer, as the title of this blog suggests, is a matter of proper design. In this
case, the <code class="highlighter-rouge">newInstance()</code> method is a “static factory method,” allowing us to initialize and setup a
new <code class="highlighter-rouge">Fragment</code> without having to call its constructor and additional setter methods. Providing static
factory methods for your fragments is good practice because it encapsulates and abstracts the steps
required to setup the object from the client. For example, consider the following code:</p>
<!--more-->
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyFragment</span> <span class="kd">extends</span> <span class="n">Fragment</span> <span class="o">{</span>
<span class="cm">/**
* Static factory method that takes an int parameter,
* initializes the fragment's arguments, and returns the
* new fragment to the client.
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">MyFragment</span> <span class="nf">newInstance</span><span class="o">(</span><span class="kt">int</span> <span class="n">index</span><span class="o">)</span> <span class="o">{</span>
<span class="n">MyFragment</span> <span class="n">f</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MyFragment</span><span class="o">();</span>
<span class="n">Bundle</span> <span class="n">args</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Bundle</span><span class="o">();</span>
<span class="n">args</span><span class="o">.</span><span class="na">putInt</span><span class="o">(</span><span class="s">"index"</span><span class="o">,</span> <span class="n">index</span><span class="o">);</span>
<span class="n">f</span><span class="o">.</span><span class="na">setArguments</span><span class="o">(</span><span class="n">args</span><span class="o">);</span>
<span class="k">return</span> <span class="n">f</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Rather than having the client call the default constructor and manually set the fragment’s arguments
themselves, we provide a static factory method that does this for them. This is preferred over the
default constructor for two reasons. One, it’s convenient for the client, and two, it enforces well-defined
behavior. By providing a static factory method, we protect ourselves from bugs down the line—we no
longer need to worry about accidentally forgetting to initialize the fragment’s arguments or incorrectly doing so.</p>
<p>Overall, while the difference between the two is mostly just a matter of design, this difference is really
important because it provides another level of abstraction and makes code a lot easier to understand.</p>
<p>Feel free to leave a comment if this blog post helped (it will motivate me to write more in the future)! :)</p>
Correctly Managing your SQLite Databasehttps://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html2012-05-21T00:00:00+00:002012-05-21T00:00:00+00:00<p>One thing that I’ve noticed other Android developers having trouble with is properly
setting up their <code class="highlighter-rouge">SQLiteDatabase</code>. Often times, I come across questions on StackOverflow
asking about error messages such as,</p>
<p>One thing that I’ve noticed other Android developers having trouble with is properly
setting up their <code class="highlighter-rouge">SQLiteDatabase</code>. Often times, I come across questions on StackOverflow
asking about error messages such as,</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>E/Database(234): Leak found
E/Database(234): Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
</code></pre></div></div>
<p>As you have probably figured out, this exception is thrown when you have opened more
<code class="highlighter-rouge">SQLiteDatabase</code> instances than you have closed. Managing the database can be complicated
when first starting out with Android development, especially to those who are just beginning
to understand the <code class="highlighter-rouge">Activity</code> lifecycle. The easiest solution is to make your database
instance a singleton instance across the entire application’s lifecycle. This will ensure
that no leaks occur, and will make your life a lot easier since it eliminates the
possibility of forgetting to close your database as you code.</p>
<!--more-->
<p>Here are two examples that illustrates three possible approaches in managing your
singleton database. These will ensure safe access to the database throughout the application.</p>
<h3 id="approach-1-use-a-singleton-to-instantiate-the-sqliteopenhelper">Approach #1: Use a Singleton to Instantiate the <code class="highlighter-rouge">SQLiteOpenHelper</code></h3>
<p>Declare your database helper as a static instance variable and use the Singleton
pattern to guarantee the singleton property. The sample code below should give you a good
idea on how to go about designing the <code class="highlighter-rouge">DatabaseHelper</code> class correctly.</p>
<p>The static <code class="highlighter-rouge">getInstance()</code> method ensures that only one <code class="highlighter-rouge">DatabaseHelper</code>
will ever exist at any given time. If the <code class="highlighter-rouge">sInstance</code> object has not been initialized,
one will be created. If one has already been created then it will simply be returned.
<strong>You should not initialize your helper object using with <code class="highlighter-rouge">new DatabaseHelper(context)</code>!</strong>
Instead, always use <code class="highlighter-rouge">DatabaseHelper.getInstance(context)</code>, as it guarantees that only one
database helper will exist across the entire application’s lifecycle.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DatabaseHelper</span> <span class="kd">extends</span> <span class="n">SQLiteOpenHelper</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="n">DatabaseHelper</span> <span class="n">sInstance</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">DATABASE_NAME</span> <span class="o">=</span> <span class="s">"database_name"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">DATABASE_TABLE</span> <span class="o">=</span> <span class="s">"table_name"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">DATABASE_VERSION</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">synchronized</span> <span class="n">DatabaseHelper</span> <span class="nf">getInstance</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Use the application context, which will ensure that you </span>
<span class="c1">// don't accidentally leak an Activity's context.</span>
<span class="c1">// See this article for more information: http://bit.ly/6LRzfx</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sInstance</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">sInstance</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DatabaseHelper</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">getApplicationContext</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">sInstance</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/**
* Constructor should be private to prevent direct instantiation.
* make call to static method "getInstance()" instead.
*/</span>
<span class="kd">private</span> <span class="nf">DatabaseHelper</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">DATABASE_NAME</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">DATABASE_VERSION</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="approach-2-wrap-the-sqlitedatabase-in-a-contentprovider">Approach #2: Wrap the <code class="highlighter-rouge">SQLiteDatabase</code> in a <code class="highlighter-rouge">ContentProvider</code></h3>
<p>This is also a nice approach. For one, the new <code class="highlighter-rouge">CursorLoader</code> class requires
<code class="highlighter-rouge">ContentProvider</code>s, so if you want an Activity or Fragment to implement <code class="highlighter-rouge">LoaderManager.LoaderCallbacks<Cursor></code>
with a <code class="highlighter-rouge">CursorLoader</code> (as discussed in <a href="/2012/07/understanding-loadermanager.html">this post</a>),
you’ll need to implement a <code class="highlighter-rouge">ContentProvider</code> for your application. Further, you don’t need to worry
about making a singleton database helper with <code class="highlighter-rouge">ContentProvider</code>s. Simply call <code class="highlighter-rouge">getContentResolver()</code>
from the Activity and the system will take care of everything for you (in other words, there is no
need for designing a Singleton pattern to prevent multiple instances from being created).</p>
<p>Leave a comment if this helped or if you have any questions!</p>