Jekyll2022-09-20T18:56:33+00:00https://www.arunkumar.dev/feed.xmlArunkumar SampathkumarArun's blog and portfolio{"twitter"=>"arunkumar_9t2"}Introducing Compass: Effective Paging with Realm and Jetpack Paging 32021-10-08T19:59:00+00:002021-10-08T19:59:00+00:00https://www.arunkumar.dev/introducing-compass-effective-paging-with-realm-and-jetpack-paging-3<p>I like <a href="https://realm.io">Realm</a> mobile database. I first started using <code class="language-plaintext highlighter-rouge">Realm</code> at a time when there were limited options for a reactive database - a feature common today with tools like <a href="https://developer.android.com/jetpack/androidx/releases/room">Room</a> and <a href="https://github.com/cashapp/sqldelight/">SqlDelight</a> (remember <a href="https://github.com/square/sqlbrite">SqlBrite</a>?). With reactivity, <code class="language-plaintext highlighter-rouge">Realm</code> was pushing for <a href="https://docs.mongodb.com/realm/sdk/android/quick-start-local/#complete-example">persistence as single source of truth</a> much earlier than the pattern caught on if I recall correctly. <code class="language-plaintext highlighter-rouge">Realm</code>’s reactivity is <a href="https://docs.mongodb.com/realm/sdk/android/examples/react-to-changes/#register-a-collection-change-listener">fine grained</a> as well i.e can emit added, removed or modified changes on a collection without tools like <a href="https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil">DiffUtil</a> and with correct usage it can integrate directly with <a href="https://developer.android.com/guide/topics/ui/layout/recyclerview">RecyclerView</a>. Apart from being reactive, it is an <a href="https://docs.mongodb.com/realm/sdk/android/examples/define-a-realm-object-model/">object oriented database</a>, so relations can be directly expressed as Java/Kotlin objects and has a capable <a href="https://docs.mongodb.com/realm/sdk/android/fundamentals/query-engine/">query</a> system with support for aggregations and backlinks.</p>
<p>However not all of these were smooth sailing in my opinion, particularly <strong>paging, lifecycle and threading</strong>. <code class="language-plaintext highlighter-rouge">Realm</code>’s live object model i.e at any point in time, we interact with the live view of the underlying database and objects are brought into memory only when they are read like <code class="language-plaintext highlighter-rouge">Object.getPropery()</code> (lazy by default). For paging, <code class="language-plaintext highlighter-rouge">Realm</code> initially recommended that it can be fast enough to read objects inside a <code class="language-plaintext highlighter-rouge">RecyclerView.Adapter.getItem()</code> call (this pattern is no longer recommended). I disagree with it since storage performance is highly variable (a background Play Store update can bring storage performance to its knees) and chances of ANR are high.</p>
<p><a href="https://docs.mongodb.com/realm/sdk/android/advanced-guides/threading/">Threading</a> is not straight forward and had few rules that need to be followed.</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">Realm</code> instances and the results <code class="language-plaintext highlighter-rouge">RealmResults</code> should not cross thread boundaries and each <code class="language-plaintext highlighter-rouge">Realm</code> instance carried a lifecycle with it should call <code class="language-plaintext highlighter-rouge">close()</code> when done.</li>
<li><code class="language-plaintext highlighter-rouge">Realm</code> objects can be observed only on a thread that has Android’s <a href="https://developer.android.com/reference/android/os/Looper">Looper</a> <a href="https://developer.android.com/reference/android/os/Looper#prepare()">prepared</a> on them.</li>
</ul>
<p>Because of these rules, writing <code class="language-plaintext highlighter-rouge">Realm</code> database code carried a bit of ceremony around it especially considering Android’s already extensive lifecyle expectations.</p>
<h3 id="motivation">Motivation</h3>
<p>Having discovered couple of patterns that can make working with <code class="language-plaintext highlighter-rouge">Realm</code> easier especially around threading, lifecycle and paging, I took the opportunity to use Kotlin’s capable type system to provide safe defaults with limited options for developer error when using <code class="language-plaintext highlighter-rouge">Realm</code>. In the remainder of the article, I would like to talk about paging in particular with Jetpack Paging 3 and how to integrate <code class="language-plaintext highlighter-rouge">Realm</code> with it by using <a href="https://github.com/arunkumar9t2/compass">Compass</a>.</p>
<p><code class="language-plaintext highlighter-rouge">compass</code> is available on <code class="language-plaintext highlighter-rouge">mavenCentral</code>:</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">dependencies</span> <span class="o">{</span>
<span class="n">implementation</span> <span class="s2">"dev.arunkumar.compass:compass:1.0.0"</span>
<span class="c1">// Paging integration</span>
<span class="n">implementation</span> <span class="s2">"dev.arunkumar.compass:compass-paging:1.0.0"</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="compass">Compass</h3>
<p><code class="language-plaintext highlighter-rouge">Compass</code> has two main components that provide the core <code class="language-plaintext highlighter-rouge">compass</code> module with extensions for threading and lifecycle and <code class="language-plaintext highlighter-rouge">compass-paging</code> for integration with <a href="https://developer.android.com/topic/libraries/architecture/paging/v3-overview">Jetpack Paging 3</a>. To observe a <code class="language-plaintext highlighter-rouge">RealmQuery</code> with <code class="language-plaintext highlighter-rouge">compass</code> we can use the <code class="language-plaintext highlighter-rouge">asPagingItems</code> extension function as shown below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">pagingFlow</span><span class="p">:</span> <span class="nc">Flow</span><span class="p"><</span><span class="nc">PagingData</span><span class="p"><</span><span class="nc">Person</span><span class="p">>></span> <span class="p">=</span> <span class="nc">RealmQuery</span> <span class="p">{</span> <span class="k">where</span><span class="p"><</span><span class="nc">Person</span><span class="p">>().</span><span class="nf">sort</span><span class="p">(</span><span class="nc">Person</span><span class="p">.</span><span class="nc">NAME</span><span class="p">)</span> <span class="p">}</span>
<span class="p">.</span><span class="nf">asPagingItems</span><span class="p">()</span>
</code></pre></div></div>
<p>Since <code class="language-plaintext highlighter-rouge">asPagingItems()</code> returns <code class="language-plaintext highlighter-rouge">Flow<PagingData<T>></code> it integrates well with</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">Paging 3</code> <a href="https://developer.android.com/topic/libraries/architecture/paging/v3-paged-data">stream of data</a> support, particularly <code class="language-plaintext highlighter-rouge">cachedIn(coroutineScope)</code>.</li>
<li><a href="https://developer.android.com/jetpack/compose">Jetpack Compose</a>’s <code class="language-plaintext highlighter-rouge">LazyColumn</code> support via <a href="https://developer.android.com/reference/kotlin/androidx/paging/compose/package-summary#(kotlinx.coroutines.flow.Flow).collectAsLazyPagingItems()">collectAsLazyPagingItems()</a>.</li>
</ul>
<p>The extension function internally manages threading, lifecycle of <code class="language-plaintext highlighter-rouge">Realm</code>s and confirms to typical expectations of a <code class="language-plaintext highlighter-rouge">Flow<T></code>:</p>
<ol>
<li>Returned objects can be safely passed around threads.</li>
<li>Automatic closing of any internal resources when <code class="language-plaintext highlighter-rouge">Flow</code> collection is stopped.</li>
</ol>
<p>Let’s unpack one by one on how <code class="language-plaintext highlighter-rouge">compass</code> achieves this.</p>
<h3 id="implementation">Implementation</h3>
<h4 id="queries">Queries</h4>
<p>Consider a typical <code class="language-plaintext highlighter-rouge">Realm</code> code with the following</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">realm</span> <span class="p">=</span> <span class="nc">Realm</span><span class="p">.</span><span class="nf">getDefaultInstance</span><span class="p">()</span> <span class="c1">// 1</span>
<span class="kd">val</span> <span class="py">persons</span> <span class="p">=</span> <span class="n">realm</span><span class="p">.</span><span class="k">where</span><span class="p"><</span><span class="nc">Person</span><span class="p">>().</span><span class="nf">findAll</span><span class="p">()</span> <span class="c1">// 2</span>
<span class="n">persons</span><span class="p">.</span><span class="nf">addChangeListener</span><span class="p">{</span> <span class="p">}</span> <span class="c1">// 3</span>
<span class="n">realm</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span> <span class="c1">// 4</span>
</code></pre></div></div>
<p>This looks straight-forward but as soon as threading is introduced, couple of challenges arise. Namely, <code class="language-plaintext highlighter-rouge">1</code>,<code class="language-plaintext highlighter-rouge">2</code>,<code class="language-plaintext highlighter-rouge">3</code>,<code class="language-plaintext highlighter-rouge">4</code> should be on the same thread. Since <code class="language-plaintext highlighter-rouge">2</code> is a database operation, our instinct should be to move this entire operation to background thread, and we would be correct. However if we want to observe any changes in <code class="language-plaintext highlighter-rouge">3</code> it would crash since the background thread most likely would not have <code class="language-plaintext highlighter-rouge">Looper</code> by default. The official solution to this problem is to explicitly use <code class="language-plaintext highlighter-rouge">*Async</code> APIs like <code class="language-plaintext highlighter-rouge">findAllAsync()</code> etc. This works for most cases but still has room for developer errors due to lack of checks especially in changes observation due to ties to Android Looper.</p>
<h5 id="deferred-execution">Deferred Execution</h5>
<p>This problem can be solved by moving the construction of the query and observation both to usage site (background thread). Compass provides <code class="language-plaintext highlighter-rouge">RealmQuery {}</code> construction function that can be used to construct <code class="language-plaintext highlighter-rouge">RealmQueryBuilder<T></code> which is a <code class="language-plaintext highlighter-rouge">typealias</code> for <code class="language-plaintext highlighter-rouge">Realm.() -> RealmQuery<T></code>. The design is inspired by lazy APIs like Kotlin sequences/flows where construction happens lazily and only invoked when certain terminal operators are called.</p>
<p>In this case, the terminal operator can enforce threading rules as needed without any ceremony needed inside the construction function. For example:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">val</span> <span class="py">personQuery</span> <span class="p">=</span> <span class="nc">RealmQuery</span> <span class="p">{</span> <span class="k">where</span><span class="p"><</span><span class="nc">Person</span><span class="p">>()</span> <span class="p">}</span>
<span class="n">personQuery</span><span class="p">.</span><span class="nf">getAll</span><span class="p">()</span> <span class="c1">// Acquire an instance of `Realm`, run the query, return results and close Realm.</span>
<span class="n">personQuery</span><span class="p">.</span><span class="nf">asFlow</span><span class="p">()</span> <span class="c1">// Observable variant</span>
</code></pre></div></div>
<h4 id="realmexecutorrealmdispatcher">RealmExecutor/RealmDispatcher</h4>
<p><code class="language-plaintext highlighter-rouge">compass</code> provides dedicated constructs like <a href="https://arunkumar9t2.github.io/compass/compass/dev.arunkumar.compass.thread/-realm-dispatcher/index.html">RealmDispatcher</a> that automatically handles basic threading expectations of <code class="language-plaintext highlighter-rouge">Realm</code>. The requirement is to ensure <code class="language-plaintext highlighter-rouge">Looper</code> is available and it is stopped when work is done. This is done via Android OS’s <a href="https://developer.android.com/reference/android/os/HandlerThread">HandlerThread</a> which is the official way to manage the <code class="language-plaintext highlighter-rouge">Looper</code> correctly. Converting <code class="language-plaintext highlighter-rouge">HandlerThread</code> to a dedicated <code class="language-plaintext highlighter-rouge">Executor</code> instance will allow us to use variety of extensions that Kotlin offers like <code class="language-plaintext highlighter-rouge">asCoroutineDispatcher()</code>. <a href="https://arunkumar9t2.github.io/compass/compass/dev.arunkumar.compass.thread/-handler-executor/index.html">HandlerExecutor</a>:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="kd">class</span> <span class="nc">HandlerExecutor</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">tag</span><span class="p">:</span> <span class="nc">String</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span> <span class="p">:</span> <span class="nc">CloseableExecutor</span> <span class="p">{</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">handlerThread</span> <span class="k">by</span> <span class="nf">lazy</span> <span class="p">{</span>
<span class="nc">HandlerThread</span><span class="p">(</span>
<span class="n">tag</span> <span class="o">?:</span> <span class="k">this</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">.</span><span class="n">simpleName</span> <span class="p">+</span> <span class="nf">hashCode</span><span class="p">(),</span>
<span class="nc">Process</span><span class="p">.</span><span class="nc">THREAD_PRIORITY_BACKGROUND</span>
<span class="p">).</span><span class="nf">apply</span> <span class="p">{</span> <span class="nf">start</span><span class="p">()</span> <span class="p">}</span>
<span class="p">}</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">handler</span> <span class="k">by</span> <span class="nf">lazy</span> <span class="p">{</span> <span class="nc">Handler</span><span class="p">(</span><span class="n">handlerThread</span><span class="p">.</span><span class="n">looper</span><span class="p">)</span> <span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">execute</span><span class="p">(</span><span class="n">command</span><span class="p">:</span> <span class="nc">Runnable</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nc">Looper</span><span class="p">.</span><span class="nf">myLooper</span><span class="p">()</span> <span class="p">==</span> <span class="n">handler</span><span class="p">.</span><span class="n">looper</span><span class="p">)</span> <span class="p">{</span>
<span class="n">command</span><span class="p">.</span><span class="nf">run</span><span class="p">()</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">handler</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="n">command</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">close</span><span class="p">()</span> <span class="p">{</span>
<span class="n">handlerThread</span><span class="p">.</span><span class="nf">quitSafely</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Using <code class="language-plaintext highlighter-rouge">RealmDispatcher</code>, the following is valid:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">withContext</span><span class="p">(</span><span class="nc">RealmDispatcher</span><span class="p">())</span> <span class="p">{</span>
<span class="nc">Realm</span> <span class="p">{</span> <span class="n">realm</span> <span class="p">-></span> <span class="c1">// Acquire default Realm with `Realm {}`</span>
<span class="kd">val</span> <span class="py">persons</span> <span class="p">=</span> <span class="n">realm</span><span class="p">.</span><span class="k">where</span><span class="p"><</span><span class="nc">Person</span><span class="p">>().</span><span class="nf">findAll</span><span class="p">()</span>
<span class="kd">val</span> <span class="py">realmChangeListener</span> <span class="p">=</span> <span class="nc">RealmChangeListener</span><span class="p"><</span><span class="nc">RealmResults</span><span class="p"><</span><span class="nc">Person</span><span class="p">>></span> <span class="p">{</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"Change liseneter called"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">persons</span><span class="p">.</span><span class="nf">addChangeListener</span><span class="p">(</span><span class="n">realmChangeListener</span><span class="p">)</span> <span class="c1">// Safe to add </span>
<span class="c1">// Make a transaction</span>
<span class="n">realm</span><span class="p">.</span><span class="nf">transact</span> <span class="p">{</span> <span class="c1">// this: Realm</span>
<span class="nf">copyToRealm</span><span class="p">(</span><span class="nc">Person</span><span class="p">())</span>
<span class="p">}</span>
<span class="nf">delay</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span> <span class="c1">// Wait till change listener is triggered</span>
<span class="p">}</span> <span class="c1">// Acquired Realm automatically closed</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Note that <code class="language-plaintext highlighter-rouge">RealmDispatcher</code> should be closed when no longer used to release resources. For automatic lifecycle handling via <a href="https://kotlinlang.org/docs/flow.html">Flow</a>, see below.</p>
<h4 id="streams-via-flow">Streams via Flow</h4>
<p><code class="language-plaintext highlighter-rouge">compass</code> builds on top of the lazy query API to provide observable functions like <code class="language-plaintext highlighter-rouge">asFlow()</code> which confirms to basic threading expectations of a <code class="language-plaintext highlighter-rouge">Flow</code>.</p>
<ul>
<li>Returned objects can be passed to different threads.</li>
<li>Handles <code class="language-plaintext highlighter-rouge">Realm</code> lifecycle until <code class="language-plaintext highlighter-rouge">Flow</code> collection is stopped.</li>
</ul>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">personsFlow</span> <span class="p">=</span> <span class="nc">RealmQuery</span> <span class="p">{</span> <span class="k">where</span><span class="p"><</span><span class="nc">Person</span><span class="p">>()</span> <span class="p">}.</span><span class="nf">asFlow</span><span class="p">()</span>
</code></pre></div></div>
<p>Internally <code class="language-plaintext highlighter-rouge">asFlow</code> creates a dedicated <code class="language-plaintext highlighter-rouge">RealmDispatcher</code> to run the queries and observe changes. The created dispatcher is automatically closed and recreated when collection stops/restarted all established through use of <code class="language-plaintext highlighter-rouge">callbackFlow</code> and <code class="language-plaintext highlighter-rouge">awaitClose</code>.</p>
<p>For ensuring <code class="language-plaintext highlighter-rouge">Realm</code> objects can be passed around threads, <code class="language-plaintext highlighter-rouge">compass</code> copies the object to memory using <code class="language-plaintext highlighter-rouge">Realm.copyFromRealm()</code>. Copying objects detaches it from <code class="language-plaintext highlighter-rouge">realm</code> and is no longer a live updating object.</p>
<h5 id="reading-subset-of-data">Reading subset of data</h5>
<p>Copying large objects from <code class="language-plaintext highlighter-rouge">Realm</code> can be expensive in terms of memory, to read only subset of results to memory we can use <code class="language-plaintext highlighter-rouge">asFlow()</code> overload that takes a <a href="https://arunkumar9t2.github.io/compass/compass/dev.arunkumar.compass/index.html#-1272439111/Classlikes/1670650899">transform</a> function.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">data class</span> <span class="nc">PersonName</span><span class="p">(</span><span class="kd">val</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">personNames</span> <span class="p">=</span> <span class="nc">RealmQuery</span> <span class="p">{</span> <span class="k">where</span><span class="p"><</span><span class="nc">Person</span><span class="p">>()</span> <span class="p">}.</span><span class="nf">asFlow</span> <span class="p">{</span> <span class="nc">PersonName</span><span class="p">(</span><span class="n">it</span><span class="p">.</span><span class="n">name</span><span class="p">)</span> <span class="p">}</span>
</code></pre></div></div>
<p>In the above example, since <code class="language-plaintext highlighter-rouge">Realm</code> is lazy by default, only <code class="language-plaintext highlighter-rouge">name</code> is brought into memory from eash <code class="language-plaintext highlighter-rouge">Person</code> instance. Conceptually in SQL terms, this is like querying only one column from a table.</p>
<h4 id="paging">Paging</h4>
<p>So far we have explored how <code class="language-plaintext highlighter-rouge">compass</code> handles common patterns around threading and lifecycle. All those concepts come together in <code class="language-plaintext highlighter-rouge">compass</code>’s paging integration.</p>
<h5 id="realmresults-is-like-a-live-cursor">RealmResults is like a live cursor</h5>
<p><code class="language-plaintext highlighter-rouge">compass</code> takes advantage of <code class="language-plaintext highlighter-rouge">Realm</code>s unique lazy loading feature to implement paging. Jetpack Paging 1 and 2 provided APIs to implement different types of paging <a href="https://developer.android.com/topic/libraries/architecture/paging/data#choose-data-source-type">sources</a>, this can be key based, position based and even dependant key based. Realm’s paging support can be implemented in variety of ways, <code class="language-plaintext highlighter-rouge">compass</code> implements it using <code class="language-plaintext highlighter-rouge">PositionalDataSource</code>. It works under the premise that <code class="language-plaintext highlighter-rouge">RealmResults<T></code> returned by <code class="language-plaintext highlighter-rouge">RealmQuery</code> is a live cursor. For example,</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">persons</span> <span class="p">=</span> <span class="n">realm</span><span class="p">.</span><span class="k">where</span><span class="p"><</span><span class="nc">Person</span><span class="p">>().</span><span class="nf">findAll</span><span class="p">()</span>
</code></pre></div></div>
<p>Here <code class="language-plaintext highlighter-rouge">persons</code> can contain 1 million entries but not all are brought into memory, they are read only when <code class="language-plaintext highlighter-rouge">persons.get(index)</code> is called, if one were to bind this to a <code class="language-plaintext highlighter-rouge">RecyclerView</code> only items in the view port would be read (nifty!). This feature alone would satisfy <a href="https://developer.android.com/reference/android/arch/paging/PositionalDataSource">PositionalDataSource</a> expectations that the implementation should be able to read data at any arbitary position.</p>
<p>Since paging can involve complicated threading, <code class="language-plaintext highlighter-rouge">compass</code>’s default implementation detaches the object from <code class="language-plaintext highlighter-rouge">Realm</code> using <code class="language-plaintext highlighter-rouge">Realm.copyFromRealm</code>, this makes it easy to apply any further <a href="https://developer.android.com/topic/libraries/architecture/paging/v3-transform">transformations</a> as needed without concern on threading.</p>
<p>As mentioned earlier, <code class="language-plaintext highlighter-rouge">copyFromRealm</code> is expensive for large nested objects and it is advised to take advantage of <code class="language-plaintext highlighter-rouge">Realm</code>’s lazy loading to bring only needed data to memory, this is done via <a href="https://arunkumar9t2.github.io/compass/compass/dev.arunkumar.compass/index.html#-1272439111/Classlikes/1670650899">RealmModelTransform</a> API.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">typealias</span> <span class="nc">RealmModelTransform</span><span class="p"><</span><span class="nc">T</span><span class="p">,</span> <span class="nc">R</span><span class="p">></span> <span class="p">=</span> <span class="nc">Realm</span><span class="p">.(</span><span class="n">realmModel</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">-></span> <span class="nc">R</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">compass</code> provides <code class="language-plaintext highlighter-rouge">ReamlCopyTransform</code> which does simply copying as shown below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">fun</span> <span class="p"><</span><span class="nc">T</span> <span class="p">:</span> <span class="nc">RealmModel</span><span class="p">,</span> <span class="nc">R</span><span class="p">></span> <span class="nf">RealmCopyTransform</span><span class="p">():</span> <span class="nc">RealmModelTransform</span><span class="p"><</span><span class="nc">T</span><span class="p">,</span> <span class="nc">R</span><span class="p">></span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span> <span class="n">model</span> <span class="p">-></span> <span class="nf">copyFromRealm</span><span class="p">(</span><span class="n">model</span><span class="p">)</span> <span class="k">as</span> <span class="nc">R</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>For customized reading of data, we can use the <code class="language-plaintext highlighter-rouge">asPagingItems</code> overload which takes a <code class="language-plaintext highlighter-rouge">transform</code> function.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">pagedPersonNames</span> <span class="p">=</span> <span class="nc">RealmQuery</span> <span class="p">{</span> <span class="k">where</span><span class="p"><</span><span class="nc">Person</span><span class="p">>()</span> <span class="p">}.</span><span class="nf">asPagingItems</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">name</span> <span class="p">}</span>
</code></pre></div></div>
<h5 id="observability-and-threading">Observability and threading</h5>
<p>For threading, <code class="language-plaintext highlighter-rouge">compass</code> uses <code class="language-plaintext highlighter-rouge">RealmDispatcher</code> to run queries and manage observability in a predictable way. Paging 3 expects that whenever there is data change, the corresponding <code class="language-plaintext highlighter-rouge">DataSource</code> implementation should <a href="https://developer.android.com/topic/libraries/architecture/paging/data#notify-data-invalid">invalidate()</a> itself and new instance should be created. With <code class="language-plaintext highlighter-rouge">Realm</code> this can be easily accomplished by using change listeners:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// this: RealmTiledDataSource</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">realmChangeListener</span> <span class="p">=</span> <span class="p">{</span> <span class="n">_</span><span class="p">:</span> <span class="nc">RealmResults</span><span class="p"><</span><span class="nc">T</span><span class="p">></span> <span class="p">-></span> <span class="nf">invalidate</span><span class="p">()</span> <span class="p">}</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">realmResults</span> <span class="k">by</span> <span class="nf">lazy</span> <span class="p">{</span>
<span class="n">realmQuery</span><span class="p">.</span><span class="nf">findAll</span><span class="p">().</span><span class="nf">apply</span> <span class="p">{</span>
<span class="nf">addChangeListener</span><span class="p">(</span><span class="n">realmChangeListener</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nf">init</span> <span class="p">{</span>
<span class="nf">addInvalidatedCallback</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">realmResults</span><span class="p">.</span><span class="n">isValid</span><span class="p">)</span> <span class="p">{</span>
<span class="n">realmResults</span><span class="p">.</span><span class="nf">removeChangeListener</span><span class="p">(</span><span class="n">realmChangeListener</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">realm</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p>See <a href="https://arunkumar9t2.github.io/compass/compass-paging/dev.arunkumar.compass.paging/-realm-tiled-data-source/index.html">RealmTiledDataSource</a></p>
</blockquote>
<p><strong>Note</strong>: <code class="language-plaintext highlighter-rouge">PositionalDataSource</code> is technincally deprecated, however similar to <code class="language-plaintext highlighter-rouge">Room</code>, <code class="language-plaintext highlighter-rouge">compass</code> relies on Paging 2 to 3 <a href="https://developer.android.com/topic/libraries/architecture/paging/v3-migration#migrate">migration support</a> specifically <code class="language-plaintext highlighter-rouge">asPagingSourceFactory</code> to support Paging 3. Although technically it should be possible to implement Paging 3’s <code class="language-plaintext highlighter-rouge">PagingSource</code> with the same patterns described in this article.</p>
<h5 id="viewmodel">ViewModel</h5>
<p>Jetpack <code class="language-plaintext highlighter-rouge">ViewModel</code> integration is straight-forward as shown below:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyViewModel</span><span class="p">:</span> <span class="nc">ViewModel</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">results</span> <span class="p">=</span> <span class="nc">RealmQuery</span> <span class="p">{</span> <span class="k">where</span><span class="p"><</span><span class="nc">Task</span><span class="p">>()</span> <span class="p">}.</span><span class="nf">asPagingItems</span><span class="p">().</span><span class="nf">cachedIn</span><span class="p">(</span><span class="n">viewModelScope</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">Flow</code> returned by <code class="language-plaintext highlighter-rouge">asPagingItems()</code> can be safely used for <a href="https://developer.android.com/topic/libraries/architecture/paging/v3-transform#transform-data-stream">transformations</a>, <a href="https://developer.android.com/topic/libraries/architecture/paging/v3-transform#handle-separators-ui">seperators</a> and <a href="https://developer.android.com/topic/libraries/architecture/paging/v3-transform#avoid-duplicate">caching</a>. Although supported, for <a href="https://developer.android.com/topic/libraries/architecture/paging/v3-transform#convert-ui-model">converting to UI model</a> prefer using <code class="language-plaintext highlighter-rouge">asPagingItems { /* convert */ }</code> as it is more efficient.</p>
<h4 id="sample">Sample</h4>
<p>A basic paging implementation with Jetpack Compose with options to sort shown below:</p>
<video class="phone" controls="">
<source src="https://github.com/arunkumar9t2/compass/blob/main/docs/videos/compass-sample.webm?raw=true" type="video/webm" />
Your browser does not support the video tag.
</video>
<h4 id="assumptions">Assumptions</h4>
<p>For simpler APIs, <code class="language-plaintext highlighter-rouge">compass</code> makes tradeoffs in managing <code class="language-plaintext highlighter-rouge">Realm</code> objects. It prefers to open short lived <code class="language-plaintext highlighter-rouge">Realm</code> instances using <code class="language-plaintext highlighter-rouge">Realm.getDefaultInstance()</code> instead of scoping <code class="language-plaintext highlighter-rouge">Realm</code>s to UI controllers.</p>
<p>The official <a href="https://docs.mongodb.com/realm/sdk/android/fundamentals/realms/#the-realm-lifecycle">guide</a> points that</p>
<blockquote>
<p>If the realm is already open on a different thread within the same process, opening the realm is less expensive, but still nontrivial.</p>
</blockquote>
<p>So far the pattern has worked well, but if your use case with different <code class="language-plaintext highlighter-rouge">Realm</code> schema has any troubles, I would love to hear in the comments.</p>
<h4 id="comparison-to-official-apis">Comparison to official APIs</h4>
<p>Problems outlined in this article around observability has in part been addressed officially by the <code class="language-plaintext highlighter-rouge">Realm</code> team, namely with the introduction of <a href="https://docs.mongodb.com/realm/sdk/android/advanced-guides/threading/#frozen-objects">Freezing</a> and <code class="language-plaintext highlighter-rouge">toFlow</code> / <code class="language-plaintext highlighter-rouge">toFlowable</code> APIs.</p>
<h5 id="freezing">Freezing</h5>
<p>Freezing done by calling <code class="language-plaintext highlighter-rouge">realm.freeze()</code>, creates immutable views that do not have threading limitations.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">frogs</span> <span class="p">=</span> <span class="n">realm</span><span class="p">.</span><span class="k">where</span><span class="p"><</span><span class="nc">Frog</span><span class="p">>().</span><span class="nf">findAll</span><span class="p">().</span><span class="nf">freeze</span><span class="p">()</span>
</code></pre></div></div>
<p>Although freezing ticks the most boxes of concern, namely being able to move objects across thread:</p>
<blockquote>
<p>Freezing creates an immutable view of a specific object, collection, or realm that still exists on disk and does not need to be deeply copied when passed around to other threads. You can freely share a frozen object across threads without concern for thread issues.</p>
</blockquote>
<p>The returned <code class="language-plaintext highlighter-rouge">frozen</code> objects still has ties to <code class="language-plaintext highlighter-rouge">Realm</code> and carries lifecycle risk with it:</p>
<blockquote>
<p>Frozen objects remain valid for as long as the realm that spawned them stays open. Avoid closing realms that contain frozen objects until all threads are done working with those frozen objects.</p>
</blockquote>
<p><code class="language-plaintext highlighter-rouge">compass</code>s <code class="language-plaintext highlighter-rouge">asFlow</code> APIs produce completely detached objects + the option of reading subset of data from each <code class="language-plaintext highlighter-rouge">RealmObject</code>.</p>
<h5 id="streams-via-toflow">Streams via toFlow</h5>
<p>Official recommendation around streams API is to use <code class="language-plaintext highlighter-rouge">toFlow</code> which internally uses <code class="language-plaintext highlighter-rouge">Freeze</code> to address threading concerns. However still lifecycle needs to be managed on <code class="language-plaintext highlighter-rouge">realm</code> seperately as shown below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">realm</span> <span class="p">=</span> <span class="nc">Realm</span><span class="p">.</span><span class="nf">getDefaultInstance</span><span class="p">()</span>
<span class="n">realm</span><span class="p">.</span><span class="k">where</span><span class="p"><</span><span class="nc">Task</span><span class="p">>().</span><span class="nf">findAllAsync</span><span class="p">()</span>
<span class="p">.</span><span class="nf">toFlow</span><span class="p">()</span>
<span class="p">.</span><span class="nf">flatMapLatest</span> <span class="p">{</span> <span class="n">frozenTasks</span> <span class="p">-></span>
<span class="n">frozenTasks</span><span class="p">.</span><span class="nf">asFlow</span><span class="p">()</span>
<span class="p">}.</span><span class="nf">map</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">id</span> <span class="p">}</span>
<span class="c1">// realm?</span>
</code></pre></div></div>
<p>In comparison, <code class="language-plaintext highlighter-rouge">compass</code>’s API <code class="language-plaintext highlighter-rouge">RealmQuery { where<Task>() }.asFlow { it.id }</code> automatically manages internal <code class="language-plaintext highlighter-rouge">Realm</code> instances and runs whole construction and execution in a background thread.</p>
<h3 id="summary">Summary</h3>
<p><code class="language-plaintext highlighter-rouge">compass</code> provides simpler APIs and types to make working with <code class="language-plaintext highlighter-rouge">Realm</code> easier. Abstractions around threading, lifecycle and paging with the use of Kotlin’s capable type system helps in avoiding common pitfalls.</p>
<h4 id="possible-improvements">Possible improvements</h4>
<p><code class="language-plaintext highlighter-rouge">compass</code>’s paging, currently does not take advantage of fine-grained notifications from <code class="language-plaintext highlighter-rouge">Realm</code> and still relies on <code class="language-plaintext highlighter-rouge">DiffUtil</code> / Composition Keys to perform updates. Through some carefull structuring it should be possible to implement changes without needing to manually calculate changeset.</p>
<blockquote>
<p>Compass is available here: <a href="https://github.com/arunkumar9t2/compass">https://github.com/arunkumar9t2/compass</a></p>
</blockquote>
<p>Any feedback greatly appreciated!</p>
<p>– Arun</p>{"twitter"=>"arunkumar_9t2"}I like Realm mobile database. I first started using Realm at a time when there were limited options for a reactive database - a feature common today with tools like Room and SqlDelight (remember SqlBrite?). With reactivity, Realm was pushing for persistence as single source of truth much earlier than the pattern caught on if I recall correctly. Realm’s reactivity is fine grained as well i.e can emit added, removed or modified changes on a collection without tools like DiffUtil and with correct usage it can integrate directly with RecyclerView. Apart from being reactive, it is an object oriented database, so relations can be directly expressed as Java/Kotlin objects and has a capable query system with support for aggregations and backlinks.Jetpack Compose for Non UI - Tree construction and source code generation2021-05-07T18:03:00+00:002021-05-07T18:03:00+00:00https://www.arunkumar.dev/jetpack-compose-for-non-ui-tree-construction-and-code-generation<p><a href="https://developer.android.com/jetpack/compose">Jepack Compose</a> is two things - a smart tree management solution and a UI toolkit built on top of that tree management solution.</p>
<p>Jake Wharton sums it up well in his <a href="https://jakewharton.com/a-jetpack-compose-by-any-other-name/">article</a> pushing for naming both these components differently and I agree.</p>
<blockquote>
<p>What this means is that Compose is, at its core, a general-purpose tool for managing a tree of nodes of any type. Well a “tree of nodes” describes just about anything, and as a result Compose can target just about anything.</p>
</blockquote>
<p>Originally built for Android, it has already crossed boundaries into <a href="https://www.jetbrains.com/lp/compose/">Desktop</a>, <a href="https://blog.jetbrains.com/kotlin/2021/05/technology-preview-jetpack-compose-for-web/">Web</a> and recently even <a href="https://github.com/JakeWharton/mosaic">command line</a>(!) thanks to Kotlin Multiplatform and tree management abstraction.</p>
<p>While all of them are UI, I wanted to try and explore Compose’s tree management capabilities for a non UI use case.</p>
<h3 id="what-we-will-build">What we will build?</h3>
<p>Code generation is undeniably a common use case in software development and usually done via generation libraries like <a href="https://github.com/square/javapoet">JavaPoet</a> or <a href="https://github.com/square/kotlinpoet">KotlinPoet</a> so that we have sufficient type safety and safeguard around corner cases.</p>
<p>In this article, we utilise Jetpack Compose’s tree management capabilities to generate code for GraphViz’s <code class="language-plaintext highlighter-rouge">dot</code> file format. I already have one Kotlin <a href="https://github.com/arunkumar9t2/scabbard/tree/main/dot-dsl">implementation</a> of <code class="language-plaintext highlighter-rouge">dot</code> file generation using DSLs in one of my other <a href="https://arunkumar9t2.github.io/scabbard/">projects</a> so it lets me draw parallels and compare how Jetpack Compose might help here. A basic understanding on Jetpack Compose would be helpful to the reader.</p>
<p>The final solution might look like below</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">DirectedGraph</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">node</span> <span class="p">{</span>
<span class="s">"shape"</span> <span class="err">`</span><span class="p">=</span><span class="err">`</span> <span class="s">"rectangle"</span>
<span class="p">}</span>
<span class="nc">Cluster</span><span class="p">(</span><span class="s">"Container A"</span><span class="p">)</span> <span class="p">{</span>
<span class="s">"A"</span> <span class="n">link</span> <span class="s">"B"</span>
<span class="p">}</span>
<span class="nc">Cluster</span><span class="p">(</span><span class="s">"Container B"</span><span class="p">)</span> <span class="p">{</span>
<span class="s">"C"</span> <span class="n">link</span> <span class="s">"D"</span>
<span class="p">}</span>
<span class="s">"A"</span> <span class="n">link</span> <span class="s">"D"</span>
<span class="p">}</span>
</code></pre></div></div>
<p>that generates the following <code class="language-plaintext highlighter-rouge">.dot</code> file.</p>
<div class="language-dot highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">digraph</span> <span class="s2">"Hello"</span> <span class="p">{</span>
<span class="k">node</span> <span class="o">[</span><span class="n">shape</span><span class="p">=</span><span class="s2">"rectangle"</span><span class="o">]</span>
<span class="s2">"A"</span> <span class="o">-></span> <span class="s2">"D"</span>
<span class="k">subgraph</span> <span class="s2">"cluster_Container A"</span> <span class="p">{</span>
<span class="k">graph</span> <span class="o">[</span><span class="n">label</span><span class="p">=</span><span class="s2">"Container A"</span><span class="o">]</span>
<span class="s2">"A"</span> <span class="o">-></span> <span class="s2">"B"</span>
<span class="p">}</span>
<span class="k">subgraph</span> <span class="s2">"cluster_Container B"</span> <span class="p">{</span>
<span class="k">graph</span> <span class="o">[</span><span class="n">label</span><span class="p">=</span><span class="s2">"Container B"</span><span class="o">]</span>
<span class="s2">"C"</span> <span class="o">-></span> <span class="s2">"D"</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/compose-dot-output.png#center" alt="Image generated with `dot` using the DSL output" />
<div class="mdl-typography--caption caption"><p>Image generated with <code class="language-plaintext highlighter-rouge">dot</code> using the DSL output</p>
</div>
</div>
</div>
<h3 id="code-generation">Code Generation</h3>
<p>Programming language source codes are usually represented as an <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">Abstract Syntax Tree</a> in various phases be it during compilation, IDE support (<a href="https://plugins.jetbrains.com/docs/intellij/psi-elements.html">PSIElement</a>), editing etc. Each node of that tree represents important bits of information like keywords and identifiers etc.</p>
<p>Lets consider the following function</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">sum</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nc">Int</span><span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"Calculating sum"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">a</span> <span class="p">+</span> <span class="n">b</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Assuming a <code class="language-plaintext highlighter-rouge">Statement</code> interface serving as the tree <code class="language-plaintext highlighter-rouge">Node</code> type and respective subclasses for functions, assigment etc, the above can be represented as shown below. Specific details like parantheses are omitted for brevity.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FunctionStatement (sum)
├── FunctionStatement (println)
└── ReturnStatement (return)
└── AssignmentStatement (a + b)
├── IdentifierStatement (a)
├── OperatorStatment (+)
└── IdentifierStatement (b)
</code></pre></div></div>
<p>Since Compose works so well with construction and managing of trees it should be possible to construct tree of <code class="language-plaintext highlighter-rouge">Statement</code>s shown above with just <code class="language-plaintext highlighter-rouge">@Composable</code> functions and then generate source code with resultant <code class="language-plaintext highlighter-rouge">Tree<Statement></code> that Compose manages.</p>
<h3 id="jetpack-compose-compiler">Jetpack Compose Compiler</h3>
<p>Before we jump into implementing such <code class="language-plaintext highlighter-rouge">@Composable</code> functions, I want to touch base on Compose Compiler and highlight few behaviors from a non UI point of view. Compose compiler plugin helps in adding/removing/moving tree nodes via pure function calls. For example, consider:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Function</span><span class="err">¹</span> <span class="p">{</span>
<span class="nc">Function</span><span class="err">²</span><span class="p">()</span>
<span class="nc">Return</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Adds nodes emitted by <code class="language-plaintext highlighter-rouge">Function²()</code> and <code class="language-plaintext highlighter-rouge">Return()</code> as children to root node <code class="language-plaintext highlighter-rouge">Function¹()</code>. This is automatically done without any <code class="language-plaintext highlighter-rouge">add()</code> statments, fluent builders or factories with only lexical information thanks to the compiler plugin. <a href="https://twitter.com/intelligibabble">Leland Richardson</a> explains why and how in <a href="http://intelligiblebabble.com/compose-from-first-principles/">this excellent article</a>.</p>
<h4 id="applier-composition-and-compose-nodes">Applier, Composition and Compose Nodes</h4>
<p>Compose has few APIs that allows us to add capabilties to manage tree of any type, let’s go about them one by one.</p>
<h5 id="applier">Applier</h5>
<p>The entry point to tree management is the <code class="language-plaintext highlighter-rouge">Applier</code> <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Applier.kt">interface</a> which will be used by Compose compiler plugin to modify the tree. We have full freedom to choose the tree implementation of our choice, the <code class="language-plaintext highlighter-rouge">Applier</code> abstraction simply gives callbacks at the right places to modify the tree way we want.</p>
<p>A simple implementation of <code class="language-plaintext highlighter-rouge">Applier</code> that can add nodes to a tree looks like below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Node</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">children</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p"><</span><span class="nc">Node</span><span class="p">>()</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">NodeApplier</span><span class="p">(</span><span class="n">node</span><span class="p">:</span> <span class="nc">Node</span><span class="p">)</span> <span class="p">:</span> <span class="nc">AbstractApplier</span><span class="p"><</span><span class="nc">Node</span><span class="p">>(</span><span class="n">node</span><span class="p">)</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onClear</span><span class="p">()</span> <span class="p">{}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">insertBottomUp</span><span class="p">(</span><span class="n">index</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">instance</span><span class="p">:</span> <span class="nc">Node</span><span class="p">)</span> <span class="p">{}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">insertTopDown</span><span class="p">(</span><span class="n">index</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">instance</span><span class="p">:</span> <span class="nc">Node</span><span class="p">)</span> <span class="p">{</span>
<span class="n">current</span><span class="p">.</span><span class="n">children</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">instance</span><span class="p">)</span> <span class="c1">// `current` is set to the `Node` that we want to modify.</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">move</span><span class="p">(</span><span class="n">from</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">to</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">count</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
<span class="n">current</span><span class="p">.</span><span class="n">children</span><span class="p">.</span><span class="nf">move</span><span class="p">(</span><span class="n">from</span><span class="p">,</span> <span class="n">to</span><span class="p">,</span> <span class="n">count</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">remove</span><span class="p">(</span><span class="n">index</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">count</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
<span class="n">current</span><span class="p">.</span><span class="n">children</span><span class="p">.</span><span class="nf">remove</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">count</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">Applier</code> is only used to mutate the tree, but we still don’t have a way to connect them with <code class="language-plaintext highlighter-rouge">@Composables</code>. Once we have the <code class="language-plaintext highlighter-rouge">Applier</code>, we need to create a <code class="language-plaintext highlighter-rouge">Composition</code> root so that other <code class="language-plaintext highlighter-rouge">@Composable</code> functions can be called. To put it simply, <code class="language-plaintext highlighter-rouge">@Composable</code> can be called only inside a <code class="language-plaintext highlighter-rouge">@Composable</code> function and <code class="language-plaintext highlighter-rouge">Composition</code> function helps in initating the root composition.</p>
<h5 id="composition">Composition</h5>
<p>To create a <code class="language-plaintext highlighter-rouge">Composition</code> we use <code class="language-plaintext highlighter-rouge">fun Composition(applier: Applier<*>, parent: CompositionContext)</code>, which takes the applier instance we created above.</p>
<p>To create the root composition:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">composition</span> <span class="p">=</span> <span class="nc">Composition</span><span class="p">(</span>
<span class="n">applier</span> <span class="p">=</span> <span class="nc">NodeApplier</span><span class="p">(</span><span class="n">node</span> <span class="p">=</span> <span class="nc">Node</span><span class="p">()),</span>
<span class="n">parent</span> <span class="p">=</span> <span class="nc">Recomposer</span><span class="p">(</span><span class="nc">Dispatchers</span><span class="p">.</span><span class="nc">Main</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">composition</span><span class="p">.</span><span class="nf">setContent</span> <span class="p">{</span>
<span class="c1">// Composable function calls</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Notable things here are creating the <code class="language-plaintext highlighter-rouge">Applier</code> with root node and passing a <code class="language-plaintext highlighter-rouge">Recomposer</code> instance. <code class="language-plaintext highlighter-rouge">Recomposer</code> is used for recomposition when the tree changes, since we are dealing with static tree in this article details around it are omitted for brevity but may be covered in a future post.</p>
<p>Once we have the root <code class="language-plaintext highlighter-rouge">Composition</code> we have a space to call <code class="language-plaintext highlighter-rouge">@Composable</code> functions.</p>
<h5 id="compose-node">Compose Node</h5>
<p>Even though we have a root <code class="language-plaintext highlighter-rouge">Composition</code> to call <code class="language-plaintext highlighter-rouge">@Composable</code> functions this does not mean that any <code class="language-plaintext highlighter-rouge">@Composable</code> can be called here and doing so would result in <code class="language-plaintext highlighter-rouge">RuntimeException</code> if the <code class="language-plaintext highlighter-rouge">Applier</code> type did not match.</p>
<h6 id="creating-composable-functions">Creating Composable Functions</h6>
<p>To place a node in the composition tree, we use <code class="language-plaintext highlighter-rouge">ComposeNode</code> function, a <code class="language-plaintext highlighter-rouge">@Composable</code> function. It takes the <code class="language-plaintext highlighter-rouge">Applier</code> type (should be the same one used in <code class="language-plaintext highlighter-rouge">Composition</code> earlier), factory functions and a updator function.</p>
<p>For the <code class="language-plaintext highlighter-rouge">Node</code> and <code class="language-plaintext highlighter-rouge">NodeApplier</code> the <code class="language-plaintext highlighter-rouge">ComposeNode</code> might look like below:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Composable</span>
<span class="k">fun</span> <span class="nf">Root</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nd">@Composable</span> <span class="p">()</span> <span class="p">-></span> <span class="nc">Unit</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">ComposeNode</span><span class="p"><</span><span class="nc">Node</span><span class="p">,</span> <span class="nc">NodeApplier</span><span class="p">>(</span>
<span class="n">factory</span> <span class="p">=</span> <span class="p">{</span> <span class="nc">Node</span><span class="p">()</span> <span class="p">},</span>
<span class="n">update</span> <span class="p">=</span> <span class="p">{},</span>
<span class="n">content</span> <span class="p">=</span> <span class="n">content</span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The call site for this function looks like <code class="language-plaintext highlighter-rouge">Root {}</code> similar to inbuilt composables. Invoking this function multiple times <em>emits</em> child nodes in the tree at right places.</p>
<p><a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/CustomTreeCompositionSamples.kt">Google Code sample for managing a generic tree.</a></p>
<p>Now that we have basic idea of how to connect <code class="language-plaintext highlighter-rouge">@Composable</code> function calls to a Tree type, in the remainder of the article we explore how to build <code class="language-plaintext highlighter-rouge">dot</code> language composables.</p>
<h3 id="expressing-dot-format-as-composables">Expressing Dot format as @Composables</h3>
<p>In my earlier <a href="https://github.com/arunkumar9t2/scabbard/blob/81f6acb1366dd59fad91f299a808574f00bbe437/dot-dsl/src/main/kotlin/dev/arunkumar/dot/dsl/DotGraphBuilder.kt">implementation</a> of DSL, I had basic abstraction root as a <code class="language-plaintext highlighter-rouge">DotGraph : Statement</code> interface that can hold a children of <code class="language-plaintext highlighter-rouge">Statement</code>s. Then extensions functions with infix and trailing lamda allows to build the tree without superflous statements like <code class="language-plaintext highlighter-rouge">add()</code> etc.</p>
<p>A <code class="language-plaintext highlighter-rouge">digraph "name" {}</code> statement can be abstracted as shown below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">DotGraph</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">header</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Statement</span><span class="p">()</span> <span class="p">{</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">elements</span><span class="p">:</span> <span class="nc">MutableList</span><span class="p"><</span><span class="nc">Statement</span><span class="p">></span> <span class="p">=</span> <span class="nf">mutableListOf</span><span class="p">()</span>
<span class="k">fun</span> <span class="nf">add</span><span class="p">(</span><span class="n">statement</span><span class="p">:</span> <span class="nc">Statement</span><span class="p">)</span> <span class="p">{</span>
<span class="n">elements</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">statement</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">write</span><span class="p">(</span><span class="n">level</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">writer</span><span class="p">:</span> <span class="nc">PrintWriter</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">indent</span><span class="p">(</span><span class="n">level</span><span class="p">,</span> <span class="n">writer</span><span class="p">)</span>
<span class="n">writer</span><span class="p">.</span><span class="nf">println</span><span class="p">(</span><span class="s">"$header {"</span><span class="p">)</span>
<span class="k">for</span> <span class="p">(</span><span class="n">element</span> <span class="k">in</span> <span class="n">elements</span><span class="p">)</span> <span class="p">{</span>
<span class="n">element</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">level</span> <span class="p">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">writer</span><span class="p">)</span>
<span class="p">}</span>
<span class="nf">indent</span><span class="p">(</span><span class="n">level</span><span class="p">,</span> <span class="n">writer</span><span class="p">)</span>
<span class="n">writer</span><span class="p">.</span><span class="nf">println</span><span class="p">(</span><span class="s">"}"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">Statement</code> impls can be anything, even children <code class="language-plaintext highlighter-rouge">DotGraphs</code>. I wanted to find a balance between doing everthing in compose vs earlier DSL. For instance, every statement in <code class="language-plaintext highlighter-rouge">dot</code> can be a node in the Composition tree similar to ASTs but that meant there will limited control and less flexibility compared to <code class="language-plaintext highlighter-rouge">infix</code> functions.</p>
<p>So the balance I settled for was to use <code class="language-plaintext highlighter-rouge">Compose</code> to manage a tree of <code class="language-plaintext highlighter-rouge">DotGraph</code>s and use traditional recievers to write dot statments like attributes etc into those <code class="language-plaintext highlighter-rouge">DotGraph</code> instances.</p>
<p>Without Compose or DSL, the tree can be constructed as follows</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">DotGraph</span><span class="p">(</span><span class="s">"digraph \"Hello\""</span><span class="p">).</span><span class="nf">apply</span> <span class="p">{</span>
<span class="nf">add</span><span class="p">(</span><span class="nc">DotStatement</span><span class="p">(</span><span class="s">"node"</span><span class="p">).</span><span class="nf">apply</span> <span class="p">{</span>
<span class="s">"shape"</span> <span class="err">`</span><span class="p">=</span><span class="err">`</span> <span class="s">"rectangle"</span>
<span class="p">})</span>
<span class="nf">add</span><span class="p">(</span><span class="nc">DotGraph</span><span class="p">(</span><span class="s">"subgraph \"cluster_Container A\""</span><span class="p">).</span><span class="nf">apply</span> <span class="p">{</span>
<span class="nf">add</span><span class="p">(</span><span class="nc">DotStatement</span><span class="p">(</span><span class="s">"graph"</span><span class="p">).</span><span class="nf">apply</span> <span class="p">{</span>
<span class="s">"label"</span> <span class="err">`</span><span class="p">=</span><span class="err">`</span> <span class="s">"Container A"</span>
<span class="p">})</span>
<span class="nf">add</span><span class="p">(</span><span class="nc">DotEdge</span><span class="p">(</span><span class="s">"A"</span><span class="p">,</span> <span class="s">"B"</span><span class="p">))</span>
<span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With existing constructs, let’s proceed to see how this can be cleaned up with Compose.</p>
<h4 id="composition-and-composenodes">Composition and ComposeNodes</h4>
<p>The <code class="language-plaintext highlighter-rouge">Applier</code> implementation for <code class="language-plaintext highlighter-rouge">DotGraph</code> is left as an excercise for the reader.</p>
<p>As mentioned earlier, I wanted to balance between Compose and traditional Kotlin constructs to add statements. Inspired by existing Compose usage of <code class="language-plaintext highlighter-rouge">Scope</code> interfaces to denote a Kotlin lamdba with receivers (<a href="https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope">DrawScope</a>, <a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/BoxScope">BoxScope</a>), I setup a <code class="language-plaintext highlighter-rouge">DotGraphScope</code> which can be attached as a Kotlin receiver to <code class="language-plaintext highlighter-rouge">@Composable</code> functions. Since every Composable callback will have this instance implicitly, it gives us place to add <code class="language-plaintext highlighter-rouge">infix</code> functions and other helpful methods.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@DslMarker</span>
<span class="k">annotation</span> <span class="kd">class</span> <span class="nc">DotDslScope</span>
<span class="nd">@DotDslScope</span>
<span class="k">inline</span> <span class="kd">class</span> <span class="nc">DotGraphScope</span><span class="p">(</span><span class="kd">val</span> <span class="py">dotGraph</span><span class="p">:</span> <span class="nc">DotGraph</span><span class="p">)</span> <span class="p">{</span>
<span class="k">inline</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nc">String</span><span class="p">.</span><span class="nf">invoke</span><span class="p">(</span><span class="n">nodeBuilder</span><span class="p">:</span> <span class="nc">DotNode</span><span class="p">.()</span> <span class="p">-></span> <span class="nc">Unit</span> <span class="p">=</span> <span class="p">{}):</span> <span class="nc">String</span> <span class="p">{</span>
<span class="n">dotGraph</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">DotNode</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nf">apply</span><span class="p">(</span><span class="n">nodeBuilder</span><span class="p">))</span>
<span class="k">return</span> <span class="k">this</span>
<span class="p">}</span>
<span class="k">infix</span> <span class="k">fun</span> <span class="nc">String</span><span class="p">.</span><span class="nf">link</span><span class="p">(</span><span class="n">target</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">EdgeBuilder</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">dotEdge</span> <span class="p">=</span> <span class="nc">DirectedDotEdge</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">target</span><span class="p">).</span><span class="nf">also</span><span class="p">(</span><span class="n">dotGraph</span><span class="o">::</span><span class="n">add</span><span class="p">)</span>
<span class="k">return</span> <span class="nc">EdgeBuilder</span><span class="p">(</span><span class="n">dotEdge</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// ----</span>
<span class="p">}</span>
</code></pre></div></div>
<p>I chose to create a <code class="language-plaintext highlighter-rouge">DirectedGraph</code> function that will create the root composition instance and provide a <code class="language-plaintext highlighter-rouge">@Composable</code> lambda to call other composable functions.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">DirectedGraph</span><span class="p">(</span>
<span class="n">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
<span class="n">parent</span><span class="p">:</span> <span class="nc">Recomposer</span> <span class="p">=</span> <span class="nc">Recomposer</span><span class="p">(</span><span class="nc">Dispatchers</span><span class="p">.</span><span class="nc">Main</span><span class="p">),</span>
<span class="n">content</span><span class="p">:</span> <span class="nd">@Composable</span> <span class="nc">DotGraphScope</span><span class="p">.()</span> <span class="p">-></span> <span class="nc">Unit</span>
<span class="p">):</span> <span class="nc">Pair</span><span class="p"><</span><span class="nc">DotGraph</span><span class="p">,</span> <span class="nc">Composition</span><span class="p">></span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">rootDotGraph</span> <span class="p">=</span> <span class="nc">DotGraph</span><span class="p">(</span><span class="s">"digraph ${name.quote}"</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">applier</span> <span class="p">=</span> <span class="nc">DotStatementApplier</span><span class="p">(</span><span class="n">rootDotGraph</span> <span class="p">=</span> <span class="n">rootDotGraph</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">composition</span> <span class="p">=</span> <span class="nc">Composition</span><span class="p">(</span><span class="n">applier</span> <span class="p">=</span> <span class="n">applier</span><span class="p">,</span> <span class="n">parent</span> <span class="p">=</span> <span class="n">parent</span><span class="p">)</span>
<span class="n">composition</span><span class="p">.</span><span class="nf">setContent</span> <span class="p">{</span>
<span class="nf">content</span><span class="p">(</span><span class="nc">DotGraphScope</span><span class="p">(</span><span class="n">rootDotGraph</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">applier</span><span class="p">.</span><span class="n">root</span> <span class="k">as</span> <span class="nc">DotGraph</span> <span class="n">to</span> <span class="n">composition</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">setContent</code> call kicks off the composition. I should note that it might be useful to retain the <code class="language-plaintext highlighter-rouge">Composition</code> instance hence the method returns both the root node of the tree and the <code class="language-plaintext highlighter-rouge">Composition</code>.</p>
<h5 id="graph-composables">Graph Composables</h5>
<p>Our current intention is to replace <code class="language-plaintext highlighter-rouge">add(DotGraph())</code> with <code class="language-plaintext highlighter-rouge">Cluster {}</code> and replace <code class="language-plaintext highlighter-rouge">add(DotStatement())</code> with infix functions, the latter is already done as part of <code class="language-plaintext highlighter-rouge">DotGraphScope</code>. As we learnt earlier, adding a node is as simple as a function call, so we could create a cluster composable that emits a <code class="language-plaintext highlighter-rouge">ComposeNode</code> of type <code class="language-plaintext highlighter-rouge">DotGraph</code> and registers a composable callback for its content.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Composable</span>
<span class="k">fun</span> <span class="nf">SubGraph</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nd">@Composable</span> <span class="nc">DotGraphScope</span><span class="p">.()</span> <span class="p">-></span> <span class="nc">Unit</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">dotGraph</span> <span class="p">=</span> <span class="nc">DotGraph</span><span class="p">(</span><span class="s">"subgraph $name"</span><span class="p">)</span>
<span class="nc">ComposeNode</span><span class="p"><</span><span class="nc">DotGraph</span><span class="p">,</span> <span class="nc">DotStatementApplier</span><span class="p">>(</span>
<span class="n">factory</span> <span class="p">=</span> <span class="p">{</span> <span class="n">dotGraph</span> <span class="p">},</span>
<span class="n">update</span> <span class="p">=</span> <span class="p">{},</span>
<span class="n">content</span> <span class="p">=</span> <span class="p">{</span>
<span class="nf">content</span><span class="p">(</span><span class="nc">DotGraphScope</span><span class="p">(</span><span class="n">dotGraph</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="nd">@Composable</span>
<span class="k">fun</span> <span class="nf">Cluster</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nd">@Composable</span> <span class="nc">DotGraphScope</span><span class="p">.()</span> <span class="p">-></span> <span class="nc">Unit</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">SubGraph</span><span class="p">(</span><span class="s">"cluster_$name"</span><span class="p">.</span><span class="n">quote</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">content</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Ideally the <code class="language-plaintext highlighter-rouge">dotGraph</code> should be constructed inside the <code class="language-plaintext highlighter-rouge">factory</code> but I cheated there a bit since I needed to use that instance below. This could be explored in a future article when tree mutability is explored.</p>
<h3 id="results">Results</h3>
<p>Connecting all together, we have <code class="language-plaintext highlighter-rouge">DirectedGraph</code> function to kick start the composition and set of <code class="language-plaintext highlighter-rouge">@Composable</code>s to construct the source code we intend to generate. Comprehensive example below:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">DirectedGraph</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">node</span> <span class="p">{</span>
<span class="s">"shape"</span> <span class="err">`</span><span class="p">=</span><span class="err">`</span> <span class="s">"rectangle"</span>
<span class="p">}</span>
<span class="nc">Cluster</span><span class="p">(</span><span class="s">"Container A"</span><span class="p">)</span> <span class="p">{</span>
<span class="s">"Item 1"</span> <span class="n">link</span> <span class="s">"Item 2"</span>
<span class="p">}</span>
<span class="nc">Cluster</span><span class="p">(</span><span class="s">"Container B"</span><span class="p">)</span> <span class="p">{</span>
<span class="s">"Item 3"</span> <span class="n">link</span> <span class="s">"Item 4"</span>
<span class="p">}</span>
<span class="nc">Cluster</span><span class="p">(</span><span class="s">"Container C"</span><span class="p">)</span> <span class="p">{</span>
<span class="s">"Item 5"</span> <span class="p">{</span>
<span class="s">"color"</span> <span class="err">`</span><span class="p">=</span><span class="err">`</span> <span class="s">"blue"</span>
<span class="p">}</span>
<span class="nc">Cluster</span><span class="p">(</span><span class="s">"Inner Container"</span><span class="p">)</span> <span class="p">{</span>
<span class="s">"Item 6"</span> <span class="p">{</span>
<span class="s">"color"</span> <span class="err">`</span><span class="p">=</span><span class="err">`</span> <span class="s">"red"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="s">"Item 1"</span> <span class="n">link</span> <span class="s">"Item 4"</span>
<span class="s">"Item 2"</span> <span class="n">link</span> <span class="s">"Item 5"</span>
<span class="s">"Item 6"</span> <span class="n">link</span> <span class="s">"Item 3"</span>
<span class="p">}</span>
</code></pre></div></div>
<p>and generated image by passing the <code class="language-plaintext highlighter-rouge">.dot</code> file to <code class="language-plaintext highlighter-rouge">dot</code>:</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/compose-dot-results.png#center" alt="Image generated with `dot` using the DSL output" />
<div class="mdl-typography--caption caption"><p>Image generated with <code class="language-plaintext highlighter-rouge">dot</code> using the DSL output</p>
</div>
</div>
</div>
<blockquote>
<p>The entire project setup with Gradle and results can be found <a href="https://github.com/arunkumar9t2/compose-dot">here</a>.</p>
</blockquote>
<h3 id="compose-advantages">Compose advantages</h3>
<p><code class="language-plaintext highlighter-rouge">DSL</code> can also provide similar functionality of constructing such trees but how does it compare against <code class="language-plaintext highlighter-rouge">@Composable</code> functions? There are some key advantages</p>
<ol>
<li>As shown above adding, moving and removing nodes are operations supported natively by compose. That means converting node operations to <code class="language-plaintext highlighter-rouge">@Composable</code> automatically adds those guarantees. For example, instead of custom types to host <code class="language-plaintext highlighter-rouge">add()</code> functions as shown below
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Builder</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">node</span><span class="p">(</span><span class="n">builder</span> <span class="p">:</span> <span class="nc">Node</span><span class="p">.()</span> <span class="p">-></span> <span class="nc">Unit</span><span class="p">))</span> <span class="p">{</span>
<span class="nf">add</span><span class="p">(</span><span class="nc">Node</span><span class="p">().</span><span class="nf">apply</span><span class="p">(</span><span class="n">builder</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div> </div>
<p>we can create a <code class="language-plaintext highlighter-rouge">@Composable Node()</code> function that has the capabilities automatically generated by compiler plugin.</p>
</li>
<li><code class="language-plaintext highlighter-rouge">@Composable</code>s are just functions. They are not usually hosted inside class types and forces code to be self contained.</li>
<li>Though mutablitity is not covered in this article, it is one of strong features of compose - the ability to do smart updates to tree might be challenging to implement with traditional DSLs. Although possible, it might not be as concise as <code class="language-plaintext highlighter-rouge">@Composable</code> functions.</li>
<li><a href="https://developer.android.com/jetpack/compose/state">State management</a>. Compose provides property based acces to state and can automatically recompose affected nodes - this can be incredibly useful when needing to modify lot of nodes in response to state changes.</li>
<li><code class="language-plaintext highlighter-rouge">CompositionLocal</code> - Using <code class="language-plaintext highlighter-rouge">@Composable</code> functions also unlocks several compose specific features like <code class="language-plaintext highlighter-rouge">CompositionLocal</code>s. Even though <code class="language-plaintext highlighter-rouge">@Composable</code>s are static functions, compiler plugin rewrites them into a scope and enables features like state hoisting. Let’s say we have a need to transfer some information from parent nodes to children nodes. Using CompositionLocal we can implicitly share this state while retaining benefits of stand alone static functions.
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">val</span> <span class="py">RootGraphScope</span> <span class="p">=</span> <span class="n">compositionLocalOf</span><span class="p"><</span><span class="nc">DotGraphScope</span><span class="p">></span> <span class="p">{</span> <span class="nf">error</span><span class="p">(</span><span class="s">"Not set"</span><span class="p">)</span> <span class="p">}</span>
<span class="nc">DirectedGraph</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">CompositionLocalProvider</span><span class="p">(</span><span class="nc">RootGraphScope</span> <span class="n">provides</span> <span class="k">this</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">node</span> <span class="p">{</span>
<span class="s">"shape"</span> <span class="err">`</span><span class="p">=</span><span class="err">`</span> <span class="s">"rectangle"</span>
<span class="p">}</span>
<span class="nc">Cluster</span><span class="p">(</span><span class="s">"Inner Container"</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">RootGraphScope</span><span class="p">.</span><span class="n">current</span><span class="p">.</span><span class="nf">graph</span> <span class="p">{</span> <span class="c1">// access parent scope from children</span>
<span class="s">"color"</span> <span class="err">`</span><span class="p">=</span><span class="err">`</span> <span class="s">"red"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div> </div>
<p>Here through use of <code class="language-plaintext highlighter-rouge">CompositionLocal</code>s it should be possible for childrent to access state from parents. It happens behind the scenes without changing the function signature.</p>
</li>
</ol>
<h3 id="future-work">Future work</h3>
<p>While the current solution explores basic tree management, there are still plethora of things I want to explore further like async with coroutines, tree mutation, recompostion and parallel composition etc. Code generation might not be an exact fit for those things though.</p>
<p>Additionally, Compose has concept of <code class="language-plaintext highlighter-rouge">Modifier</code>s to add additional node attributes. For example <code class="language-plaintext highlighter-rouge">shape = rectangle</code> for dot node could be a <code class="language-plaintext highlighter-rouge">Modifier</code> instead but I chose to avoid it since I wanted to retain ability to add any type of attribute with less code. But still <code class="language-plaintext highlighter-rouge">Modifier</code> is a valid usecase and I intend to explore them further.</p>
<p>Would be glad to hear any feedback/concerns in the comments below, thanks.</p>
<p>– Arun</p>{"twitter"=>"arunkumar_9t2"}Jepack Compose is two things - a smart tree management solution and a UI toolkit built on top of that tree management solution.Scabbard: Dagger Hilt integration and visualizing component hierarchies2020-06-20T22:20:00+00:002020-06-20T22:20:00+00:00https://www.arunkumar.dev/scabbard-dagger-hilt-integration-and-visualizing-component-hierarchies<p>Recently Google released an opinionated and recommended way for dependency injection in Android with Dagger. Much has already been said on this topic and the official <a href="https://developer.android.com/training/dependency-injection/hilt-android">guides</a> are pretty good way to get started. In this short article, I plan to share a bit about <a href="/introducing-scabbard-a-tool-to-visualize-dagger-2-dependency-graphs/">Scabbard</a>’s new release <strong>0.4.0</strong> which brings couple of integrations for Hilt.</p>
<h3 id="hilt-overview">Hilt overview</h3>
<p>As mentioned earlier, Hilt is opinionated in both component hierarchy and component entry points. This means it encourages us to simply define the bindings and where we want it placed and Hilt takes care of placing it correctly in a set of predefined components. While this is great for reducing “boilerplate” (quotes because I did not think of them as boilerplate before), I could not help but think there is more magic happening behind the scenes. For example, <code class="language-plaintext highlighter-rouge">dagger-android</code> automated <code class="language-plaintext highlighter-rouge">@Subcomponent</code> creation for us while Hilt automates the whole <code class="language-plaintext highlighter-rouge">@Component</code> creation as well. In order to do this, Hilt provides new annotations like <code class="language-plaintext highlighter-rouge">@AndroidEntryPoint</code> and <code class="language-plaintext highlighter-rouge">@HiltAndroidApp</code>. I do believe that with Scabbard, it should be possible to gain insights on what Hilt is doing behind the scenes and how they relate to these annotations.</p>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="/assets/images/hilt-components.svg" alt="Hilt hierarchy from official guide" width="100%" />
<div class="mdl-typography--caption caption"><p>Hilt hierarchy from official guide</p>
</div>
</div>
</div>
<h3 id="scabbards-hilt-support">Scabbard’s Hilt support</h3>
<p>While Scabbard’s image generation <a href="https://twitter.com/arunkumar_9t2/status/1260915216663015426">worked</a> out of the box with Hilt, the IDE plugin was not aware of these new annotations. Luckily it was easy to add support because of <a href="https://dagger.dev/hilt/monolithic.html">monolithic</a> components. Since Hilt encourages monolith components, we can establish a many:1 mapping between Android system components and Hilt generated components. In other words, if <code class="language-plaintext highlighter-rouge">@AndroidEntryPoint</code> is added on an <code class="language-plaintext highlighter-rouge">Activity</code> then the bindings for it will always be in <code class="language-plaintext highlighter-rouge">ActivityComponent</code> (generated) no matter which Activity. With these assumptions, Scabbard supports the following annotations.</p>
<h4 id="androidentrypoint"><code class="language-plaintext highlighter-rouge">@AndroidEntryPoint</code></h4>
<p>Scabbard will show a gutter icon on Android entry point annotated components that links to the equivalent Hilt generated component as shown here.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/android-entry-point-activity-sample.png#center" alt="Scabbard Gutter for Android Entry Points" />
<div class="mdl-typography--caption caption"><p>Scabbard Gutter for Android Entry Points</p>
</div>
</div>
</div>
<h5 id="withfragmentbindings"><code class="language-plaintext highlighter-rouge">@WithFragmentBindings</code></h5>
<p>From the graph above we see that it is possible for <code class="language-plaintext highlighter-rouge">View</code> to optionally have bindings from Fragment and this is differentiated via the <code class="language-plaintext highlighter-rouge">@WithFragmentBindings</code> annotation. By checking for presence of this annotation, Scabbard links to <code class="language-plaintext highlighter-rouge">ViewComponent</code> or <code class="language-plaintext highlighter-rouge">ViewWithFragmentComponent</code>.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/android-entry-point-view.png#center" alt="Scabbard Gutter for Views" />
<div class="mdl-typography--caption caption"><p>Scabbard Gutter for Views</p>
</div>
</div>
</div>
<h5 id="hiltandroidapp"><code class="language-plaintext highlighter-rouge">@HiltAndroidApp</code></h5>
<p>Similar to above but links to the application root component.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/hilt-android-app-sample.png#center" alt="Scabbard Gutter for viewing root application component" />
<div class="mdl-typography--caption caption"><p>Scabbard Gutter for viewing root application component</p>
</div>
</div>
</div>
<h5 id="definecomponent"><code class="language-plaintext highlighter-rouge">@DefineComponent</code></h5>
<p>Since Hilt handles the component generation now, there must be a way to define custom components and that is where <code class="language-plaintext highlighter-rouge">@DefineComponent</code> comes in. When we define a custom interface annotated with <code class="language-plaintext highlighter-rouge">@DefineComponent</code>, Hilt generates a <code class="language-plaintext highlighter-rouge">@Component</code> interface and makes it implement the interface we defined earlier. To find the generated component, it was as simple as finding the subclasses of the interface.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/hilt-custom-component.png#center" alt="Scabbard Gutter for viewing custom component graph" />
<div class="mdl-typography--caption caption"><p>Scabbard Gutter for viewing custom component graph</p>
</div>
</div>
</div>
<p>This is the gist of the recent additions for Hilt. With regards to the graph themselves not much has changed other than some minor cleanups in names of the components.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/dev.arunkumar.scabbard.sample.hilt.HiltSampleApp_HiltComponents.ApplicationC.svg#center" alt="" />
<div class="mdl-typography--caption caption">
</div>
</div>
</div>
<h3 id="visualizing-component-hierarchies">Visualizing Component Hierarchies</h3>
<p>This has been in plan for a while and I am happy to report Scabbard 0.4.0 supports visualizing Component hierarchies and scopes similar to the one presented in Hilt docs. Actually there is a long <a href="https://github.com/google/dagger/issues/288">thread</a> on dagger repo around this subject and this was my inspiration to build scabbard in the first place. The hierarchy images will be generated alongside dependency graph images and gutter icon pop up presents option to open it. This is only presented for root components. For example, we can visualize the entire magic that <code class="language-plaintext highlighter-rouge">Hilt</code> does behind the scenes by simple adding scabbard.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/tree_dev.arunkumar.scabbard.sample.hilt.HiltSampleApp_HiltComponents.ApplicationC_old.svg#center" alt="Hilt generated components' hierarchy" />
<div class="mdl-typography--caption caption"><p>Hilt generated components’ hierarchy</p>
</div>
</div>
</div>
<p>Now let’s try modifying the hierarchy by adding a custom component with <code class="language-plaintext highlighter-rouge">@DefineComponent</code>:</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/tree_dev.arunkumar.scabbard.sample.hilt.HiltSampleApp_HiltComponents.ApplicationC.svg#center" alt="" />
<div class="mdl-typography--caption caption">
</div>
</div>
</div>
<p>As expected, the component became the subcomponent of <code class="language-plaintext highlighter-rouge">ApplicationComponent</code>. <em>Additionally as mentioned in 0.2.0 release notes, you could open the subcomponent graph by clicking on the components when you open the svg in browser.</em></p>
<h3 id="summary">Summary</h3>
<p>In addition to these new features, there have been couple of minor bug fixes as well which are described in <a href="https://github.com/arunkumar9t2/scabbard/releases/tag/0.4.0">release notes</a>. Overall I am excited for this release and would be glad to hear any feedback or suggestions for improvements. Please reach out to me via Twitter, Email or comments below, thanks and stay safe!</p>{"twitter"=>"arunkumar_9t2"}Recently Google released an opinionated and recommended way for dependency injection in Android with Dagger. Much has already been said on this topic and the official guides are pretty good way to get started. In this short article, I plan to share a bit about Scabbard’s new release 0.4.0 which brings couple of integrations for Hilt.Dagger SPI - Extending Dagger with custom Dependency Graph validations2020-04-25T00:18:00+00:002020-04-25T00:18:00+00:00https://www.arunkumar.dev/dagger-spi-building-custom-validations-for-dependency-graphs<p>In one of recent Dagger versions, Google added support for processing internal Dagger dependency graph information as part of the <code class="language-plaintext highlighter-rouge">dagger-spi</code> artifact (also read <a href="https://stackoverflow.com/questions/2954372/difference-between-spi-and-api">SPI vs API</a>). According to the <a href="https://Dagger.dev/dev-guide/spi">docs</a>, it allows us access to the same model information that Dagger internally uses and lets us add few functionality on top of Dagger’s compiler - like a plugin. Recently I used this functionality to <a href="https://arunkumar.dev/introducing-scabbard-a-tool-to-visualize-dagger-2-dependency-graphs/">build a tool</a> that visualizes how the overall dependency graph of your project is structured. In this article, I plan to discuss one other functionality of the SPI artifact (validations) and explain how to consume this SPI from scratch.</p>
<h3 id="what-we-will-build">What we will build?</h3>
<p>A compile time validator for Dagger declarations that can</p>
<ul>
<li>Validate a Dagger binding according to project specific needs.</li>
<li>Have customized behavior based on options received from Java compiler options.</li>
<li>Provide rich formatted error messages similar to Dagger.</li>
<li>Minor guidance on how to organize these validations for scaling.</li>
</ul>
<h3 id="why-custom-validations">Why custom validations?</h3>
<p>While Dagger is excellent at compile time static validations and preventing misconfigured DI graph from crashing your application, it does not have any knowledge of the consuming framework’s functional needs. At its core, it only sees types we declare and don’t know anything about framework requirements - which is good. <code class="language-plaintext highlighter-rouge">dagger-android</code> and upcoming <code class="language-plaintext highlighter-rouge">dagger.hilt</code> are exceptions but the point still stands because they are built on top of Dagger core. Because of this, there is nothing stopping us from declaring bindings that can negatively affect your application stability and we usually realy on our expertise to manually vet those declarations. If we somehow are able to connect these framework specific functionalities to Dagger’s already existing validations then we can prevent misuse.</p>
<h4 id="various-faces-of-androids-context">Various Faces of Android’s Context</h4>
<p>If you are an Android developer today, you probably would have stumbled upon this <a href="https://stackoverflow.com/questions/3572463/what-is-context-on-android">question</a>. Each of the <code class="language-plaintext highlighter-rouge">ContextWrapper</code> implementations serve a different purpose. For example, <code class="language-plaintext highlighter-rouge">Application</code>, <code class="language-plaintext highlighter-rouge">Service</code> or <code class="language-plaintext highlighter-rouge">Activity</code> etc. It is common knowledge to excercise caution when using <code class="language-plaintext highlighter-rouge">Activity</code> instance - holding it too long outside of the active scope will easily leak memory and usual solution is to prefer <code class="language-plaintext highlighter-rouge">Application</code> instance. But then again you lose all the configuration and theme data if you use <code class="language-plaintext highlighter-rouge">Application</code>. Given all the specifics, all the below declarations in Dagger are valid:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Binds</span>
<span class="k">fun</span> <span class="nf">bindApplication</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Application</span><span class="p">):</span> <span class="nc">Context</span>
<span class="nd">@Binds</span>
<span class="k">fun</span> <span class="nf">bindActivity</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Activity</span><span class="p">):</span> <span class="nc">Context</span>
<span class="nd">@Binds</span>
<span class="k">fun</span> <span class="nf">bindService</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Service</span><span class="p">):</span> <span class="nc">Context</span>
</code></pre></div></div>
<p>With these bindings, it is possible to inject short lived implementation like <code class="language-plaintext highlighter-rouge">Activity</code> into a larger scoped object causing memory leak. I personally prefer to avoid <code class="language-plaintext highlighter-rouge">Context</code> bindings altogether and <a href="https://github.com/arunkumar9t2/scabbard/blob/449be3e48bc465daaf09992475de3ae16ff556ef/samples/android-kotlin/src/main/java/dev/arunkumar/scabbard/di/AppComponent.kt#L36">bind implementation</a> specifically. Using Dagger <code class="language-plaintext highlighter-rouge">@Qualifier</code> is also another approach but <strong>it is not enforced</strong> by Dagger.</p>
<p>In this article, let’s build a custom validation for <code class="language-plaintext highlighter-rouge">android.content.Context</code> that fails the build if it detects a <code class="language-plaintext highlighter-rouge">Context</code> binding without any <code class="language-plaintext highlighter-rouge">@Qualifier</code>.</p>
<h3 id="trident">Trident</h3>
<p>Continuing the knife related naming thing going on with Dagger, for this project I chose <strong>Trident</strong>, a special form of late middle ages <a href="https://en.wikipedia.org/wiki/Parrying_dagger#Trident_dagger">Dagger</a> designed for defending or parrying. I saw the word “defend”, so I am rolling with it for validations (heh).</p>
<h3 id="bindinggraphplugin">BindingGraphPlugin</h3>
<p>Before jumping to implementation details, I think it helps to talk a bit about specifics of the SPI first. <a href="https://github.com/google/dagger/blob/master/java/dagger/spi/BindingGraphPlugin.java"><code class="language-plaintext highlighter-rouge">BindingGraphPlugin</code></a> is our single source of extension where the <code class="language-plaintext highlighter-rouge">visitGraph(bindingGraph: BindingGraph, diagnosticReporter: DiagnosticReporter)</code> will be called for every <code class="language-plaintext highlighter-rouge">@Component</code>. From here, we can utilize the graph information available in <code class="language-plaintext highlighter-rouge">BindingGraph</code> and then report errors using <code class="language-plaintext highlighter-rouge">DiagnosticReporter</code>. In order to let Dagger discover our custom plugin, we have to make our implementation available in the classpath so that Dagger can use <code class="language-plaintext highlighter-rouge">ServiceLoader</code> to instantiate our class (more on this later).</p>
<h4 id="bindinggraph">BindingGraph</h4>
<p><code class="language-plaintext highlighter-rouge">BindingGraph</code> is how Dagger represents our dependency graph. Dagger performs a lot of checks before construction and then finally reports the constructed graph to plugin, where we are able to inspect it. It is <strong>not possible to modify the graph</strong> once constructed. Some notable constructs of <code class="language-plaintext highlighter-rouge">BindingGraph</code> are listed below.</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">Node</code> - As the name suggests, a node in the graph and it can be any of the below.
<ul>
<li><code class="language-plaintext highlighter-rouge">ComponentNode</code> - A node that defines the <code class="language-plaintext highlighter-rouge">Component</code> itself. For example, if you have an <code class="language-plaintext highlighter-rouge">AppComponent</code> and a <code class="language-plaintext highlighter-rouge">AppSubComponent</code> they will be present as <code class="language-plaintext highlighter-rouge">ComponentNode</code> in the graph.</li>
<li><code class="language-plaintext highlighter-rouge">MaybeBinding</code> - A marker to denote that <code class="language-plaintext highlighter-rouge">Node</code> can be a dependency binding. <em>Can</em> is an important term here.
<ul>
<li><code class="language-plaintext highlighter-rouge">MissingBinding</code> - If we forget to declare a binding that another dependency uses, it is denoted as <code class="language-plaintext highlighter-rouge">MissingBinding</code> and used in error reporting and build usually fails. One exception is full binding graph validation but that is not the scope of this article.</li>
<li><code class="language-plaintext highlighter-rouge">Binding</code> - A valid dependency declaration is denoted as <code class="language-plaintext highlighter-rouge">Binding</code>. For example, if we have <code class="language-plaintext highlighter-rouge">@Provides fun provides(): Vehicle</code>, then we have <code class="language-plaintext highlighter-rouge">Binding</code> instance for <code class="language-plaintext highlighter-rouge">Vehicle</code>.</li>
</ul>
</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">Edge</code> - As the name suggests, an edge connects any two nodes in the graph. There are many variations of it as listed below.
<ul>
<li><code class="language-plaintext highlighter-rouge">DependencyEdge</code> - An edge between two dependency, meaning the source depends on the target. For example, <code class="language-plaintext highlighter-rouge">Vehicle(tyres: List<Tyre>)</code>, edge starts from <code class="language-plaintext highlighter-rouge">Binding</code> of vehicle and ends at binding of tyres.</li>
<li><code class="language-plaintext highlighter-rouge">SubcomponentCreatorBindingEdge</code> - An edge to represent a link between a parent component and creator of the subcomponent. Example, <code class="language-plaintext highlighter-rouge">Subcomponent.Builder</code> or <code class="language-plaintext highlighter-rouge">Subcomponent.Factory</code>.</li>
<li><code class="language-plaintext highlighter-rouge">ChildFactoryMethodEdge</code> - Similar to above but represents a link between the parent component and the child subcomponent.</li>
</ul>
</li>
</ul>
<h4 id="diagnostic-reporter">Diagnostic Reporter</h4>
<p>Based on data from <code class="language-plaintext highlighter-rouge">BindingGraph</code> we can report our custom validation result using <a href="https://github.com/google/dagger/blob/master/java/dagger/spi/DiagnosticReporter.java"><code class="language-plaintext highlighter-rouge">DiagnosticReporter</code></a>. The API is straight forward - for the <code class="language-plaintext highlighter-rouge">Node</code>s or <code class="language-plaintext highlighter-rouge">Edge</code>s we think are invalid, we call one of the <code class="language-plaintext highlighter-rouge">reportXXX()</code> methods and Dagger will format that error in a neat way and fail the build or warn accordingly.</p>
<p>In addition to these, there are also <code class="language-plaintext highlighter-rouge">Filer</code>, <code class="language-plaintext highlighter-rouge">Types</code>, <code class="language-plaintext highlighter-rouge">Elements</code> and an option map are provided. <code class="language-plaintext highlighter-rouge">Filer</code> can be used to generate files and <code class="language-plaintext highlighter-rouge">Types</code>, <code class="language-plaintext highlighter-rouge">Elements</code> give access to type system which we can use if needed.</p>
<h3 id="setting-up-the-project">Setting up the project</h3>
<p>As mentioned earlier, all of these validation run during compile time and not at runtime. In order for Dagger to discover our custom plugin using Java’s <code class="language-plaintext highlighter-rouge">ServiceLoader</code>, we have to make sure we declare our plugin code in the annotation processor classpath. i.e either <code class="language-plaintext highlighter-rouge">annotationProcessor</code> or <code class="language-plaintext highlighter-rouge">kapt</code> for Koltin projects.</p>
<ol>
<li>Create a Java or Kotlin library module.</li>
<li>Add an <code class="language-plaintext highlighter-rouge">implmentation</code> dependency on <code class="language-plaintext highlighter-rouge">dagger-spi</code> like <code class="language-plaintext highlighter-rouge">implementation "com.google.dagger:dagger-spi:${versions.dagger}"</code>.</li>
<li>Now we should be able to extend <code class="language-plaintext highlighter-rouge">BindingGraphPlugin</code> and add our custom validations.
<ul>
<li>For automating <code class="language-plaintext highlighter-rouge">ServiceLoader</code> declarations, we can use Google’s <a href="https://github.com/google/auto/tree/master/service">auto service</a>. Thus the declartion becomes
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@AutoService</span><span class="p">(</span><span class="nc">BindingGraphPlugin</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
<span class="kd">class</span> <span class="nc">TridentValidator</span> <span class="p">:</span> <span class="nc">BindingGraphPlugin</span> <span class="p">{</span>
<span class="c1">// implementation</span>
<span class="p">}</span>
</code></pre></div> </div>
</li>
</ul>
</li>
<li>Finally this module should be consumed in the annotation processor configuration like <code class="language-plaintext highlighter-rouge">kapt project(":dagger-validator")</code></li>
</ol>
<h3 id="writing-validations">Writing validations</h3>
<p>We can verify if the above setup is working by adding a <code class="language-plaintext highlighter-rouge">println</code> to <code class="language-plaintext highlighter-rouge">visitGraph</code>. Once done, we can straight away start with validations in <code class="language-plaintext highlighter-rouge">visitGraph</code> block. For Android’s Context validation, we simply need to look for <code class="language-plaintext highlighter-rouge">Binding</code> instance that has a type <code class="language-plaintext highlighter-rouge">android.content.Context</code> and whether it has any <code class="language-plaintext highlighter-rouge">@Qualifier</code> associated with it. If it does not have any then we simply call <code class="language-plaintext highlighter-rouge">diagnosticReporter.reportBinding</code> as shown below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">fun</span> <span class="nf">visitGraph</span><span class="p">(</span>
<span class="n">bindingGraph</span><span class="p">:</span> <span class="nc">BindingGraph</span><span class="p">,</span>
<span class="n">diagnosticReporter</span><span class="p">:</span> <span class="nc">DiagnosticReporter</span>
<span class="p">)</span> <span class="p">{</span>
<span class="n">bindingGraph</span><span class="p">.</span><span class="nf">bindings</span><span class="p">()</span>
<span class="p">.</span><span class="nf">filter</span> <span class="p">{</span> <span class="n">binding</span> <span class="p">-></span>
<span class="kd">val</span> <span class="py">key</span> <span class="p">=</span> <span class="n">binding</span><span class="p">.</span><span class="nf">key</span><span class="p">()</span>
<span class="n">key</span><span class="p">.</span><span class="nf">type</span><span class="p">().</span><span class="nf">toString</span><span class="p">()</span> <span class="p">==</span> <span class="s">"android.content.Context"</span> <span class="p">&&</span> <span class="p">!</span><span class="n">key</span><span class="p">.</span><span class="nf">qualifier</span><span class="p">().</span><span class="n">isPresent</span>
<span class="p">}.</span><span class="nf">forEach</span> <span class="p">{</span> <span class="n">contextBinding</span> <span class="p">-></span>
<span class="n">diagnosticReporter</span><span class="p">.</span><span class="nf">reportBinding</span><span class="p">(</span>
<span class="nc">ERROR</span><span class="p">,</span>
<span class="n">contextBinding</span><span class="p">,</span>
<span class="s">"Please annotate context binding with any qualifier"</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In the consuming <code class="language-plaintext highlighter-rouge">app</code> module, let’s bind <code class="language-plaintext highlighter-rouge">Context</code> with <code class="language-plaintext highlighter-rouge">@BindsInstance</code> and run the build.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Singleton</span>
<span class="nd">@Component</span>
<span class="kd">interface</span> <span class="nc">AppComponent</span> <span class="p">:</span> <span class="nc">AndroidInjector</span><span class="p"><</span><span class="nc">SpiValidation</span><span class="p">></span> <span class="p">{</span>
<span class="nd">@Component</span><span class="p">.</span><span class="nc">Factory</span>
<span class="kd">interface</span> <span class="nc">Factory</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">create</span><span class="p">(</span><span class="nd">@BindsInstance</span> <span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">):</span> <span class="nc">AppComponent</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As expected the build would fail with the error as show below. By using <code class="language-plaintext highlighter-rouge">DiagnosticReporter</code>, we are able to retain the same error format Dagger uses.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">error:</span> <span class="o">[</span><span class="n">dev</span><span class="o">.</span><span class="na">arunkumar</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">validator</span><span class="o">.</span><span class="na">TridentValidator</span><span class="o">]</span> <span class="nc">Please</span> <span class="n">annotate</span> <span class="n">context</span> <span class="n">binding</span> <span class="n">with</span> <span class="n">any</span> <span class="n">qualifier</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">interface</span> <span class="nc">AppComponent</span> <span class="kd">extends</span> <span class="n">dagger</span><span class="o">.</span><span class="na">android</span><span class="o">.</span><span class="na">AndroidInjector</span><span class="o"><</span><span class="n">dev</span><span class="o">.</span><span class="na">arunkumar</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">spi</span><span class="o">.</span><span class="na">validation</span><span class="o">.</span><span class="na">SpiValidation</span><span class="o">></span> <span class="o">{</span>
<span class="o">^</span>
<span class="n">android</span><span class="o">.</span><span class="na">content</span><span class="o">.</span><span class="na">Context</span> <span class="n">is</span> <span class="n">injected</span> <span class="n">at</span>
<span class="n">dev</span><span class="o">.</span><span class="na">arunkumar</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">spi</span><span class="o">.</span><span class="na">validation</span><span class="o">.</span><span class="na">MainActivity</span><span class="o">.</span><span class="na">context</span>
<span class="n">dev</span><span class="o">.</span><span class="na">arunkumar</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">spi</span><span class="o">.</span><span class="na">validation</span><span class="o">.</span><span class="na">MainActivity</span> <span class="n">is</span> <span class="n">injected</span> <span class="n">at</span>
<span class="n">dagger</span><span class="o">.</span><span class="na">android</span><span class="o">.</span><span class="na">AndroidInjector</span><span class="o">.</span><span class="na">inject</span><span class="o">(</span><span class="no">T</span><span class="o">)</span> <span class="o">[</span><span class="n">dev</span><span class="o">.</span><span class="na">arunkumar</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">spi</span><span class="o">.</span><span class="na">validation</span><span class="o">.</span><span class="na">di</span><span class="o">.</span><span class="na">AppComponent</span> <span class="err">→</span> <span class="n">dev</span><span class="o">.</span><span class="na">arunkumar</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">spi</span><span class="o">.</span><span class="na">validation</span><span class="o">.</span><span class="na">MainActivity_Builder_MainActivity</span><span class="o">.</span><span class="na">MainActivitySubcomponent</span><span class="o">]</span>
<span class="o">></span> <span class="nc">Task</span> <span class="o">:</span><span class="nl">app:</span><span class="n">kaptDebugKotlin</span> <span class="no">FAILED</span>
</code></pre></div></div>
<h3 id="customizing-behavior">Customizing Behavior</h3>
<p>Similar to Dagger, there is a way for us to customize our validation behavior based on compiler arguments provided via Gradle and <code class="language-plaintext highlighter-rouge">javac</code>. This is done via <code class="language-plaintext highlighter-rouge">supportedOptions()</code> and <code class="language-plaintext highlighter-rouge">initOptions()</code> respectively. The overall flow of options looks as shown in the diagram below.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/dagger-spi-options.png#center" alt="BindingGraphPlugin options flow" />
<div class="mdl-typography--caption caption"><p>BindingGraphPlugin options flow</p>
</div>
</div>
</div>
<p>First, we declare all the supported options via <code class="language-plaintext highlighter-rouge">supportedOptions()</code> as <code class="language-plaintext highlighter-rouge">Set<String></code>. Dagger then uses this information to filter out raw options received from compiler and then calls <code class="language-plaintext highlighter-rouge">initOptions</code>. It is good practice to properly namespace the option key for clarity. For example, instead of simply stating <code class="language-plaintext highlighter-rouge">enabled</code>, we could expose <code class="language-plaintext highlighter-rouge">trident.enabled</code> which is much clearer in intention. Personally, I don’t much prefer working with <code class="language-plaintext highlighter-rouge">Map<String, String></code> for options. It is better to abstract the options into a type safe data structure. One such way is shown below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Name space for compiler options
*/</span>
<span class="k">private</span> <span class="k">const</span> <span class="kd">val</span> <span class="py">TRIDENT_NAMESPACE</span> <span class="p">=</span> <span class="s">"trident"</span>
<span class="cm">/**
* Source of truth for all supported options
*/</span>
<span class="k">enum</span> <span class="kd">class</span> <span class="nc">SupportedOptions</span><span class="p">(</span><span class="kd">val</span> <span class="py">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">ENABLED</span><span class="p">(</span><span class="s">"$TRIDENT_NAMESPACE.enabled"</span><span class="p">),</span>
<span class="p">}</span>
<span class="kd">val</span> <span class="py">SUPPORTED_OPTIONS</span> <span class="p">=</span> <span class="nf">values</span><span class="p">().</span><span class="nf">map</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">key</span> <span class="p">}.</span><span class="nf">toMutableSet</span><span class="p">()</span>
<span class="kd">data class</span> <span class="nc">TridentOptions</span><span class="p">(</span><span class="kd">val</span> <span class="py">enabled</span><span class="p">:</span> <span class="nc">Boolean</span> <span class="p">=</span> <span class="k">false</span><span class="p">)</span>
<span class="cm">/**
* @return true if this map contains `key` and its value is a `Boolean` with `true`.
*/</span>
<span class="k">private</span> <span class="k">fun</span> <span class="nf">Map</span><span class="p"><</span><span class="nc">String</span><span class="p">,</span> <span class="nc">String</span><span class="p">>.</span><span class="nf">booleanValue</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">Boolean</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">containsKey</span><span class="p">(</span><span class="n">key</span><span class="p">)</span> <span class="p">&&</span> <span class="k">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span><span class="o">?.</span><span class="nf">toBoolean</span><span class="p">()</span> <span class="p">==</span> <span class="k">true</span>
<span class="p">}</span>
<span class="cm">/**
* Parses raw key value pair received from javac and maps it to typed data structure (`TridentOptions`)
*/</span>
<span class="k">fun</span> <span class="nf">Map</span><span class="p"><</span><span class="nc">String</span><span class="p">,</span> <span class="nc">String</span><span class="p">>.</span><span class="nf">parseTridentOptions</span><span class="p">():</span> <span class="nc">TridentOptions</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">enabled</span> <span class="p">=</span> <span class="nf">booleanValue</span><span class="p">(</span><span class="nc">ENABLED</span><span class="p">.</span><span class="n">key</span><span class="p">)</span>
<span class="k">return</span> <span class="nc">TridentOptions</span><span class="p">(</span><span class="n">enabled</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Then in <code class="language-plaintext highlighter-rouge">TridentValidator</code> it becomes easy to declare and parse options to <code class="language-plaintext highlighter-rouge">TridentOptions</code>.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Map to store user defined option values
*/</span>
<span class="k">private</span> <span class="k">lateinit</span> <span class="kd">var</span> <span class="py">options</span><span class="p">:</span> <span class="nc">Map</span><span class="p"><</span><span class="nc">String</span><span class="p">,</span> <span class="nc">String</span><span class="p">></span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">initOptions</span><span class="p">(</span><span class="n">options</span><span class="p">:</span> <span class="nc">MutableMap</span><span class="p"><</span><span class="nc">String</span><span class="p">,</span> <span class="nc">String</span><span class="p">>)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="n">options</span> <span class="p">=</span> <span class="n">options</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">supportedOptions</span><span class="p">()</span> <span class="p">=</span> <span class="nc">SUPPORTED_OPTIONS</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">visitGraph</span><span class="p">(</span>
<span class="n">bindingGraph</span><span class="p">:</span> <span class="nc">BindingGraph</span><span class="p">,</span>
<span class="n">diagnosticReporter</span><span class="p">:</span> <span class="nc">DiagnosticReporter</span>
<span class="p">){</span>
<span class="kd">val</span> <span class="py">tridentOptions</span> <span class="p">=</span> <span class="n">options</span><span class="p">.</span><span class="nf">parseTridentOptions</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="n">tridentOptions</span><span class="p">.</span><span class="n">enabled</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Do validations</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>So far we have covered setting up the project, writing validation and the ability to customize behavior based on build arguments. This forms the crux of the article and the remainder of the article is about my own take on organizing the validations for better seperation with Dagger.</p>
<h3 id="organizing-the-validations">Organizing the validations</h3>
<p>In order to better oganize and scope individual validations, I decided to refactor the base setup with Dagger and introducing seperate class for each type of validation and ability to add/remove validations by using Multibindings. As a first step, define a base <code class="language-plaintext highlighter-rouge">Validator</code> class as shown below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Validator</span>
<span class="nd">@Inject</span>
<span class="k">constructor</span><span class="p">(</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">tridentOptions</span><span class="p">:</span> <span class="nc">TridentOptions</span><span class="p">,</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">validations</span><span class="p">:</span> <span class="nc">Set</span><span class="p"><</span><span class="err">@</span><span class="nc">JvmSuppressWildcards</span> <span class="nc">Validation</span><span class="p">></span>
<span class="p">)</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">doValidation</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">tridentOptions</span><span class="p">.</span><span class="n">enabled</span><span class="p">)</span> <span class="p">{</span>
<span class="n">validations</span><span class="p">.</span><span class="nf">forEach</span> <span class="p">{</span> <span class="n">validation</span> <span class="p">-></span>
<span class="n">validation</span><span class="p">.</span><span class="nf">validate</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With this we can configure Dagger to contribute <code class="language-plaintext highlighter-rouge">n</code> number of <code class="language-plaintext highlighter-rouge">Validation</code> implementations that the <code class="language-plaintext highlighter-rouge">Validator</code> can execute.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Marker interface to annotate that a class performs validation.
*/</span>
<span class="kd">interface</span> <span class="nc">Validation</span> <span class="p">{</span>
<span class="cm">/**
* The [BindingGraph] of the component for which the validation is to be performed
*/</span>
<span class="kd">val</span> <span class="py">bindingGraph</span><span class="p">:</span> <span class="nc">BindingGraph</span>
<span class="cm">/**
* The [diagnosticReporter] instance which can be used to report errors/warning to dagger.
*/</span>
<span class="kd">val</span> <span class="py">diagnosticReporter</span><span class="p">:</span> <span class="nc">DiagnosticReporter</span>
<span class="cm">/**
* Implementation of the method is expected to utilize `bindingGraph` and report any validation
* failures/concerns to `diagnosticReporter`.
*/</span>
<span class="k">fun</span> <span class="nf">validate</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">bindingGraph</code> and <code class="language-plaintext highlighter-rouge">diagnosticReporter</code> are the minimum required objects for validation hence they are part of interface. Implementations could request any other dependency from Dagger if required. Finally we configure our injector <code class="language-plaintext highlighter-rouge">TridentComponent</code> to accept these values via combination of <code class="language-plaintext highlighter-rouge">@BindsInstance</code> and <code class="language-plaintext highlighter-rouge">@Module</code> for placing these instances in the graph.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span><span class="p">(</span>
<span class="n">modules</span> <span class="p">=</span> <span class="p">[</span>
<span class="nc">TridentModule</span><span class="o">::</span><span class="k">class</span><span class="p">,</span>
<span class="nc">ValidationModule</span><span class="o">::</span><span class="k">class</span>
<span class="p">]</span>
<span class="p">)</span>
<span class="kd">interface</span> <span class="nc">TridentComponent</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">validator</span><span class="p">():</span> <span class="nc">Validator</span>
<span class="nd">@Component</span><span class="p">.</span><span class="nc">Factory</span>
<span class="kd">interface</span> <span class="nc">Factory</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">create</span><span class="p">(</span>
<span class="n">tridentModule</span><span class="p">:</span> <span class="nc">TridentModule</span><span class="p">,</span>
<span class="nd">@BindsInstance</span> <span class="n">bindingGraph</span><span class="p">:</span> <span class="nc">BindingGraph</span><span class="p">,</span>
<span class="nd">@BindsInstance</span> <span class="n">diagnosticReporter</span><span class="p">:</span> <span class="nc">DiagnosticReporter</span>
<span class="p">):</span> <span class="nc">TridentComponent</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Overall, the dependency graph looks like below (generated with <a href="https://arunkumar9t2.github.io/scabbard/">Scabbard</a>).</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/dagger-trident-component.svg#center" alt="Trident dependency graph" />
<div class="mdl-typography--caption caption"><p>Trident dependency graph</p>
</div>
</div>
</div>
<p>I also added another <code class="language-plaintext highlighter-rouge">Validation</code> that ensure primitives are not bound as-is and has a qualifier. Works for <code class="language-plaintext highlighter-rouge">Integer</code> alone for now but can be easily extended.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">PrimitivesValidation</span>
<span class="nd">@Inject</span>
<span class="k">constructor</span><span class="p">(</span>
<span class="k">override</span> <span class="kd">val</span> <span class="py">bindingGraph</span><span class="p">:</span> <span class="nc">BindingGraph</span><span class="p">,</span>
<span class="k">override</span> <span class="kd">val</span> <span class="py">diagnosticReporter</span><span class="p">:</span> <span class="nc">DiagnosticReporter</span>
<span class="p">)</span> <span class="p">:</span> <span class="nc">Validation</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">validate</span><span class="p">()</span> <span class="p">{</span>
<span class="n">bindingGraph</span><span class="p">.</span><span class="nf">bindings</span><span class="p">()</span>
<span class="p">.</span><span class="nf">filter</span> <span class="p">{</span> <span class="n">binding</span> <span class="p">-></span>
<span class="kd">val</span> <span class="py">key</span> <span class="p">=</span> <span class="n">binding</span><span class="p">.</span><span class="nf">key</span><span class="p">()</span>
<span class="n">key</span><span class="p">.</span><span class="nf">type</span><span class="p">().</span><span class="nf">toString</span><span class="p">()</span> <span class="p">==</span> <span class="nc">Integer</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">.</span><span class="n">name</span> <span class="p">&&</span> <span class="p">!</span><span class="n">key</span><span class="p">.</span><span class="nf">qualifier</span><span class="p">().</span><span class="n">isPresent</span>
<span class="p">}.</span><span class="nf">forEach</span> <span class="p">{</span> <span class="n">binding</span> <span class="p">-></span>
<span class="n">diagnosticReporter</span><span class="p">.</span><span class="nf">reportBinding</span><span class="p">(</span>
<span class="nc">WARNING</span><span class="p">,</span>
<span class="n">binding</span><span class="p">,</span>
<span class="s">"Primitives should be annotated with any qualifier"</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Connecting all together, the validation in <code class="language-plaintext highlighter-rouge">TridentValidator.visitGraph</code> becomes as shown below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">fun</span> <span class="nf">visitGraph</span><span class="p">(</span>
<span class="n">bindingGraph</span><span class="p">:</span> <span class="nc">BindingGraph</span><span class="p">,</span>
<span class="n">diagnosticReporter</span><span class="p">:</span> <span class="nc">DiagnosticReporter</span>
<span class="p">)</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">tridentModule</span> <span class="p">=</span> <span class="nc">TridentModule</span><span class="p">(</span><span class="n">types</span><span class="p">,</span> <span class="n">elements</span><span class="p">,</span> <span class="n">options</span><span class="p">.</span><span class="nf">asTridentOptions</span><span class="p">())</span>
<span class="nc">DaggerTridentComponent</span><span class="p">.</span><span class="nf">factory</span><span class="p">()</span>
<span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="n">tridentModule</span><span class="p">,</span> <span class="n">bindingGraph</span><span class="p">,</span> <span class="n">diagnosticReporter</span><span class="p">)</span>
<span class="p">.</span><span class="nf">validator</span><span class="p">()</span>
<span class="p">.</span><span class="nf">doValidation</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p>The full source of this sample is available <a href="https://github.com/arunkumar9t2/blog-resources/tree/master/dagger-spi-validations">here</a></p>
</blockquote>
<h3 id="conclusion">Conclusion</h3>
<p>In this article, Dagger SPI specifics, internal Dagger model representations, and building custom validations for Dagger dependency graph was presented in detail. Dagger SPI, although limited (only inspection allowed) is a great way to naturally extend Dagger’s compiler according to project specific and framerwork specific needs. Although only few methods of <code class="language-plaintext highlighter-rouge">DiagnosticReporter</code> were covered in this article, it is powerful and allows us to target many different types of nodes and edges. For example, it can be used to block any usage of <code class="language-plaintext highlighter-rouge">@Subcomponent</code> at all. Finally an opinionated way to organize the valdiation code better was presented.</p>
<h4 id="further-work">Further work</h4>
<p>Some of the things that I considered initially for the article were:</p>
<ul>
<li>Adding a compiler option to toggle between <code class="language-plaintext highlighter-rouge">Diagnostic.Kind.ERROR</code> or <code class="language-plaintext highlighter-rouge">Diagnostic.Kind.WARNING</code> thus allowing us to configure severity of validation.</li>
<li>Custom validation that vets any illegal dependency edge. For example, any instance in which it is forbidden to use another specific binding as a dependency.</li>
</ul>
<p>Would be glad to hear any feedback/concerns in the comments below, thanks.</p>
<p>– Arun</p>{"twitter"=>"arunkumar_9t2"}In one of recent Dagger versions, Google added support for processing internal Dagger dependency graph information as part of the dagger-spi artifact (also read SPI vs API). According to the docs, it allows us access to the same model information that Dagger internally uses and lets us add few functionality on top of Dagger’s compiler - like a plugin. Recently I used this functionality to build a tool that visualizes how the overall dependency graph of your project is structured. In this article, I plan to discuss one other functionality of the SPI artifact (validations) and explain how to consume this SPI from scratch.Introducing Scabbard: Easily visualize Dagger 2 dependency graphs2019-12-28T01:43:00+00:002019-12-28T01:43:00+00:00https://www.arunkumar.dev/introducing-scabbard-a-tool-to-visualize-dagger-2-dependency-graphs<p>I am excited to announce <a href="https://arunkumar9t2.github.io/scabbard/">Scabbard</a>, a tool to visualize Dagger 2 dependency graphs. Here’s Scabbard in action:</p>
<video width="100%" controls="">
<source src="https://arunkumar9t2.github.io/scabbard/video/scabbard-demo.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<p>Here is an advanced example from <a href="https://github.com/google/iosched">Google’s I/O 19</a> app:</p>
<video width="100%" controls="">
<source src="https://arunkumar9t2.github.io/scabbard/video/iosched_appcomponent.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<p>Apart from ensuring runtime safety, one of the advantages of a compile-time framework for dependency injection is that the entire graph of dependencies can be known at compile time even before binary is generated. This enables features like compilation errors when there are missing bindings which is a very good step towards reducing developer mistakes. But the cost we pay for that is the mental effort of knowing all the bindings, knowing current scope, navigating errors to make dagger happy etc. Very soon we realize there is a visualization problem here and it is evident from many dagger tutorial articles which tries to visualize the dagger structure in their own ways to convey the message. I tried to tackle this problem with Scabbard.</p>
<h3 id="download">Download</h3>
<p>Please follow <a href="https://arunkumar9t2.github.io/scabbard/">Getting Started</a> guide on the project website to start generating graphs right away. Hopefully you would have graphs by the time you finish reading this article.</p>
<h3 id="features">Features</h3>
<ul>
<li>
<p><strong>Visualize</strong> entry points, dependency graph, component relationships and scopes in your Dagger setup.</p>
</li>
<li>
<p><strong>Minimal setup</strong> - Scabbard’s Gradle plugin prepares your project for graph generation and provides ability to customize graph generation behavior.</p>
</li>
<li>
<p><strong>IDE integration</strong> - Easily view a <code class="language-plaintext highlighter-rouge">@Component</code> or a <code class="language-plaintext highlighter-rouge">@Subcomponnet</code> graphs directly from source code via gutter icons (IntelliJ/Android Studio).</p>
</li>
<li>
<p><strong>Supports</strong> both Kotlin and Java.</p>
</li>
</ul>
<h3 id="how-it-works">How it works</h3>
<p>In order to get to the point shown in above videos, Scabbard has 3 different components working together.</p>
<ul>
<li>
<p><strong>Scabbard Processor</strong> - A <code class="language-plaintext highlighter-rouge">BindingGraphPlugin</code> which is provided as part of <a href="https://dagger.dev/spi.html">Dagger SPI</a>. It receives callbacks from Dagger’s main compiler about graph information and is responsible for generating <code class="language-plaintext highlighter-rouge">png</code> images and <code class="language-plaintext highlighter-rouge">dot</code> files.</p>
</li>
<li>
<p><strong>Scabbard Gradle Plugin</strong> - A gradle plugin to abstract away internal implementation details required in setting up the above processor and also acts as an API to configure Scabbard behavior. Being a Gradle plugin allows it do stuff that is otherwise not possible with pure annotation processors.</p>
</li>
<li>
<p><strong>Scabbard IntelliJ Plugin</strong> - An IntelliJ plugin to link generated graphs back to their source code. This plugin also sets up stage for building any additional UI tools based on Dagger information in the future.</p>
</li>
</ul>
<h3 id="goals">Goals</h3>
<p>For Scabbard, I had the following goals in mind.</p>
<ul>
<li>Establish ability to build informational tools with Dagger graph information - as mentioned above the IDE and Gradle plugins work together with shared knowledge to provide UX improvements. This can be easily extended to build additional tools (<del>like linking <code class="language-plaintext highlighter-rouge">@Inject</code> with bindings</del> this feature is now natively <a href="https://medium.com/androiddevelopers/dagger-navigation-support-in-android-studio-49aa5d149ec9">available</a> as part of Android Studio since 4.1 Canary 07.).</li>
<li>Easy to use visualization - Scabbard aims to be simple to setup and tries to abstract away the internal implementation details involved in graph generation</li>
<li>Readable Graph information - it is very easy to end up in complicated graph diagrams which is technically correct but hard to decipher due to their complexity especially in large projects like <em>Grab’s</em>. To tackle this, Scabbard splits the graph by <code class="language-plaintext highlighter-rouge">@Component</code> and <code class="language-plaintext highlighter-rouge">@Subcomponent</code> to keep the graphs small and understandable.</li>
<li>Opionated strucure - internal dagger representation can be inferred and represented in many different ways, Scabbard aims to start with a base representation and evolve it based on community feedback.</li>
</ul>
<h3 id="tech-stack">Tech Stack</h3>
<h4 id="graphviz">GraphViz</h4>
<p><a href="https://www.graphviz.org/">GraphViz</a> is a popular tool for drawing graphs and Scabbard primarly uses it for generating images. In addition to <code class="language-plaintext highlighter-rouge">png</code> files, Scabbard generates a raw <code class="language-plaintext highlighter-rouge">dot</code> file which can be analyzed in variety of tools (<a href="https://gephi.org/">Gephi</a> etc).</p>
<h4 id="kotlin">Kotlin</h4>
<p>Project is fully written in Kotlin and it enabled me to build better APIs (DSL) allowing me to concentrate on expressing the graph cleanly instead of worrying about API specifics. Scabbard also ships a <code class="language-plaintext highlighter-rouge">dot-dsl</code> artifact for writing dot files easily with Kotlin, example:</p>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://github.com/arunkumar9t2/scabbard/blob/main/docs/images/dot-dsl.png?raw=true" alt="Dot-dsl for constructing dot files in Kotlin" width="100%" />
<div class="mdl-typography--caption caption"><p>Dot-dsl for constructing dot files in Kotlin</p>
</div>
</div>
</div>
<h3 id="cheat-sheet">Cheat sheet</h3>
<p>Below is the example <code class="language-plaintext highlighter-rouge">AppComponent</code> used as a base for building graph and its attributes (please <code class="language-plaintext highlighter-rouge">Open in new tab</code> for better quality). There is a plan to provide a better detailed cheat sheet to help in understanding the graph better, contributions welcome!</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="https://arunkumar9t2.github.io/scabbard/images/dev.arunkumar.scabbard.di.AppComponent.svg#center" alt="" />
<div class="mdl-typography--caption caption">
</div>
</div>
</div>
<h3 id="highlights">Highlights</h3>
<p>There are a lot to cover, below are some selected highlights and examples.</p>
<h4 id="interactive-graphs-with-svg">Interactive Graphs with SVG</h4>
<blockquote>
<p>Available since 0.2.0</p>
</blockquote>
<p>Scabbard utilitizes hyperlinks available as part of SVG spec and enables navigation between <code class="language-plaintext highlighter-rouge">@Component</code> and <code class="language-plaintext highlighter-rouge">@Subcomponent</code> as shown below.</p>
<video width="100%" controls="">
<source src="https://arunkumar9t2.github.io/scabbard/video/svg_sample.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<h4 id="visualizing-errors">Visualizing errors</h4>
<p>Scabbard can highlight <code class="language-plaintext highlighter-rouge">Missing Bindings</code> in red color which can be easily used to point to why the error arises. Requires <a href="https://arunkumar9t2.github.io/scabbard/configuration/#enable-full-binding-graph-validation">full binding graph validation.</a></p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="https://github.com/arunkumar9t2/scabbard/blob/main/docs/images/missing-binding.png?raw=true#center" alt="Missing bindings" />
<div class="mdl-typography--caption caption"><p>Missing bindings</p>
</div>
</div>
</div>
<h4 id="google-io-2019"><a href="https://github.com/google/iosched">Google IO 2019</a></h4>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="https://arunkumar9t2.github.io/scabbard/images/com.google.samples.apps.iosched.di.AppComponent.svg#center" alt="AppComponent" />
<div class="mdl-typography--caption caption"><p>AppComponent</p>
</div>
</div>
</div>
<h4 id="plaid"><a href="https://github.com/android/plaid">Plaid</a></h4>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="https://arunkumar9t2.github.io/scabbard/images/io.plaidapp.dagger.HomeComponent.svg#center" alt="HomeComponent" />
<div class="mdl-typography--caption caption"><p>HomeComponent</p>
</div>
</div>
</div>
<h3 id="summary">Summary</h3>
<p>Overall I am excited to share Scabbard with you and it has been challenging project with lots of stuff learnt particularly dagger internals. Hopefully it is helpful. Would love to hear feedback and iterate on this further. Please share your thoughts in the comments or <a href="https://github.com/arunkumar9t2/scabbard/issues">Github issues</a>, or on <a href="https://twitter.com/arunkumar_9t2">Twitter</a>.</p>{"twitter"=>"arunkumar_9t2"}I am excited to announce Scabbard, a tool to visualize Dagger 2 dependency graphs. Here’s Scabbard in action:Seamless Android app launch animations using Intent Source Bounds2019-05-19T17:53:00+00:002019-05-19T17:53:00+00:00https://www.arunkumar.dev/seamless-android-app-launch-animations-using-intent-sourcebounds<p>The title of this article refers to Android <a href="https://developer.android.com/reference/android/content/Intent">Intent</a> class’ field <code class="language-plaintext highlighter-rouge">mSourceBounds</code>. Surprisingly the documentation for this field is sparse and I have not seen many apps taking advantage of this field. In this article, I will attempt to decode the intention (pun intended) behind Source Bounds and provide ways it can be used in context of animations.</p>
<p>What we will build:</p>
<div style="position:relative;padding-bottom:20px;">
<iframe src="https://gfycat.com/ifr/ImpressionableRemoteLaughingthrush" frameborder="0" scrolling="no" allowfullscreen="" width="640" height="453"></iframe>
</div>
<h3 id="intentmsourcebounds">Intent.mSourceBounds</h3>
<p>As per documentation, source bounds is way for app developers to hint to the receiver of the <code class="language-plaintext highlighter-rouge">Intent</code> where the intent is originating from by defining a <code class="language-plaintext highlighter-rouge">Rect</code>.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Get the bounds of the sender of this intent, in screen coordinates. This can be
* used as a hint to the receiver for animations and the like. Null means that there
* is no source bounds.
*/</span>
<span class="kd">public</span> <span class="nd">@Nullable</span> <span class="nc">Rect</span> <span class="nf">getSourceBounds</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">mSourceBounds</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>By using the provided <code class="language-plaintext highlighter-rouge">Rect</code>, the receiver can prepare for showing its content and animations are great way to prepare content entry. Before we delve into details of what’s possible with this field, let’s discuss few examples</p>
<ul>
<li>A photo gallery app could use the field to hint the <code class="language-plaintext highlighter-rouge">Photo</code> location in a grid and <code class="language-plaintext highlighter-rouge">PhotoDetail</code> screen could use that to animate in the full image from origin in previous screen.</li>
<li>User clicks on a button that launches a new activity. The new activity could originate from where the button was placed.</li>
</ul>
<p>Astute readers might already made have comparison to <code class="language-plaintext highlighter-rouge">Shared Element Transition</code> framework which already does the above things for us. Is it not? Not exactly.</p>
<p>The shared element transition is better used for things that are <strong>shared between two screens</strong>. In the first example, <code class="language-plaintext highlighter-rouge">Photo</code> in grid and detail screens are the same contextually, whereas in the second example, the button only serves as a hint for the next activity to load. The loaded activity might not contain the button element at all. In cases like this, we just want to hint the originating location and that is where <code class="language-plaintext highlighter-rouge">SourceBounds</code> come in. Moreover, shared element transitions are confined to your app and typically not run when launched by external app eg: launched by launcher app.</p>
<h3 id="specification">Specification</h3>
<ul>
<li>Source bounds <code class="language-plaintext highlighter-rouge">Rect</code> is defined relative to screen coordinates.</li>
<li>Since it is part of <code class="language-plaintext highlighter-rouge">Intent</code> it can be accessed from any <code class="language-plaintext highlighter-rouge">Activity</code> by calling <code class="language-plaintext highlighter-rouge">intent.sourceBounds</code>.</li>
<li>When receiving external <code class="language-plaintext highlighter-rouge">Intent</code>s, it is upto to the caller to specify the source bounds so there is a possibility the source bounds could be wrong/invalid.</li>
<li>Source bounds can be <code class="language-plaintext highlighter-rouge">null</code>.</li>
</ul>
<h2 id="android-launchers">Android Launchers</h2>
<p>Like any development work, I set out to find any exsiting usage of this API to understand its usage. Turns out, there are few <a href="https://www.google.com/search?q=setsourcebounds%20site%3A%3Ahttps%3A%2F%2Fandroid.googlesource.com">references</a> in Android repository. One particular reference that caught my interest was its usage in Android <code class="language-plaintext highlighter-rouge">Launcher3</code> codebase. Basically whenever any app is launched by the Launcher, it attaches the source bounds in the <code class="language-plaintext highlighter-rouge">Intent</code> that is fired. The source bounds point to the icon location screen in both home screen or the app drawer whichever was used to launch the app. <strong>This essentially means Android apps can infer where the app icon is on home screen.</strong></p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/intent-source-bounds-launcher.png#center" alt="" />
<div class="mdl-typography--caption caption">
</div>
</div>
</div>
<p>With this information - <em>bounds of icon on home screen</em>, it becomes easy to add <strong>delightful animations</strong> to onboard users to your Android app. In the remainder of the article, we will discuss few ways to utlize this field.</p>
<h2 id="animations">Animations</h2>
<p>For app entry animations, it is important to understand existing behaviors that might cause unpleasant experiences for the end user.</p>
<ol>
<li>Android Launcher already executes an app launch animation which can conflict with custom animations</li>
<li>Android <code class="language-plaintext highlighter-rouge">WindowManager</code> controls the root layout’s bounds, background and visibility. Custom animation code should be wary of this and prepare accordingly.</li>
<li><code class="language-plaintext highlighter-rouge">Source Bounds</code> completely depends on the caller and it is possible it has wrong information.</li>
<li><code class="language-plaintext highlighter-rouge">Source Bounds</code> can be missing and app should be prepared to run without animations</li>
</ol>
<p>To overcome <code class="language-plaintext highlighter-rouge">1</code>, we can user <a href="https://developer.android.com/reference/android/app/Activity.html#overridePendingTransition(int,%20int)">overridePendingTransition</a> just before <code class="language-plaintext highlighter-rouge">super.onCreate(savedInstanceState)</code> to override the animation set by <code class="language-plaintext highlighter-rouge">Launcher</code>. For <code class="language-plaintext highlighter-rouge">2</code>, there are ways to override/controle <code class="language-plaintext highlighter-rouge">WindowManager</code> behavior, but I chose to keep this example simple. One property, we must overriding is the Window background which can be defined in <code class="language-plaintext highlighter-rouge">styles.xml</code> like below:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><style</span> <span class="na">name=</span><span class="s">"AppTheme.NoWindowBackground"</span> <span class="na">parent=</span><span class="s">"AppTheme.NoActionBar"</span><span class="nt">></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"android:windowBackground"</span><span class="nt">></span>@android:color/transparent<span class="nt"></item></span>
<span class="nt"><item</span> <span class="na">name=</span><span class="s">"android:windowIsTranslucent"</span><span class="nt">></span>true<span class="nt"></item></span>
<span class="nt"></style></span>
</code></pre></div></div>
<p>These properties allows us to have a <strong>transparent space where we can run our animations</strong> and it would appear like it is part of the launcher due to transparency.</p>
<p>For <code class="language-plaintext highlighter-rouge">3</code>, one possible way to check it simply check in the bounds are within our desired area. If we are having a hero image on screen, we could simple check <code class="language-plaintext highlighter-rouge">heroBounds.contains(sourceBounds)</code>.</p>
<p>In the 3 upcoming examples, we would require the steps above so I added these <a href="https://gist.github.com/arunkumar9t2/5f1d045beb4b787cfd1a106782f59f0e">Kotlin extensions</a> to avoid repeated code and avoided inheritance. The code should be self explanatory.</p>
<h3 id="circular-reveal-animation">Circular Reveal Animation</h3>
<p>In circular reveal sample, we will try to reveal the content of our app from the home screen icon location in a circular way just like what Lollipop introduced. To perform circular reveal:</p>
<ul>
<li>Find the relative center point <code class="language-plaintext highlighter-rouge">(centerX, centerY)</code> on our root layout which corresponds to icon on our home screen. Since source bounds correspond to screen coordinates, we should find the <a href="https://github.com/arunkumar9t2/blog-resources/blob/master/intent-source-bounds/app/src/main/java/dev/arunkumar/intentsourcebounds/util/ViewExt.kt">Rect</a> for our concerned <code class="language-plaintext highlighter-rouge">View</code> i.e root layout. Then we simply do <code class="language-plaintext highlighter-rouge">sourceBounds.centerX() - rootLayoutBounds.left</code> to calculate the center point.</li>
<li>Calculate a start and end radius - start radius can be calculated from source bounds and end radius = <code class="language-plaintext highlighter-rouge">hypot(width.toFloat(), height.toFloat())</code>.</li>
<li>Feed the above details to <code class="language-plaintext highlighter-rouge">ViewAnimationUtils.createCircularReveal</code> which will animate the view for us.</li>
</ul>
<p>In this case, the <code class="language-plaintext highlighter-rouge">rootContentLayout</code> will be the <code class="language-plaintext highlighter-rouge">CoordinatorLayout</code> with a background defined. Plugging together everything:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">private</span> <span class="k">fun</span> <span class="nf">performCircularReveal</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(!</span><span class="n">hasSourceBounds</span><span class="p">)</span> <span class="p">{</span>
<span class="n">rootContentLayout</span><span class="p">.</span><span class="n">isInvisible</span> <span class="p">=</span> <span class="k">false</span> <span class="c1">// No source bounds, simply show the layout</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">sourceBounds</span> <span class="p">{</span> <span class="n">sourceBounds</span> <span class="p">-></span>
<span class="n">rootContentLayout</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
<span class="nf">screenBounds</span> <span class="p">{</span> <span class="n">rootLayoutBounds</span> <span class="p">-></span>
<span class="c1">// Verify if sourceBounds is valid</span>
<span class="k">if</span> <span class="p">(</span><span class="n">rootLayoutBounds</span><span class="p">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">sourceBounds</span><span class="p">))</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">circle</span> <span class="p">=</span> <span class="nf">createCircularReveal</span><span class="p">(</span>
<span class="n">centerX</span> <span class="p">=</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="nf">centerX</span><span class="p">()</span> <span class="p">-</span> <span class="n">rootLayoutBounds</span><span class="p">.</span><span class="n">left</span><span class="p">,</span>
<span class="n">centerY</span> <span class="p">=</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="nf">centerY</span><span class="p">()</span> <span class="p">-</span> <span class="n">rootLayoutBounds</span><span class="p">.</span><span class="n">top</span><span class="p">,</span>
<span class="n">startRadius</span> <span class="p">=</span> <span class="p">(</span><span class="nf">minOf</span><span class="p">(</span><span class="n">sourceBounds</span><span class="p">.</span><span class="nf">width</span><span class="p">(),</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="nf">height</span><span class="p">())</span> <span class="p">*</span> <span class="mf">0.2</span><span class="p">).</span><span class="nf">toFloat</span><span class="p">(),</span>
<span class="n">endRadius</span> <span class="p">=</span> <span class="nf">hypot</span><span class="p">(</span><span class="n">width</span><span class="p">.</span><span class="nf">toFloat</span><span class="p">(),</span> <span class="n">height</span><span class="p">.</span><span class="nf">toFloat</span><span class="p">())</span>
<span class="p">).</span><span class="nf">apply</span> <span class="p">{</span>
<span class="n">isInvisible</span> <span class="p">=</span> <span class="k">false</span>
<span class="n">duration</span> <span class="p">=</span> <span class="mi">500L</span>
<span class="p">}</span>
<span class="nc">AnimatorSet</span><span class="p">()</span>
<span class="p">.</span><span class="nf">apply</span> <span class="p">{</span> <span class="nf">playTogether</span><span class="p">(</span><span class="n">circle</span><span class="p">,</span> <span class="n">statusBarAnimator</span><span class="p">,</span> <span class="n">navigationBarAnimator</span><span class="p">)</span> <span class="p">}</span>
<span class="p">.</span><span class="nf">start</span><span class="p">()</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">isInvisible</span> <span class="p">=</span> <span class="k">false</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Note that, since we control only the window of our app, we should take care of how system elements behave during animation. In this example, I chose to animate the status bar color from transparent to current status bar color to avoid snapping.</p>
<div style="position:relative;padding-bottom:20px;">
<iframe src="https://gfycat.com/ifr/TastyDazzlingIndigobunting" frameborder="0" scrolling="no" allowfullscreen="" width="640" height="453"></iframe>
</div>
<p>The same animation in slow motion:</p>
<div style="position:relative;padding-bottom:20px;">
<iframe src="https://gfycat.com/ifr/TestyGiantCreature" frameborder="0" scrolling="no" allowfullscreen="" width="640" height="453"></iframe>
</div>
<p>The full source of the above animation can be found <a href="https://github.com/arunkumar9t2/blog-resources/blob/master/intent-source-bounds/app/src/main/java/dev/arunkumar/intentsourcebounds/CircularRevealActivity.kt">here</a>.</p>
<h3 id="material-transform">Material Transform</h3>
<p>For material transform, we imagine our app icon to be the initial bounds of our entire root layout and during animation we transform the bounds from app icon to end root bounds. Recently, I started advocating for <code class="language-plaintext highlighter-rouge">TransitionManager</code> for choreographing transition and wrote <a href="https://github.com/arunkumar9t2/transition-x">transition-x</a>, a Kotlin DSL that helps to write <code class="language-plaintext highlighter-rouge">TransitionSet</code> in a declarative, type-safe way. When using <code class="language-plaintext highlighter-rouge">TransitionManager</code>, it is only required to think about the start state and the end state. Once we have this, we can choreograph the animation using <code class="language-plaintext highlighter-rouge">Transition</code> framework. In the following example, I will be using <code class="language-plaintext highlighter-rouge">transition-x</code> but it should be very easy to decode it to normal <code class="language-plaintext highlighter-rouge">TransitionSet</code> code.</p>
<p><strong>Another important note for using <code class="language-plaintext highlighter-rouge">TransitionManager</code> is performance</strong>. Transition framework uses a private API called <code class="language-plaintext highlighter-rouge">suppressLayout</code> which stops layout passes until animation is done. This is critical for performance and that is why I prefer Transitions instead of <code class="language-plaintext highlighter-rouge">ObjectAnimator</code>s.</p>
<ul>
<li><strong>Start state</strong>: Content layout appears where <code class="language-plaintext highlighter-rouge">sourceBounds</code> is defined.</li>
<li><strong>End state</strong>: Default layout how we defined in XML.</li>
</ul>
<h4 id="start-state">Start State</h4>
<p>To apply the start state, I first validate if the <code class="language-plaintext highlighter-rouge">sourceBounds</code> is valid and then update <code class="language-plaintext highlighter-rouge">rootLayout</code>s, position and size like below:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">rootLayout</span><span class="p">.</span><span class="n">updateLayoutParams</span><span class="p"><</span><span class="nc">FrameLayout</span><span class="p">.</span><span class="nc">LayoutParams</span><span class="p">></span> <span class="p">{</span>
<span class="n">width</span> <span class="p">=</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="nf">width</span><span class="p">()</span>
<span class="n">height</span> <span class="p">=</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="nf">height</span><span class="p">()</span>
<span class="n">leftMargin</span> <span class="p">=</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="n">left</span>
<span class="n">topMargin</span> <span class="p">=</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="n">top</span>
<span class="p">}</span>
<span class="n">rootLayout</span><span class="p">.</span><span class="n">isVisible</span> <span class="p">=</span> <span class="k">true</span>
</code></pre></div></div>
<p>In the first frame, the root layout just sits there taking the size of the homescreen icon.</p>
<h4 id="end-state">End State</h4>
<p>For the end state, I simply reverse the start state modifying code and let <code class="language-plaintext highlighter-rouge">TransitionManger</code> and android layouts take care of animation. One detail to note is use of <code class="language-plaintext highlighter-rouge">post {}</code>, we want <code class="language-plaintext highlighter-rouge">TransitionManager</code> to compute the difference between <code class="language-plaintext highlighter-rouge">start</code> and <code class="language-plaintext highlighter-rouge">end</code> state, so by <code class="language-plaintext highlighter-rouge">post</code>ing, we execute in the next frame and then call <code class="language-plaintext highlighter-rouge">rootLayout.prepareTransition</code> which simply is an alias for <code class="language-plaintext highlighter-rouge">TransitionManager.beginDelayedTransition</code>.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">rootContentLayout</span><span class="p">.</span><span class="nf">post</span> <span class="p">{</span>
<span class="n">rootLayout</span><span class="p">.</span><span class="nf">prepareTransition</span> <span class="p">{</span>
<span class="nf">auto</span> <span class="p">{</span> <span class="c1">// AutoTransition</span>
<span class="nf">ease</span> <span class="p">{</span>
<span class="n">standardEasing</span> <span class="c1">// FastOutSlowInInterpolator</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">updateLayoutParams</span><span class="p"><</span><span class="nc">FrameLayout</span><span class="p">.</span><span class="nc">LayoutParams</span><span class="p">></span> <span class="p">{</span>
<span class="n">width</span> <span class="p">=</span> <span class="nc">FrameLayout</span><span class="p">.</span><span class="nc">LayoutParams</span><span class="p">.</span><span class="nc">MATCH_PARENT</span>
<span class="n">height</span> <span class="p">=</span> <span class="nc">FrameLayout</span><span class="p">.</span><span class="nc">LayoutParams</span><span class="p">.</span><span class="nc">MATCH_PARENT</span>
<span class="n">leftMargin</span> <span class="p">=</span> <span class="mi">0</span>
<span class="n">topMargin</span> <span class="p">=</span> <span class="mi">0</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h4 id="animation">Animation</h4>
<p>Combining everything, the final code:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">fun</span> <span class="nf">performMaterialTransform</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(!</span><span class="n">hasSourceBounds</span><span class="p">)</span> <span class="p">{</span>
<span class="n">rootContentLayout</span><span class="p">.</span><span class="n">isInvisible</span> <span class="p">=</span> <span class="k">false</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">sourceBounds</span> <span class="p">{</span> <span class="n">sourceBounds</span> <span class="p">-></span>
<span class="n">rootContentLayout</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
<span class="nf">screenBounds</span> <span class="p">{</span> <span class="n">layoutBounds</span> <span class="p">-></span>
<span class="k">if</span> <span class="p">(</span><span class="n">layoutBounds</span><span class="p">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">sourceBounds</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// Apply source bounds dimensions to target</span>
<span class="n">updateLayoutParams</span><span class="p"><</span><span class="nc">FrameLayout</span><span class="p">.</span><span class="nc">LayoutParams</span><span class="p">></span> <span class="p">{</span>
<span class="n">width</span> <span class="p">=</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="nf">width</span><span class="p">()</span>
<span class="n">height</span> <span class="p">=</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="nf">height</span><span class="p">()</span>
<span class="n">leftMargin</span> <span class="p">=</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="n">left</span>
<span class="n">topMargin</span> <span class="p">=</span> <span class="n">sourceBounds</span><span class="p">.</span><span class="n">top</span>
<span class="p">}</span>
<span class="n">isVisible</span> <span class="p">=</span> <span class="k">true</span>
<span class="nf">post</span> <span class="p">{</span>
<span class="n">rootLayout</span><span class="p">.</span><span class="nf">prepareTransition</span> <span class="p">{</span>
<span class="nf">auto</span> <span class="p">{</span>
<span class="nf">ease</span> <span class="p">{</span>
<span class="n">standardEasing</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">updateLayoutParams</span><span class="p"><</span><span class="nc">FrameLayout</span><span class="p">.</span><span class="nc">LayoutParams</span><span class="p">></span> <span class="p">{</span>
<span class="n">width</span> <span class="p">=</span> <span class="nc">FrameLayout</span><span class="p">.</span><span class="nc">LayoutParams</span><span class="p">.</span><span class="nc">MATCH_PARENT</span>
<span class="n">height</span> <span class="p">=</span> <span class="nc">FrameLayout</span><span class="p">.</span><span class="nc">LayoutParams</span><span class="p">.</span><span class="nc">MATCH_PARENT</span>
<span class="n">leftMargin</span> <span class="p">=</span> <span class="mi">0</span>
<span class="n">topMargin</span> <span class="p">=</span> <span class="mi">0</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">rootContentLayout</span><span class="p">.</span><span class="n">isInvisible</span> <span class="p">=</span> <span class="k">false</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And the result:</p>
<div style="position:relative;padding-bottom:20px;">
<iframe src="https://gfycat.com/ifr/BlueTightKawala" frameborder="0" scrolling="no" allowfullscreen="" width="640" height="453"></iframe>
</div>
<h3 id="hero-transform">Hero Transform</h3>
<p>Hero transform is similar to Shared Element <code class="language-plaintext highlighter-rouge">photo</code> transition example discussed at the beginning of the article. The home layout defined below has app icon branding by using source bounds, we can have a hero animation and fade in the rest of the content(textViews).</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/intent-source-bounds-hero.png#center" alt="" />
<div class="mdl-typography--caption caption">
</div>
</div>
</div>
<p>Like material transform above, I will continue using <code class="language-plaintext highlighter-rouge">TransitionManager</code> to pull off the hero transition. The start state is similar and is left as an excercise for the reader.</p>
<h4 id="end-state-1">End State</h4>
<p>Defining end state is simple, we take advantage of <a href="https://developer.android.com/reference/android/support/constraint/ConstraintSet">ConstraintSet</a>’s <code class="language-plaintext highlighter-rouge">applyTo</code> method and reapply the home screen constraints again which reverts the changes made by start state. Then the only remaining part is transition choreography. <code class="language-plaintext highlighter-rouge">AutoTransition</code> does not help here as it by default has a order (disappear, move, appear) and uses <code class="language-plaintext highlighter-rouge">Fade</code> for animating <code class="language-plaintext highlighter-rouge">visibility</code>.</p>
<p>Transition steps</p>
<ul>
<li><strong>App icon hero transition</strong>: The icon moves from <code class="language-plaintext highlighter-rouge">sourceBounds</code> location to its default location (<code class="language-plaintext highlighter-rouge">center|top</code>). <a href="https://developer.android.com/reference/androidx/transition/ChangeBounds?hl=en">ChangeBounds</a> transition can help here and <code class="language-plaintext highlighter-rouge">moveResize {}</code> is the <code class="language-plaintext highlighter-rouge">transition-x</code> equivalent. To add material touch, we can set <a href="https://developer.android.com/reference/android/transition/ArcMotion">ArcMotion</a> in the <a href="https://developer.android.com/reference/android/transition/PathMotion.html">PathMotion</a> property which curves the movement a bit.</li>
<li><strong>Texts</strong>: The text items below simply change visibility. <a href="https://developer.android.com/reference/android/support/transition/Slide?hl=en">Slide</a> is great for this and slides in content from bottom. By setting targets, we make slide only affect the texts.</li>
<li><strong>Background</strong>: The background color changes from <code class="language-plaintext highlighter-rouge">transparent</code> to <code class="language-plaintext highlighter-rouge">white</code>. We can use <a href="https://arunkumar9t2.github.io/transition-x/transitionx/in.arunkumarsampath.transitionx.transition.changecolor/-change-color/index.html">ChangeColor</a> included with <code class="language-plaintext highlighter-rouge">transition-x</code> to animate the color changes.</li>
</ul>
<p>Putting the above code together we have:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">heroTransformRootLayout</span><span class="p">.</span><span class="nf">prepareTransition</span> <span class="p">{</span>
<span class="nf">changeColor</span> <span class="p">{</span>
<span class="p">+</span><span class="n">heroTransformContentLayout</span>
<span class="p">}</span>
<span class="nf">slide</span> <span class="p">{</span>
<span class="p">+</span><span class="n">heroTitle</span>
<span class="p">+</span><span class="n">heroContent</span>
<span class="p">}</span>
<span class="nf">moveResize</span> <span class="p">{</span>
<span class="n">pathMotion</span> <span class="p">=</span> <span class="nc">ArcMotion</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">defaultHeroConstraints</span><span class="p">.</span><span class="nf">applyTo</span><span class="p">(</span><span class="n">heroTransformContentLayout</span><span class="p">)</span>
<span class="nf">layoutDefaults</span><span class="p">()</span>
</code></pre></div></div>
<div style="position:relative;padding-bottom:20px;">
<iframe src="https://gfycat.com/ifr/FarUnnaturalBlackfish" frameborder="0" scrolling="no" allowfullscreen="" width="640" height="453"></iframe>
</div>
<p>The full code for the above can be found <a href="https://github.com/arunkumar9t2/blog-resources/blob/master/intent-source-bounds/app/src/main/java/dev/arunkumar/intentsourcebounds/HeroTransformActivity.kt">here</a>:</p>
<h2 id="practical-example">Practical Example</h2>
<p>The motivation for this article came from working on one of my side project app: <a href="https://play.google.com/store/apps/details?id=com.arun.t9dailer">T9 App Launcher</a>. The app shows a dialog like screen to launch other apps with T9 Sequence. Since conceptually it is closely tied to the system launcher, it made sense to reduce the gap between the launcher and my app and seamless transition was one way. Currently the store version does the circular reveal transition:</p>
<div style="position:relative;padding-bottom:20px;">
<iframe src="https://gfycat.com/ifr/OblongFriendlyCreature" frameborder="0" scrolling="no" allowfullscreen="" width="640" height="453"></iframe>
</div>
<h2 id="bonus">Bonus</h2>
<p>One another neat side effect of this implementation is custom launchers like <a href="https://play.google.com/store/apps/details?id=com.teslacoilsw.launcher">Nova Launcher</a> send the <code class="language-plaintext highlighter-rouge">Source Bounds</code> information for gestures too. I have configured T9 to launch whenver I swipe up on an empty space in my home screen and <strong><code class="language-plaintext highlighter-rouge">Nova Launcher</code> sends the location where my finger was lifted up</strong>. This makes T9 launch from where I lifted my finger and becomes a cool effect.</p>
<div style="position:relative;padding-bottom:20px;">
<iframe src="https://gfycat.com/ifr/ShyRaggedFinwhale" frameborder="0" scrolling="no" allowfullscreen="" width="640" height="453"></iframe>
</div>
<h2 id="considerations">Considerations</h2>
<p>If your app launches any other app in some instance, then sending <code class="language-plaintext highlighter-rouge">sourceBounds</code> to that app can help. To do this, I have written a Kotlin extension that gives the <code class="language-plaintext highlighter-rouge">sourceBounds</code> from the clicked <code class="language-plaintext highlighter-rouge">View</code> just like <code class="language-plaintext highlighter-rouge">Intent</code> expects. Make your app a good Android citizen!</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* Computes a [Rect] defining the location of this [View] in terms of screen coordinates
*
* Note: The view must be laid out before calling this else the returned [Rect] might not be valid
*/</span>
<span class="k">private</span> <span class="k">fun</span> <span class="nc">View</span><span class="p">.</span><span class="nf">computeScreenBounds</span><span class="p">():</span> <span class="nc">Rect</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">viewLocation</span> <span class="p">=</span> <span class="nc">IntArray</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="nf">apply</span> <span class="p">{</span> <span class="nf">getLocationOnScreen</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">}</span>
<span class="kd">val</span> <span class="py">contentX</span> <span class="p">=</span> <span class="n">viewLocation</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="kd">val</span> <span class="py">contentY</span> <span class="p">=</span> <span class="n">viewLocation</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">return</span> <span class="nc">Rect</span><span class="p">(</span>
<span class="n">contentX</span><span class="p">,</span>
<span class="n">contentY</span><span class="p">,</span>
<span class="n">contentX</span> <span class="p">+</span> <span class="n">width</span><span class="p">,</span>
<span class="n">contentY</span> <span class="p">+</span> <span class="n">height</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="cm">/**
* Computes a [Rect] defining the location of this [View] and invokes [action] with the computed bounds when available
*/</span>
<span class="k">fun</span> <span class="nc">View</span><span class="p">.</span><span class="nf">screenBounds</span><span class="p">(</span><span class="n">action</span><span class="p">:</span> <span class="p">(</span><span class="nc">Rect</span><span class="p">)</span> <span class="p">-></span> <span class="nc">Unit</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(!</span><span class="nc">ViewCompat</span><span class="p">.</span><span class="nf">isLaidOut</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">&&</span> <span class="p">!</span><span class="n">isLayoutRequested</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">action</span><span class="p">(</span><span class="nf">computeScreenBounds</span><span class="p">())</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">doOnNextLayout</span> <span class="p">{</span>
<span class="nf">action</span><span class="p">(</span><span class="nf">computeScreenBounds</span><span class="p">())</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="summary">Summary</h2>
<p>In this article, <code class="language-plaintext highlighter-rouge">sourceBounds</code> from <code class="language-plaintext highlighter-rouge">Intent</code> was introduced and was explored in context of animations. Inferring home screen icon location for animations opens up new ways to innovatively welcome users on to your app. I believe small UX details like can help in differentiating your app. Although not a core focus, <code class="language-plaintext highlighter-rouge">TransitionManager</code> for choreographing perfomant animations was shown and it proves to be simpler and concise way to animate changes instead of dealing with many object animators. Using source bounds for entry animations can scale well in the future since the app only only relies on the location. For example, if Android OS is updated to include hardware button location when app is launched via button, then app automatically respects it and shows a nice animation originating from the hardware button.</p>
<p>The sample application source code can be found <a href="https://github.com/arunkumar9t2/blog-resources/tree/master/intent-source-bounds">here</a>.</p>
<p>Do you have any other idea for using sourceBounds or any feedback on the content? Please let me know in the comments below.</p>
<p>– Arun</p>{"twitter"=>"arunkumar_9t2"}The title of this article refers to Android Intent class’ field mSourceBounds. Surprisingly the documentation for this field is sparse and I have not seen many apps taking advantage of this field. In this article, I will attempt to decode the intention (pun intended) behind Source Bounds and provide ways it can be used in context of animations.Dagger Recipes: Illustrative step by step guide to achieve constructor injection in WorkManager2018-12-20T16:44:00+00:002018-12-20T16:44:00+00:00https://www.arunkumar.dev/dagger-recipes-illustrative-step-by-step-guide-to-achieve-constructor-injection-in-workmanager<p>Recently, while working on an app, I decided to try out Jetpack’s WorkManager for all background jobs (it’s pretty great :+1:). The app already uses Dagger 2 for DI and I wanted to carry that over to WorkManager as well when executing jobs.</p>
<p>You might already know, Android system components are not constructor injectable prior to Pie and currently available solutions use <code class="language-plaintext highlighter-rouge">Members injection</code> and there are variatons in it.</p>
<ul>
<li>The first is accessing your injector component from <code class="language-plaintext highlighter-rouge">Application</code> class and then calling <code class="language-plaintext highlighter-rouge">component.inject(this)</code> which would do a member injection. This approach breaks DI best pratices - the class getting injected should not know its injector and it makes it difficult to access this <code class="language-plaintext highlighter-rouge">Activity</code> in a modular project.</li>
<li>The second is using <code class="language-plaintext highlighter-rouge">dagger.android</code> and <code class="language-plaintext highlighter-rouge">Multibinding</code>s to implement a factory + service locator apporach to get the dependencies runtime. Injectable android components only needed to do <code class="language-plaintext highlighter-rouge">AndroidInjection.inject(this)</code>. I initially avoided this approach (lack of good documentation and unknowns), but have learnt to live with it especially in a modular project and this is the goto approach I take.</li>
</ul>
<h3 id="why-constructor-injection">Why constructor injection?</h3>
<p>One pratice I follow after setting up Dagger is everything apart from Android components should be constructor injected. This means once the class is instantiated the dependencies are already resolved, there is no waiting for a lifecycle event to do member injection and you can reduce stateful code by making the dependency immutable i.e <code class="language-plaintext highlighter-rouge">val</code>. This approach has few benefits</p>
<ul>
<li>Reduced stateful code in resolving dependencies.</li>
<li>Improves readability - exactly know what dependencies the class wants.</li>
<li>No dependency on the injector itself - constuctor injected class can exist in different components. In a isolated module for example, a <code class="language-plaintext highlighter-rouge">TestComponent</code> can be used to provide dependecies for testing and during usage, the dependencies can provided by the app component.</li>
</ul>
<h4 id="about-this-article">About this article</h4>
<p>With above in mind, in this article, I will <em>walk you through my thought process</em> of how I acheived construction injection in <code class="language-plaintext highlighter-rouge">WorkManager</code>. <strong>I believe knowing to walk through dagger errors and then configuring it to successfull compile using various options dagger provides is a helpful skill to have.</strong> To do this, I will start with a <strong>failing build</strong> and then step through ways to help dagger help us.</p>
<h3 id="what-we-will-build">What we will build</h3>
<p>We will have a simple app with a single <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code>. The app will use <code class="language-plaintext highlighter-rouge">Repository</code> pattern in the data layer and Dagger is setup to inject repositories. The <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code> has a dependency on <code class="language-plaintext highlighter-rouge">WordsRepository</code>. We will concentrate on injecting <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code>.</p>
<p>The initial failing setup can be found in this <a href="https://github.com/arunkumar9t2/dagger-workmanager/tree/bd5f6787261f1e0cc3fa4c17900f1063db62c916">commit</a>.</p>
<p>Concerned <code class="language-plaintext highlighter-rouge">Worker</code> subclass what we would like to inject:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">HelloWorldWorker</span>
<span class="nd">@Inject</span>
<span class="k">constructor</span><span class="p">(</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">application</span><span class="p">:</span> <span class="nc">Application</span><span class="p">,</span>
<span class="n">workerParameters</span><span class="p">:</span> <span class="nc">WorkerParameters</span><span class="p">,</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">wordsRepository</span><span class="p">:</span> <span class="nc">WordsRepository</span> <span class="c1">// App dependency</span>
<span class="p">)</span> <span class="p">:</span> <span class="nc">RxWorker</span><span class="p">(</span><span class="n">application</span><span class="p">,</span> <span class="n">workerParameters</span><span class="p">)</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">createWork</span><span class="p">():</span> <span class="nc">Single</span><span class="p"><</span><span class="nc">Result</span><span class="p">></span> <span class="p">{</span>
<span class="k">return</span> <span class="n">wordsRepository</span><span class="p">.</span><span class="nf">sayHelloWorld</span><span class="p">()</span>
<span class="p">.</span><span class="nf">doOnSuccess</span> <span class="p">{</span> <span class="n">message</span> <span class="p">-></span>
<span class="c1">// Toasts are bad, don't use it.</span>
<span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="n">application</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span>
<span class="p">}.</span><span class="nf">map</span> <span class="p">{</span> <span class="nc">Result</span><span class="p">.</span><span class="nf">success</span><span class="p">()</span> <span class="p">}</span>
<span class="p">.</span><span class="nf">onErrorReturnItem</span><span class="p">(</span><span class="nc">Result</span><span class="p">.</span><span class="nf">failure</span><span class="p">())</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In the remainder of the article, we will see how this class can be initialized and make it compatible with <code class="language-plaintext highlighter-rouge">WorkManager</code>.</p>
<h3 id="missing-pieces">Missing pieces</h3>
<p><code class="language-plaintext highlighter-rouge">HelloWorldWorker</code>’s constructor is marked with <code class="language-plaintext highlighter-rouge">@Inject</code>, we are letting Dagger know that this <code class="language-plaintext highlighter-rouge">Worker</code> participates in DI. Since this class is supposed to be initialized by <code class="language-plaintext highlighter-rouge">WorkManager</code>, we don’t actually request an instance from Dagger but instead <code class="language-plaintext highlighter-rouge">WorkManager</code> does. When we run the app and try to start the <code class="language-plaintext highlighter-rouge">Worker</code> we get:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Could</span> <span class="n">not</span> <span class="n">instantiate</span> <span class="n">in</span><span class="o">.</span><span class="na">arunkumarsampath</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">workmanager</span><span class="o">.</span><span class="na">jobs</span><span class="o">.</span><span class="na">HelloWorldWorker</span>
<span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">NoSuchMethodException</span><span class="o">:</span> <span class="o"><</span><span class="n">init</span><span class="o">></span> <span class="o">[</span><span class="kd">class</span> <span class="nc">android</span><span class="o">.</span><span class="na">content</span><span class="o">.</span><span class="na">Context</span><span class="o">,</span> <span class="kd">class</span> <span class="nc">androidx</span><span class="o">.</span><span class="na">work</span><span class="o">.</span><span class="na">WorkerParameters</span><span class="o">]</span>
<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">Class</span><span class="o">.</span><span class="na">getConstructor0</span><span class="o">(</span><span class="nc">Class</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">2320</span><span class="o">)</span>
<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">Class</span><span class="o">.</span><span class="na">getDeclaredConstructor</span><span class="o">(</span><span class="nc">Class</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">2166</span><span class="o">)</span>
<span class="n">at</span> <span class="n">androidx</span><span class="o">.</span><span class="na">work</span><span class="o">.</span><span class="na">WorkerFactory</span><span class="o">.</span><span class="na">createWorkerWithDefaultFallback</span><span class="o">(</span><span class="nc">WorkerFactory</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">91</span><span class="o">)</span>
<span class="n">at</span> <span class="n">androidx</span><span class="o">.</span><span class="na">work</span><span class="o">.</span><span class="na">impl</span><span class="o">.</span><span class="na">WorkerWrapper</span><span class="o">.</span><span class="na">runWorker</span><span class="o">(</span><span class="nc">WorkerWrapper</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">191</span><span class="o">)</span>
<span class="n">at</span> <span class="n">androidx</span><span class="o">.</span><span class="na">work</span><span class="o">.</span><span class="na">impl</span><span class="o">.</span><span class="na">WorkerWrapper</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">WorkerWrapper</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">125</span><span class="o">)</span>
<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">concurrent</span><span class="o">.</span><span class="na">ThreadPoolExecutor</span><span class="o">.</span><span class="na">runWorker</span><span class="o">(</span><span class="nc">ThreadPoolExecutor</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">1162</span><span class="o">)</span>
<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">concurrent</span><span class="o">.</span><span class="na">ThreadPoolExecutor</span><span class="n">$Worker</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">ThreadPoolExecutor</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">636</span><span class="o">)</span>
<span class="n">at</span> <span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">Thread</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">Thread</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">764</span><span class="o">)</span>
</code></pre></div></div>
<p>WorkManager here tries to instantiate <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code> with two params <code class="language-plaintext highlighter-rouge">context</code> and <code class="language-plaintext highlighter-rouge">workerParameters</code>, but we do have a third one <code class="language-plaintext highlighter-rouge">wordsRepository</code>. Before we try to fix the params issue, let’s see if the graph is complete. Dagger, while processing recursively tries to satisfy all dependencies (the ones you <em>provide</em> - more on this later). To force Dagger to check if <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code>’s dependencies can be resolved, let’s request it directly in <code class="language-plaintext highlighter-rouge">HomeActivity</code>.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">HomeActivity</span> <span class="p">:</span> <span class="nc">DaggerAppCompatActivity</span><span class="p">()</span> <span class="p">{</span>
<span class="nd">@Inject</span>
<span class="k">lateinit</span> <span class="kd">var</span> <span class="py">helloWorldWorker</span><span class="p">:</span> <span class="nc">HelloWorldWorker</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span>
<span class="nf">setupUi</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now when we try to build the app, we get a <strong>compile</strong> time error:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span><span class="nc">Dagger</span><span class="o">/</span><span class="nc">MissingBinding</span><span class="o">]</span> <span class="n">androidx</span><span class="o">.</span><span class="na">work</span><span class="o">.</span><span class="na">WorkerParameters</span> <span class="n">cannot</span> <span class="n">be</span> <span class="n">provided</span> <span class="n">without</span> <span class="n">an</span> <span class="nd">@Inject</span> <span class="n">constructor</span> <span class="n">or</span> <span class="n">an</span> <span class="nd">@Provides</span><span class="o">-</span><span class="n">annotated</span> <span class="n">method</span><span class="o">.</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">interface</span> <span class="nc">AppComponent</span> <span class="kd">extends</span> <span class="n">dagger</span><span class="o">.</span><span class="na">android</span><span class="o">.</span><span class="na">AndroidInjector</span><span class="o"><</span><span class="n">in</span><span class="o">.</span><span class="na">arunkumarsampath</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">workmanager</span><span class="o">.</span><span class="na">WorkManagerApp</span><span class="o">></span> <span class="o">{</span>
<span class="o">^</span>
<span class="n">androidx</span><span class="o">.</span><span class="na">work</span><span class="o">.</span><span class="na">WorkerParameters</span> <span class="n">is</span> <span class="n">injected</span> <span class="n">at</span>
<span class="n">in</span><span class="o">.</span><span class="na">arunkumarsampath</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">workmanager</span><span class="o">.</span><span class="na">jobs</span><span class="o">.</span><span class="na">HelloWorldWorker</span><span class="o">(</span><span class="err">…</span><span class="o">,</span> <span class="n">workerParameters</span><span class="o">,</span> <span class="err">…</span><span class="o">)</span>
<span class="n">in</span><span class="o">.</span><span class="na">arunkumarsampath</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">workmanager</span><span class="o">.</span><span class="na">jobs</span><span class="o">.</span><span class="na">HelloWorldWorker</span> <span class="n">is</span> <span class="n">injected</span> <span class="n">at</span>
<span class="n">in</span><span class="o">.</span><span class="na">arunkumarsampath</span><span class="o">.</span><span class="na">dagger</span><span class="o">.</span><span class="na">workmanager</span><span class="o">.</span><span class="na">home</span><span class="o">.</span><span class="na">HomeActivity</span><span class="o">.</span><span class="na">helloWorldWorker</span>
<span class="o">...</span> <span class="c1">// left for brevity</span>
</code></pre></div></div>
<p>Looks like Dagger cannot find <code class="language-plaintext highlighter-rouge">WorkerParameters</code> instance but it did not complain about <code class="language-plaintext highlighter-rouge">application</code> or <code class="language-plaintext highlighter-rouge">wordsRepository</code>. The remaining missing piece is how to <strong>expose</strong> <code class="language-plaintext highlighter-rouge">WorkerParameters</code> at compile time and then letting <code class="language-plaintext highlighter-rouge">WorkerManager</code> use it? Let’s visualize this below.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/dagger-workmanager-1.png#center" alt="Missing binding for WorkerParameters" />
<div class="mdl-typography--caption caption"><p>Missing binding for WorkerParameters</p>
</div>
</div>
</div>
<h3 id="workerfactory">WorkerFactory</h3>
<p>Since one of the recent releases, Google added something called <code class="language-plaintext highlighter-rouge">WorkerFactory</code> whose implementation is used to instantiate <code class="language-plaintext highlighter-rouge">Worker</code>s. It is possible to provide a custom isntance via <code class="language-plaintext highlighter-rouge">WorkManager.initialize</code>. <code class="language-plaintext highlighter-rouge">WorkerFactory</code> has <code class="language-plaintext highlighter-rouge">createWorker</code> method which has all the information we need.</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="nd">@Nullable</span> <span class="nc">ListenableWorker</span> <span class="nf">createWorker</span><span class="o">(</span>
<span class="nd">@NonNull</span> <span class="nc">Context</span> <span class="n">appContext</span><span class="o">,</span>
<span class="nd">@NonNull</span> <span class="nc">String</span> <span class="n">workerClassName</span><span class="o">,</span>
<span class="nd">@NonNull</span> <span class="nc">WorkerParameters</span> <span class="n">workerParameters</span><span class="o">);</span>
</code></pre></div></div>
<p>Before we discuss about exposing <code class="language-plaintext highlighter-rouge">workerParameters</code> as a binding, let’s get the setting custom <code class="language-plaintext highlighter-rouge">WorkerFactory</code> instance out of the way. WorkManager uses a <code class="language-plaintext highlighter-rouge">ContentProvider</code> by default to intialize itself (that’s how it gets <code class="language-plaintext highlighter-rouge">application</code> context). We can disable this by using <code class="language-plaintext highlighter-rouge">tools:node="remove"</code> in AndroidManifest.</p>
<h4 id="setting-custom-workerfactory-instances">Setting custom WorkerFactory instances</h4>
<ul>
<li>Remove content provider initializer by adding the below in <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code></li>
</ul>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><provider</span> <span class="na">android:name=</span><span class="s">"androidx.work.impl.WorkManagerInitializer"</span>
<span class="na">android:authorities=</span><span class="s">"${applicationId}.workmanager-init"</span>
<span class="na">android:exported=</span><span class="s">"false"</span>
<span class="na">tools:node=</span><span class="s">"remove"</span> <span class="nt">/></span>
</code></pre></div></div>
<ul>
<li>Initialize WorkManager with custom <code class="language-plaintext highlighter-rouge">WorkerFactory</code> in <code class="language-plaintext highlighter-rouge">Application.onCreate</code>.</li>
</ul>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">WorkManager</span><span class="p">.</span><span class="nf">initialize</span><span class="p">(</span>
<span class="n">application</span><span class="p">,</span>
<span class="nc">Configuration</span><span class="p">.</span><span class="nc">Builder</span><span class="p">().</span><span class="nf">run</span> <span class="p">{</span>
<span class="nf">setWorkerFactory</span><span class="p">(</span><span class="n">workerFactory</span><span class="p">)</span> <span class="c1">// Pass custom worker factory instance</span>
<span class="nf">build</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">)</span>
</code></pre></div></div>
<p>Now writing a custom <code class="language-plaintext highlighter-rouge">WorkerFactory</code> and exposing <code class="language-plaintext highlighter-rouge">workerParameter</code>s binding from there is pending, visualizing below.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/dagger-workmanager-2.png#center" alt="How to expose WorkerParameters received in createWorker as Dagger Binding?" />
<div class="mdl-typography--caption caption"><p>How to expose WorkerParameters received in createWorker as Dagger Binding?</p>
</div>
</div>
</div>
<h3 id="exposing-workerparameters">Exposing WorkerParameters</h3>
<h4 id="dagger-bindings-refresher">Dagger bindings refresher</h4>
<p>First, I’d like to do a refresher on various options we have to <code class="language-plaintext highlighter-rouge">expose</code> bindings to Dagger. For classes we don’t own the constructor, we have to guide Dagger ourselves and that is where <code class="language-plaintext highlighter-rouge">@Module</code> comes in. We typically write methods in <code class="language-plaintext highlighter-rouge">@Module</code> annotated classes and return the <code class="language-plaintext highlighter-rouge">Type</code> we want exposed. This way when this <code class="language-plaintext highlighter-rouge">Type</code> is requested, Dagger uses the method we wrote. In order to not repeat information, I suggest reading this excellent <a href="https://medium.com/@Zhuinden/that-missing-guide-how-to-use-dagger2-ef116fbea97">article</a> by Gabor Varadi.</p>
<h4 id="enter-subcomponents">Enter Subcomponents</h4>
<p>Above we notice that dagger is able to find <code class="language-plaintext highlighter-rouge">Application</code> instance when we injected <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code>. If we look at the <a href="https://github.com/arunkumar9t2/dagger-workmanager/blob/bd5f6787261f1e0cc3fa4c17900f1063db62c916/app/src/main/java/in/arunkumarsampath/dagger/workmanager/di/app/AppComponent.kt#L29">source</a>, while building our root component (<code class="language-plaintext highlighter-rouge">AppComponent</code>), it is possible to pass in parameters using <code class="language-plaintext highlighter-rouge">@BindsInstance</code>. <code class="language-plaintext highlighter-rouge">application</code> is the parameter in this case. One crucial detail is that, <strong>types passed in with <code class="language-plaintext highlighter-rouge">@BindsInstance</code> are exposed as binding to the entire graph</strong>.</p>
<p>Since <code class="language-plaintext highlighter-rouge">Application</code> is known only at runtime, we use <code class="language-plaintext highlighter-rouge">@BindsInstance</code> to pass the created instance which then becomes available to the whole graph. During compile time validation, the <strong>checks pass since <code class="language-plaintext highlighter-rouge">@BindsInstance</code> is considered as binding.</strong></p>
<p>Example:
We already request <code class="language-plaintext highlighter-rouge">application</code> instance in <a href="https://github.com/arunkumar9t2/dagger-workmanager/blob/bd5f6787261f1e0cc3fa4c17900f1063db62c916/app/src/main/java/in/arunkumarsampath/dagger/workmanager/data/words/DefaultWordsRepository.kt">DefaultWordsRepository</a>. This request is satisfied by <code class="language-plaintext highlighter-rouge">@BindInstance</code> in the <code class="language-plaintext highlighter-rouge">AppComponent</code>. It looks like for runtime known types <code class="language-plaintext highlighter-rouge">@BindsInstance</code> is a good candidate for exposing that type. The <code class="language-plaintext highlighter-rouge">workerParameters</code> is known only when <code class="language-plaintext highlighter-rouge">createWorker</code> is called, if we create a component there with <code class="language-plaintext highlighter-rouge">@BindsInstance</code> of type <code class="language-plaintext highlighter-rouge">WorkerParameters</code> we should be able to expose it to Dagger.</p>
<p><code class="language-plaintext highlighter-rouge">Subcomponents</code> are great candidate for this. It automatically inherits all the bindings from parent component and makes it available within the subcomponent. Let’s update the visualization with this info about how <code class="language-plaintext highlighter-rouge">application</code> and <code class="language-plaintext highlighter-rouge">wordsRepository</code> are satisfied.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/dagger-workmanager-3.png#center" alt="" />
<div class="mdl-typography--caption caption">
</div>
</div>
</div>
<p>Now in order to provide <code class="language-plaintext highlighter-rouge">WorkerParameters</code>, we can create a <code class="language-plaintext highlighter-rouge">SubComponent</code> at <code class="language-plaintext highlighter-rouge">createWorker</code>. Now Dagger should be able to resolve <code class="language-plaintext highlighter-rouge">workerParameter</code>s for <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code> at the constructor <strong>as long as <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code> and the exposed <code class="language-plaintext highlighter-rouge">WorkerParameters</code> are in the same component</strong>. We will come back to this later, now we will visualize updated approach.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/dagger-workmanager-4.png#center" alt="Using a intermediate subcomponent to expose WorkerParameters" />
<div class="mdl-typography--caption caption"><p>Using a intermediate subcomponent to expose WorkerParameters</p>
</div>
</div>
</div>
<h4 id="writing-workersubcomponent">Writing WorkerSubcomponent</h4>
<p>As discussed, our <code class="language-plaintext highlighter-rouge">WorkerSubcomponent</code> takes a <code class="language-plaintext highlighter-rouge">WorkerParameters</code> param and it is pretty straight forward to write.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Subcomponent</span>
<span class="kd">interface</span> <span class="nc">WorkerSubcomponent</span> <span class="p">{</span>
<span class="nd">@Subcomponent</span><span class="p">.</span><span class="nc">Builder</span>
<span class="kd">interface</span> <span class="nc">Builder</span> <span class="p">{</span>
<span class="nd">@BindsInstance</span>
<span class="k">fun</span> <span class="nf">workerParameters</span><span class="p">(</span><span class="n">param</span><span class="p">:</span> <span class="nc">WorkerParameters</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Builder</span>
<span class="k">fun</span> <span class="nf">build</span><span class="p">():</span> <span class="nc">WorkerSubcomponent</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h5 id="connecting-appcomponent-and-workersubcomponent">Connecting AppComponent and WorkerSubcomponent</h5>
<p>In order to let <code class="language-plaintext highlighter-rouge">AppComponent</code> know the <code class="language-plaintext highlighter-rouge">WorkerSubComponent</code>, it is sufficient to write an abstract method that returns <code class="language-plaintext highlighter-rouge">WorkSubComponent</code>’s <code class="language-plaintext highlighter-rouge">Builder</code> in <code class="language-plaintext highlighter-rouge">AppComponent</code>. This is important since <strong>without this <code class="language-plaintext highlighter-rouge">WorkerSubComponent</code> won’t inherit <code class="language-plaintext highlighter-rouge">Application</code> or <code class="language-plaintext highlighter-rouge">WordsRepository</code> etc</strong>.</p>
<p>Udpated AppComponent:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Singleton</span>
<span class="nd">@Component</span><span class="p">(</span>
<span class="n">modules</span> <span class="p">=</span> <span class="p">[</span>
<span class="nc">AndroidSupportInjectionModule</span><span class="o">::</span><span class="k">class</span><span class="p">,</span>
<span class="nc">HomeBuilder</span><span class="o">::</span><span class="k">class</span><span class="p">,</span>
<span class="nc">DataModule</span><span class="o">::</span><span class="k">class</span>
<span class="p">]</span>
<span class="p">)</span>
<span class="kd">interface</span> <span class="nc">AppComponent</span> <span class="p">:</span> <span class="nc">AndroidInjector</span><span class="p"><</span><span class="nc">WorkManagerApp</span><span class="p">></span> <span class="p">{</span>
<span class="c1">// Establish WorkerSubcomponent as subcomponent</span>
<span class="k">fun</span> <span class="nf">workerSubcomponentBuilder</span><span class="p">():</span> <span class="nc">WorkerSubcomponent</span><span class="p">.</span><span class="nc">Builder</span>
<span class="nd">@Component</span><span class="p">.</span><span class="nc">Builder</span>
<span class="kd">interface</span> <span class="nc">Builder</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">build</span><span class="p">():</span> <span class="nc">AppComponent</span>
<span class="nd">@BindsInstance</span>
<span class="k">fun</span> <span class="nf">application</span><span class="p">(</span><span class="n">application</span><span class="p">:</span> <span class="nc">Application</span><span class="p">):</span> <span class="nc">Builder</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Let us visualize the updated binding graph now.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/dagger-workmanager-5.png#center" alt="Final binding graph to HelloWorldWorker's dependencies" />
<div class="mdl-typography--caption caption"><p>Final binding graph to HelloWorldWorker’s dependencies</p>
</div>
</div>
</div>
<h3 id="connecting-the-dots">Connecting the dots</h3>
<p>Now since we have a approach to provide a full graph, let’s look into how to use it and actually automate instantiating instances and let <code class="language-plaintext highlighter-rouge">WorkManager</code> use them.</p>
<h4 id="daggerwokerfactory---custom-worker-factory">DaggerWokerFactory - custom worker factory</h4>
<p>We will start with writing our DaggerWorkerFactory which has following responsibilities</p>
<ul>
<li>With <code class="language-plaintext highlighter-rouge">workerParameters</code> create a subcomponent and complete binding graph for <code class="language-plaintext highlighter-rouge">Worker</code> instances.</li>
<li>With <code class="language-plaintext highlighter-rouge">workerClassName</code> instantiate the <code class="language-plaintext highlighter-rouge">Worker</code> instance and return it for <code class="language-plaintext highlighter-rouge">WorkerManager</code> to use.</li>
</ul>
<h5 id="creating-the-workersubcomponent">Creating the WorkerSubcomponent</h5>
<p>Since we already declared <code class="language-plaintext highlighter-rouge">AppComponent.workerSubcomponentBuilder()</code> in <code class="language-plaintext highlighter-rouge">AppComponent</code>, we can directly request an instance in constructor and use it to create <code class="language-plaintext highlighter-rouge">WorkerSubComponent</code>. Note that the Dagger maintains the internal implementation of the builders. Covering that is beyond the scope of the article.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Singleton</span>
<span class="kd">class</span> <span class="nc">DaggerWorkerFactory</span>
<span class="nd">@Inject</span>
<span class="k">constructor</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">workerSubcomponent</span><span class="p">:</span> <span class="nc">WorkerSubcomponent</span><span class="p">.</span><span class="nc">Builder</span><span class="p">)</span> <span class="p">:</span> <span class="nc">WorkerFactory</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">createWorker</span><span class="p">(</span>
<span class="n">appContext</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span>
<span class="n">workerClassName</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
<span class="n">workerParameters</span><span class="p">:</span> <span class="nc">WorkerParameters</span>
<span class="p">):</span> <span class="nc">ListenableWorker</span><span class="p">?</span> <span class="p">{</span>
<span class="n">workerSubcomponent</span>
<span class="p">.</span><span class="nf">workerParameters</span><span class="p">(</span><span class="n">workerParameters</span><span class="p">)</span>
<span class="p">.</span><span class="nf">build</span><span class="p">()</span>
<span class="c1">// How to create workerClassName instance?</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>After creating the subcomponent, creating the instance with <code class="language-plaintext highlighter-rouge">workerClassName</code> still remains unknown which is covered in the next section.</p>
<h5 id="creating-instances-with-class-name">Creating instances with class name</h5>
<p>Dagger already has a mechanism to request dependencies by their type. Within Dagger, it is done using <code class="language-plaintext highlighter-rouge">Provider<Type></code> and by calling <code class="language-plaintext highlighter-rouge">provider.get()</code> we can get an instance of <code class="language-plaintext highlighter-rouge">Type</code>. To create Worker instance with class name, all we need is <code class="language-plaintext highlighter-rouge">Provider<HelloWorldWorker></code>.</p>
<p><strong>Note</strong>: We can’t get a <code class="language-plaintext highlighter-rouge">Provider<T></code> without a complete graph for <code class="language-plaintext highlighter-rouge">T</code>. Which means for a incomplete graph, compilaton fails and <code class="language-plaintext highlighter-rouge">Provider<T></code> is not generated at all.</p>
<p>In order to write it in a generic way i.e using this <code class="language-plaintext highlighter-rouge">DaggerWorkerFactory</code> for instantiating all available <code class="language-plaintext highlighter-rouge">Workers</code> in the app, we will have to do it without hardcoding <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code> type.</p>
<h5 id="multibindings">Multibindings</h5>
<p>From <a href="https://google.github.io/dagger/multibindings.html">Google docs</a></p>
<blockquote>
<p>Dagger allows you to bind several objects into a collection even when the objects are bound in different modules using multibindings. Dagger assembles the collection so that application code can inject it without depending directly on the individual bindings.</p>
</blockquote>
<p>This seems like a perfect fit for our use case which is getting the provider by key from a map. Suppose if we have s <code class="language-plaintext highlighter-rouge">Map<Class<out RxWorker>, Provider<RxWorker>></code> instance, we can use the <code class="language-plaintext highlighter-rouge">workerClassName</code> from <code class="language-plaintext highlighter-rouge">createWorker()</code> and get the <code class="language-plaintext highlighter-rouge">Provider<HelloWorldWorker></code> and return the instance.</p>
<p>In order to get <code class="language-plaintext highlighter-rouge">Map<Class<out RxWorker>, Provider<RxWorker>></code>, we have to configure multibinding. We can do that by using <code class="language-plaintext highlighter-rouge">@Binds</code> and <code class="language-plaintext highlighter-rouge">@IntoMap</code> annotations. We also need to specify the key type (<code class="language-plaintext highlighter-rouge">Class<out RxWorker></code>) for the <code class="language-plaintext highlighter-rouge">Map</code> which can be done using a custom annotation as shown below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Target</span><span class="p">(</span><span class="nc">AnnotationTarget</span><span class="p">.</span><span class="nc">FUNCTION</span><span class="p">,</span> <span class="nc">AnnotationTarget</span><span class="p">.</span><span class="nc">PROPERTY_GETTER</span><span class="p">,</span> <span class="nc">AnnotationTarget</span><span class="p">.</span><span class="nc">PROPERTY_SETTER</span><span class="p">)</span>
<span class="nd">@Retention</span><span class="p">()</span>
<span class="nd">@MapKey</span>
<span class="k">annotation</span> <span class="kd">class</span> <span class="nc">WorkManagerKey</span><span class="p">(</span><span class="kd">val</span> <span class="py">value</span><span class="p">:</span> <span class="nc">KClass</span><span class="p"><</span><span class="k">out</span> <span class="nc">RxWorker</span><span class="p">>)</span>
</code></pre></div></div>
<p>For dagger to generate this map instance, we have to declare the multibinding generating code in any <code class="language-plaintext highlighter-rouge">@Module</code>. My personal preference to add it as a <code class="language-plaintext highlighter-rouge">Builder</code> class inside the class that participates in map generation, in this case <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code>.</p>
<p>Updated <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code>:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">HelloWorldWorker</span>
<span class="nd">@Inject</span>
<span class="k">constructor</span><span class="p">(</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">application</span><span class="p">:</span> <span class="nc">Application</span><span class="p">,</span>
<span class="n">workerParameters</span><span class="p">:</span> <span class="nc">WorkerParameters</span><span class="p">,</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">wordsRepository</span><span class="p">:</span> <span class="nc">WordsRepository</span>
<span class="p">)</span> <span class="p">:</span> <span class="nc">RxWorker</span><span class="p">(</span><span class="n">application</span><span class="p">,</span> <span class="n">workerParameters</span><span class="p">)</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">createWork</span><span class="p">():</span> <span class="nc">Single</span><span class="p"><</span><span class="nc">Result</span><span class="p">></span> <span class="p">{</span>
<span class="k">return</span> <span class="n">wordsRepository</span><span class="p">.</span><span class="nf">sayHelloWorld</span><span class="p">()</span>
<span class="p">.</span><span class="nf">doOnSuccess</span> <span class="p">{</span> <span class="n">message</span> <span class="p">-></span>
<span class="c1">// Toasts are bad, don't use it.</span>
<span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="n">application</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span>
<span class="p">}.</span><span class="nf">map</span> <span class="p">{</span> <span class="nc">Result</span><span class="p">.</span><span class="nf">success</span><span class="p">()</span> <span class="p">}</span>
<span class="p">.</span><span class="nf">onErrorReturnItem</span><span class="p">(</span><span class="nc">Result</span><span class="p">.</span><span class="nf">failure</span><span class="p">())</span>
<span class="p">}</span>
<span class="nd">@Module</span>
<span class="k">abstract</span> <span class="kd">class</span> <span class="nc">Builder</span> <span class="p">{</span>
<span class="nd">@Binds</span>
<span class="nd">@IntoMap</span>
<span class="nd">@WorkerKey</span><span class="p">(</span><span class="nc">HelloWorldWorker</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
<span class="k">abstract</span> <span class="k">fun</span> <span class="nf">bindHelloWorldWorker</span><span class="p">(</span><span class="n">worker</span><span class="p">:</span> <span class="nc">HelloWorldWorker</span><span class="p">):</span> <span class="nc">RxWorker</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If you remember, for binding graph to be complete, the <code class="language-plaintext highlighter-rouge">WorkerParameters</code> exposed and generated map bindings should be in the same component. Since <code class="language-plaintext highlighter-rouge">WorkerSubComponent</code> is responsible for exposing <code class="language-plaintext highlighter-rouge">WorkerParameters</code> we will install the multibinding module to <code class="language-plaintext highlighter-rouge">WorkerSubComponent</code> as follows.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Subcomponent</span><span class="p">(</span><span class="n">modules</span> <span class="p">=</span> <span class="p">[</span><span class="nc">HelloWorldWorker</span><span class="p">.</span><span class="nc">Builder</span><span class="o">::</span><span class="k">class</span><span class="p">])</span>
<span class="kd">interface</span> <span class="nc">WorkerSubcomponent</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">workers</span><span class="p">():</span> <span class="nc">Map</span><span class="p"><</span><span class="nc">Class</span><span class="p"><</span><span class="k">out</span> <span class="nc">RxWorker</span><span class="p">>,</span> <span class="nc">Provider</span><span class="p"><</span><span class="nc">RxWorker</span><span class="p">>></span>
<span class="nd">@Subcomponent</span><span class="p">.</span><span class="nc">Builder</span>
<span class="kd">interface</span> <span class="nc">Builder</span> <span class="p">{</span>
<span class="nd">@BindsInstance</span>
<span class="k">fun</span> <span class="nf">workerParameters</span><span class="p">(</span><span class="n">param</span><span class="p">:</span> <span class="nc">WorkerParameters</span><span class="p">):</span> <span class="nc">Builder</span>
<span class="k">fun</span> <span class="nf">build</span><span class="p">():</span> <span class="nc">WorkerSubcomponent</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As noted above, for convenience we expose a method to get the binding for <code class="language-plaintext highlighter-rouge">Map<Class<out RxWorker>, Provider<RxWorker>></code> with <code class="language-plaintext highlighter-rouge">fun workers()</code>. Connecting everything the <code class="language-plaintext highlighter-rouge">DaggerWorkerFactory</code> looks like below.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Singleton</span>
<span class="kd">class</span> <span class="nc">DaggerWorkerFactory</span>
<span class="nd">@Inject</span>
<span class="k">constructor</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">workerSubcomponent</span><span class="p">:</span> <span class="nc">WorkerSubcomponent</span><span class="p">.</span><span class="nc">Builder</span><span class="p">)</span> <span class="p">:</span> <span class="nc">WorkerFactory</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">createWorker</span><span class="p">(</span>
<span class="n">appContext</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span>
<span class="n">workerClassName</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
<span class="n">workerParameters</span><span class="p">:</span> <span class="nc">WorkerParameters</span>
<span class="p">)</span> <span class="p">=</span> <span class="n">workerSubcomponent</span>
<span class="p">.</span><span class="nf">workerParameters</span><span class="p">(</span><span class="n">workerParameters</span><span class="p">)</span>
<span class="p">.</span><span class="nf">build</span><span class="p">().</span><span class="nf">run</span> <span class="p">{</span>
<span class="nf">createWorker</span><span class="p">(</span><span class="n">workerClassName</span><span class="p">,</span> <span class="nf">workers</span><span class="p">())</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">fun</span> <span class="nf">createWorker</span><span class="p">(</span>
<span class="n">workerClassName</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
<span class="n">workers</span><span class="p">:</span> <span class="nc">Map</span><span class="p"><</span><span class="nc">Class</span><span class="p"><</span><span class="k">out</span> <span class="nc">RxWorker</span><span class="p">>,</span> <span class="nc">Provider</span><span class="p"><</span><span class="nc">RxWorker</span><span class="p">>></span>
<span class="p">):</span> <span class="nc">ListenableWorker</span><span class="p">?</span> <span class="p">=</span> <span class="k">try</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">workerClass</span> <span class="p">=</span> <span class="nc">Class</span><span class="p">.</span><span class="nf">forName</span><span class="p">(</span><span class="n">workerClassName</span><span class="p">).</span><span class="nf">asSubclass</span><span class="p">(</span><span class="nc">RxWorker</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">)</span>
<span class="kd">var</span> <span class="py">provider</span> <span class="p">=</span> <span class="n">workers</span><span class="p">[</span><span class="n">workerClass</span><span class="p">]</span>
<span class="k">if</span> <span class="p">(</span><span class="n">provider</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">((</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="k">in</span> <span class="n">workers</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">workerClass</span><span class="p">.</span><span class="nf">isAssignableFrom</span><span class="p">(</span><span class="n">key</span><span class="p">))</span> <span class="p">{</span>
<span class="n">provider</span> <span class="p">=</span> <span class="n">value</span>
<span class="k">break</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">provider</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="nc">IllegalArgumentException</span><span class="p">(</span><span class="s">"Missing binding for $workerClassName"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">provider</span><span class="p">.</span><span class="k">get</span><span class="p">()</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">:</span> <span class="nc">Exception</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="nc">RuntimeException</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With the given <code class="language-plaintext highlighter-rouge">workerClassName</code> we find a appropriate <code class="language-plaintext highlighter-rouge">Provider<T></code> and get an instance and return it to <code class="language-plaintext highlighter-rouge">WorkManager</code>.</p>
<h3 id="results">Results</h3>
<p>The changes for above sections are done in this <a href="https://github.com/arunkumar9t2/dagger-workmanager/commit/778a40726dc0d4c528f5ea550bbd81ef262e1a1a">commit</a>. Debugging the <code class="language-plaintext highlighter-rouge">HelloWorldWorker</code>, we can see that the injection has happened.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="/assets/images/dagger-workmanager-6.png#center" alt="Constructor injection in Worker instance" />
<div class="mdl-typography--caption caption"><p>Constructor injection in Worker instance</p>
</div>
</div>
</div>
<p>The entire source of the sample app with working injection can be found <a href="https://github.com/arunkumar9t2/dagger-workmanager">here</a>.</p>
<h3 id="summary">Summary</h3>
<p>In this article, we discussed a way to configure dagger for advanced cases where a dependency instance can be accessed only during runtime. In <code class="language-plaintext highlighter-rouge">WorkManager</code>, we get to know about <code class="language-plaintext highlighter-rouge">WorkerParameter</code> only at the time of initialization by <code class="language-plaintext highlighter-rouge">WorkerFactory</code>. In order to handle this case, we break down our root component to sub components and pass in the required type (workParameters) as parameter to a subcomponent. Then we use <code class="language-plaintext highlighter-rouge">Multibindings</code> to instantiate the instances. The implemented solution is a generic one and as long as all <code class="language-plaintext highlighter-rouge">Worker</code>s have a multibinding generator, the injection will work for all available <code class="language-plaintext highlighter-rouge">Workers</code>. Since the initialization happens in <code class="language-plaintext highlighter-rouge">onCreate</code> of the application, even when the <code class="language-plaintext highlighter-rouge">WorkManager</code> wakes up the application to do jobs, the custom factory we provided would be set.</p>
<h3 id="misc">Misc</h3>
<p>Thanks for following through till the end of the article. Liked what you read? I would be glad to receive feedback or criticism if any. If you do have any review comments/feedback please reach out to me or express in the comments below. I plan to write more about practical uses of Dagger in this Recipes series.</p>{"twitter"=>"arunkumar_9t2"}Recently, while working on an app, I decided to try out Jetpack’s WorkManager for all background jobs (it’s pretty great :+1:). The app already uses Dagger 2 for DI and I wanted to carry that over to WorkManager as well when executing jobs.Transition X — Declarative Kotlin DSL for choreographing Android Transitions2018-09-21T00:00:00+00:002018-09-21T00:00:00+00:00https://www.arunkumar.dev/transtition-x-declarative-kotlin-dsl-for-choreographing-android-transitions<p>Material Design’s announcement at Google IO 2014 redefined Android UX. New emphasis were given to <strong>motion</strong> and the guidelines encouraged using motion as a tool to be expressive and adding character to your app.</p>
<p>From <a href="https://material.io/design/motion/understanding-motion.html#usage">Material.io:</a></p>
<blockquote>
<p><strong>Motion provides meaning</strong>
Motion focuses attention and maintains continuity, through subtle feedback and coherent transitions. As elements appear on screen, they transform and reorganize the environment, with interactions generating new transformations.</p>
</blockquote>
<p>On the technical side of things, for the first time <code class="language-plaintext highlighter-rouge">Android Lollipop</code> used a separate thread (<code class="language-plaintext highlighter-rouge">RenderThread</code>) to handle animations even if there are delays in main thread. Introduction of RenderThread allowed things like <code class="language-plaintext highlighter-rouge">Circular Reveal Animation</code>, <code class="language-plaintext highlighter-rouge">AnimatedVectorDrawables</code>, <code class="language-plaintext highlighter-rouge">Ripple Feedback</code> without noticeable slowdowns.</p>
<h3 id="transitions-framework">Transitions Framework</h3>
<p>Before Android Lollipop, <strong>Kitkat introduced Scenes & Transitions API</strong> which were an abstraction on top of Animations to reduce effort required for simple animations. Basically it was possible to give a start and end layout and the framework would animate the difference.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="img-center" src="https://cdn-images-1.medium.com/max/2000/1*eN_khPCZ-qb67y1P_Zw8sA.png#center" alt="https://developer.android.com/training/transitions/" />
<div class="mdl-typography--caption caption"><p>https://developer.android.com/training/transitions/</p>
</div>
</div>
</div>
<p>In order to let the framework animate simple changes, all we must do is to call <code class="language-plaintext highlighter-rouge">TransitionManager.beginDelayedTransition(veiwGroup)</code> before changing the layout and the framework would take care of animating the difference. I recommend reading this <a href="https://medium.com/google-developers/transitions-in-the-android-support-library-8bc86a1d688e">article</a> by <a href="https://medium.com/@andkulikov">Andrey Kulikov</a>, which covers different types of animations you could do with the framework.</p>
<h3 id="transition-x--better-transitions-with-kotlin">Transition X — Better transitions with Kotlin</h3>
<p>What Kotlin has to do with transitions and Material Motion? <em>Let’s find out.</em></p>
<p>Material Choreography provides certain guidelines on how these animations should play; namely <strong>ordering, easing, duration and type.</strong></p>
<p><a href="https://material.io/design/motion/choreography.html#"><strong>Choreography</strong></a></p>
<blockquote>
<p>Complex layout changes use shared transformation to create smooth transitions from one layout to the next. Elements are grouped together and transform as a single unit, rather than animating independently, avoiding multiple transformations that overlap and compete for attention.</p>
</blockquote>
<p>The transition framework is already capable of <a href="https://developer.android.com/reference/android/support/transition/Transition">this</a>. But the boilerplate required for choreographing detailed transition were immense. Let’s take an example. Android lets us create <a href="https://developer.android.com/reference/android/support/transition/TransitionSet">TransitionSet</a> instance by using XML as shown below.</p>
<noscript><pre>400: Invalid request</pre></noscript>
<script src="https://gist.github.com/8f3e4aea6edf98540bbb73cd23b58347.js"> </script>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="fill" src="https://cdn-images-1.medium.com/max/2000/1*YM_HbwYVDLDF8bSIUiW1Ig.png" alt="Android studio has auto completion for common properties." />
<div class="mdl-typography--caption caption"><p>Android studio has auto completion for common properties.</p>
</div>
</div>
</div>
<p>It is no argument that XML takes lot of words to convey structure and that grows linearly when you want to choreograph complex transitions. Later in the code you would use <code class="language-plaintext highlighter-rouge">TransitionInflater</code> to get an instance then call <code class="language-plaintext highlighter-rouge">TransitionManager.beginDelayedTransition</code>. <strong>This has high chance of getting repeated all over the place.</strong></p>
<p>Using XML also breaks type safety when working with custom transition instances.</p>
<p>I set to solve this problem using Kotlin by taking advantage of lamdas with receivers feature and reducing much of the boilerplate. To achieve the same transition, Transition X code would look like below:</p>
<noscript><pre>400: Invalid request</pre></noscript>
<script src="https://gist.github.com/32d1562ce9994ccdcae4069b8789cfb1.js"> </script>
<p>Transition X provides a <code class="language-plaintext highlighter-rouge">prepareTransition</code> extension method on <code class="language-plaintext highlighter-rouge">ViewGroup</code> classes which can be used to construct complex transition instances. After <code class="language-plaintext highlighter-rouge">prepareTransition</code> you typically apply the layout changes for kick starting the transition. Scheduling call to <code class="language-plaintext highlighter-rouge">TransitionManager</code> is handled automatically, you only need to concentrate on declaring different transitions and their characteristics and the library handles the rest.</p>
<h4 id="transition-x-library-features">Transition X library features</h4>
<ul>
<li>
<p><strong>Declarative —</strong> Easily declare variety of transitions and specify their properties. No math required. Transition X has variety of methods for inbuilt transitions from support library like <code class="language-plaintext highlighter-rouge">ChangeBounds</code>, <code class="language-plaintext highlighter-rouge">ChangeTransform</code>, <code class="language-plaintext highlighter-rouge">ChangeClipBounds</code> etc.</p>
</li>
<li>
<p><strong>Rich tooling support —</strong> Transition X inherits all of Kotlin’s powerful IDE tooling integrations and provides you with auto complete for all available transition and related properties.</p>
</li>
</ul>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="fill" src="https://cdn-images-1.medium.com/max/2000/1*K12hNjCMdWjAn6NSgOQvrg.png" alt="IDE autocomplete for all available transitions and properties" />
<div class="mdl-typography--caption caption"><p>IDE autocomplete for all available transitions and properties</p>
</div>
</div>
</div>
<ul>
<li><strong>Extensible —</strong> Have a custom transition? Transition X can work with them too, thanks to Kotlin’s <code class="language-plaintext highlighter-rouge">reified</code> inline types.</li>
</ul>
<noscript><pre>400: Invalid request</pre></noscript>
<script src="https://gist.github.com/efeadcc4a0a9b3c69c1cb80ae5c79954.js"> </script>
<ul>
<li><strong>Natural syntax —</strong> The syntax is simple to understand and reduces cognitive load needed to follow the animation choreography. Lifecycle methods like <code class="language-plaintext highlighter-rouge">onEnd</code>, <code class="language-plaintext highlighter-rouge">onStart</code> provide greater clarity and control over how rest of the code reacts to transition.</li>
</ul>
<noscript><pre>400: Invalid request</pre></noscript>
<script src="https://gist.github.com/997dc7a99371aeab92d12ecf9bb93a25.js"> </script>
<p>The remainder of the article will explore some common transitions and demonstrate how they are accomplished with Transition X.</p>
<h3 id="examples">Examples</h3>
<h5 id="snackbar-transition">Snackbar transition</h5>
<p>Snackbar + Fab animation is one of the first things that caught our eye when material design principles were introduced. The real implementation however used <code class="language-plaintext highlighter-rouge">CoordinatorLayout</code> and bunch of math to mimic the moving the behavior. With <code class="language-plaintext highlighter-rouge">ConstraintLayout</code> and <code class="language-plaintext highlighter-rouge">Transition X</code>, it becomes simple as ever.</p>
<noscript><pre>400: Invalid request</pre></noscript>
<script src="https://gist.github.com/1ce29f06ade092e61757e90d365113ca.js"> </script>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://cdn-images-1.medium.com/max/2000/1*T-UNjRal8DnF7rcbvRpc8Q.gif" alt="" width="100%" />
<div class="mdl-typography--caption caption">
</div>
</div>
</div>
<p>Here the FAB has a bottom constraint attached to the SnackbarMessage layout. When the SnackbarMessage becomes visible, the FAB moves to the top because of the constraints.</p>
<p>Breaking down the code above:</p>
<ul>
<li>
<p><strong>moveResize</strong>: Internally adds a <code class="language-plaintext highlighter-rouge">ChangeBounds</code> transition which animates <code class="language-plaintext highlighter-rouge">View</code>’s bounds. By using <code class="language-plaintext highlighter-rouge">+</code> operator overload, we can ask this transition to only affect fab and nothing else.</p>
</li>
<li>
<p><strong>slide:</strong> Internally adds a <code class="language-plaintext highlighter-rouge">Slide</code> animation which triggers when view visibility changes. Like fab this transition is exclusively set to affect <code class="language-plaintext highlighter-rouge">snackbarMessage</code> alone.</p>
</li>
<li>
<p><strong>decelerateEasing:</strong> Adds a <code class="language-plaintext highlighter-rouge">LinearOutSlowInInterpolator</code> on this transition.</p>
</li>
</ul>
<h5 id="advanced-choreography">Advanced Choreography:</h5>
<p>At most times, it is not beneficial to simply move resize items on screen without context. Material Choreography recommends greater control over which elements move/fade and how. In the following example, there are several things happening.</p>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://cdn-images-1.medium.com/max/2000/1*f1U8T2lR8vCLdatdGeekfA.gif" alt="" width="100%" />
<div class="mdl-typography--caption caption">
</div>
</div>
</div>
<ol>
<li>
<p>The card size changes to accommodate larger images. As per material choreography items resizing should have <code class="language-plaintext highlighter-rouge">FastOutSlowInInterpolator</code>.</p>
</li>
<li>
<p>Notice the <code class="language-plaintext highlighter-rouge">Calm your mind..</code>. text does not simply move to the bottom, but instead fades away and emerges naturally under expanded images.</p>
</li>
<li>
<p>Images’ scale type and bounds changes.</p>
</li>
<li>
<p>Collapse/Expand text transition waits for the resize animation to end and does not change together with the resize animation.</p>
</li>
</ol>
<p>The code for the above transition:</p>
<noscript><pre>400: Invalid request</pre></noscript>
<script src="https://gist.github.com/1d2cd1f519a7d4e26818bd39abd1fe22.js"> </script>
<p>Notice that it has become considerably easier to pull off this transition with no math or calculations, just clever declarations with different targets and the <code class="language-plaintext highlighter-rouge">TransitionManager</code> takes care of animating the rest.</p>
<h3 id="why-not-objectanimators">Why not ObjectAnimators?</h3>
<p>Transitions internally use object animators and they simply are an abstraction over <code class="language-plaintext highlighter-rouge">Animators</code> with start and end values. It is possible to use <code class="language-plaintext highlighter-rouge">Animators</code> directly but there are certain concerns.</p>
<ul>
<li>
<p><strong>Performance</strong>: Object animators are one of the powerful ways to handle animations, but the transition framework has some important optimizations which make them desirable. Namely the <code class="language-plaintext highlighter-rouge">ChangeBounds</code> transition. <code class="language-plaintext highlighter-rouge">ChangeBounds</code> internally uses a hidden private API called <code class="language-plaintext highlighter-rouge">ViewGroup.suppressLayout</code> which temporarily pauses all <code class="language-plaintext highlighter-rouge">invalidate()</code> calls to the layout which means less pressure on the CPU to constantly remeasure moving items on the screen.</p>
</li>
<li>
<p><strong>Increased complexity:</strong> Object animators requires calculation and knowing end value before layout changes whereas <code class="language-plaintext highlighter-rouge">Transition</code> framework automatically gets the information after the layout has changed.</p>
</li>
</ul>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://cdn-images-1.medium.com/max/2000/1*QtPP5kF4xw86nZ5UYfESSw.gif" alt="" width="100%" />
<div class="mdl-typography--caption caption">
</div>
</div>
</div>
<p>In this example, the ImageView’s scale and gravity alone is changed. By using <code class="language-plaintext highlighter-rouge">moveResize</code> and <code class="language-plaintext highlighter-rouge">ArcMotion</code> it is possible perform this hero transition without needing to calculate where the ImageView would be when thelayoutchanges are applied.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">frameLayout</span><span class="p">.</span><span class="nf">prepareTransition</span> <span class="p">{</span>
<span class="nf">moveResize</span> <span class="p">{</span>
<span class="n">pathMotion</span> <span class="p">=</span> <span class="nc">ArcMotion</span><span class="p">()</span>
<span class="p">+</span><span class="n">userIconView</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With <code class="language-plaintext highlighter-rouge">ObjectAnimator</code> however, we would need to calculate the end location of the ImageView beforehand to run this transition.</p>
<h3 id="gotchas">Gotchas</h3>
<p>Like any framework, <code class="language-plaintext highlighter-rouge">Transition</code> does have some <a href="https://developer.android.com/training/transitions/#Limitations">limitations</a>. All of them can considerably overcome using Transition X DSL. For example, to exclude all <code class="language-plaintext highlighter-rouge">RecyclerView</code> from participating in the transition it is sufficient to call <code class="language-plaintext highlighter-rouge">exclude<RecyclerView>()</code> and all the RVs inside current layout will not participate in the transition.</p>
<h3 id="conclusion">Conclusion</h3>
<p>I strongly believe Transition X style DSL reduces complexity and simplifies workflow when working with transitions. The aim of this library is to increase exposure to the Transition framework and unlock their potential using Kotlin’s language features. The entire library and a sample app with basic transitions are available in this <a href="https://github.com/arunkumar9t2/transition-x">repo</a>.</p>
<p>If you have any feedback on the project, I would be happy to hear from you in the comments below or on <a href="https://twitter.com/arunkumar_9t2">Twitter</a> or on <a href="https://plus.google.com/u/0/+arunkumar5592">Google+</a>.</p>
<hr />{"twitter"=>"arunkumar_9t2"}Material Design’s announcement at Google IO 2014 redefined Android UX. New emphasis were given to motion and the guidelines encouraged using motion as a tool to be expressive and adding character to your app.Introducing Lynket (previously Chromer)— A better way to browse on Android2018-02-21T00:00:00+00:002018-02-21T00:00:00+00:00https://www.arunkumar.dev/Lynket-browser-2nd-anniversary-update<p><em>This post originally appeared on <a href="https://medium.com/@arunkumar9t2/introducing-chromer-2-0-a-better-way-to-browse-on-android-c34ff729df1b">Medium</a>.</em></p>
<p>I would like to introduce you to my open source Android app <strong>Chromer</strong>, now called Lynket. In this article, I want to talk about how Lynket does things differently and tries to remain relevant among top browsers such as Chrome, Brave, Firefox, Samsung Internet or Opera etc.</p>
<div style="position:relative;padding-top:56.25%;">
<iframe src="https://www.youtube.com/embed/Hcd2R2Lh5ks" frameborder="0" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;"></iframe>
</div>
<h3 id="brief-history">Brief history</h3>
<p>Let’s say you are using WhatsApp and you receive a link. Naturally clicking on it would open the link in your browser app and you leave WhatsApp to read the link. What if WhatsApp wants to show you the link and customize it so that you don’t have to <strong>leave the app</strong> just for visiting this link? They have to use a system component called WebView which was only way developers to show web pages or build a custom browser.</p>
<p>While WebView served this purpose well, it was not perfect. There were flaws such as security patches required an Android System update (which is slow), performance were rather sub par and in worst cases <a href="https://plus.google.com/+AnthonyRestaino/posts/NA5DyBLDJFr">crashes</a> the app. Popular custom browser Link Bubble’s Chris Lacy <a href="https://www.androidpolice.com/2014/05/16/interview-link-bubble-and-action-launcher-developer-chris-lacy-shares-his-thoughts-on-android-app-development-and-more/">agrees</a> on the sentiments.</p>
<h4 id="googles-answer-and-progressive-enhancements">Google’s answer and progressive enhancements</h4>
<p>Luckily all these cries did not fall on deaf ears. Browser app such as <em>Chrome</em> or <em>Firefox</em> are generally better because they use their own rendering engine updated through Play Store. It would be nice if the same happens with WebView, wouldn’t it? Google did that exactly in Android Lollipop.</p>
<p>However at IO/15, Google announced something unexpected. They said we are giving developers an API to load a light weight version of Chrome onto their apps! They called it Custom Tabs and it was faster and smoother in many ways.</p>
<h4 id="custom-tabs">Custom Tabs</h4>
<p>Without saying, Custom Tabs is much superior to WebView. It is updated through Play Store (along with provider app), shares saved passwords and can essentially sync your browsing activity with Google account but it <strong>has to be manually implemented by each and every app.</strong></p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="fill" src="https://developer.chrome.com/multidevice/images/customtab/performance.gif#center" alt="Custom Tabs Performance comparison" />
<div class="mdl-typography--caption caption"><p>Custom Tabs Performance comparison</p>
</div>
</div>
</div>
<h3 id="enter-chromer">Enter Chromer</h3>
<p>In December 2015, I <a href="https://plus.google.com/u/0/+arunkumar5592/posts/CQmK1kRgHSh">launched</a> Chromer which enabled any app to use Chrome Custom Tabs <em>without the apps’ developer effort.</em> I was motivated to develop Chromer since apps that I use took a long time to implement CCT. It quickly grew to be liked by many users currently sitting at 4.6 rating from 8000+ reviewers.</p>
<h3 id="20--anniversary-update-and-lynket">2.0 — Anniversary Update and Lynket</h3>
<p>Things change — Google has constantly improved the browsing experience and I am <em>forever</em> a fan of them. Google Chrome experimented opening Custom Tabs by default without the need for Chromer and system WebView in Nougat+ versions now uses Google Chrome internally.</p>
<p>Unfortunately, Chromer’s name had to be changed to comply with Play Store policies since it resembles Chrome. It was never my intention to imitate Chrome in anyway. I deliberately kept the icons different and made it clear in the description that initial versions required Chrome to work. Google rejected the update even though app had been live for 2 years with multiple updates in the past.</p>
<p>Lynket, the new name is derived from <strong>Tabs</strong> in Italian — <code class="language-plaintext highlighter-rouge">Linguetta</code>. With that said, can Lynket still offer a better browsing experience?</p>
<p>I would like to walk through certain different things I have tried in Lynket to improve your browsing experience.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="fill" src="https://cdn-images-1.medium.com/max/3840/1*V3S_bcE7iXJnD0F0qfsaMA.png" alt="New design of 2.0" />
<div class="mdl-typography--caption caption"><p>New design of 2.0</p>
</div>
</div>
</div>
<h3 id="lynket-works-with-your-favorite-browser">Lynket works with your favorite browser</h3>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://cdn-images-1.medium.com/max/2000/1*rItxnyZcB7B0PzZAI6yZRg.gif" alt="Changing rendering engines" width="100%" />
<div class="mdl-typography--caption caption"><p>Changing rendering engines</p>
</div>
</div>
</div>
<p>In addition to many customization options, Lynket allows you to <em>choose</em> which rendering engine/browser you want to use for loading pages. This means, it <strong>inherits several behaviors</strong> from them while adding more.</p>
<ul>
<li>Brave Browser — Applies adblocking, tracking protection</li>
<li>Google Chrome — Google account sync, login info sharing.</li>
<li>Samsung Internet — Samsung Pass (Biometric login) and Samsung Cloud.</li>
</ul>
<p>I cannot possibly build a better rendering engine, so Lynket leaves rendering to the top players and adds customizations on top of it through the Custom Tab protocol.</p>
<h3 id="the-curious-case-of-multitasking">The curious case of Multitasking</h3>
<p>It is no doubt that Chris Lacy’s Link Bubble pioneered the bubble browsing in Android, inspired by Facebook’s Chat Heads. It allowed you to load links in floating bubbles, while you continue browsing so you don’t waste time looking at loading screens; but limitations of WebView still applied.</p>
<p>Later <a href="https://brave.com">Brave</a> bought Link Bubble and tried to provide the same experience but <a href="https://brave.com/unpublishing-link-bubble/">they too did not feel comfortable</a> working with WebView API and the same was unpublished later.</p>
<p>In their own words (emphasis mine):</p>
<blockquote>
<p>given Google’s refusal to fix Android to support a <strong>webview that runs and renders from a background service</strong> (this is how Link Bubble runs in order to float bubbles over your current app). After a number of Android releases with no fixes in sight for significant bugs, we decided it was time to move on. …which offers the crucial features that users say they need to deal with today’s Web: <strong>tabs</strong></p>
</blockquote>
<p>Another problem was WebView in a Service simply does not respect the Android’s app lifecycle. A Service with WebView would take huge amounts of RAM and there was no automated way to release it, unless the developer handles it manually (<a href="https://developer.android.com/topic/performance/memory.html#release">by listening for low memory events</a>).</p>
<p>I took the challenge to solve this problem by use of Custom Tabs. I had launched bubble browsing in Lynket last year but 2.0 contains important polishes that elevate your browsing experience.</p>
<p>Given the benefits of Custom Tabs, it would be awesome if we can:</p>
<ul>
<li>Load Custom Tabs in background</li>
<li>Respect Android’s lifecycle</li>
<li>Intuitive way to multitask between Tabs</li>
<li>Load links in Floating Bubbles</li>
</ul>
<p>That’s exactly what Lynket 2.0 does!</p>
<h4 id="better-bubble-browsing--web-heads">Better Bubble Browsing — Web Heads</h4>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://cdn-images-1.medium.com/max/2000/1*XqIwvDNAhYm4U165RQUNfg.gif" alt="Web Heads - Floating bubbles" width="100%" />
<div class="mdl-typography--caption caption"><p>Web Heads - Floating bubbles</p>
</div>
</div>
</div>
<h4 id="respect-android-app-lifecycle">Respect Android App Lifecycle</h4>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://cdn-images-1.medium.com/max/2160/1*lKc2IVc3wH0FecA0Jj2hrA.png" alt="Lynkets uses Android's Overview screen to multitask" width="100%" />
<div class="mdl-typography--caption caption"><p>Lynkets uses Android’s Overview screen to multitask</p>
</div>
</div>
</div>
<p>Lynket uses your Android’s recents screen to create multiple tabs. This is achieved by use of <em>Lollipop’s Document API.</em> This is similar to Google Chrome’s Merge Tabs and Apps mode which was removed later.</p>
<h5 id="why-this-is-better">Why this is better?</h5>
<p>Custom Tabs are normal activities which Android System can <strong>close and reload automatically</strong> when in need of memory. This way your phone won’t lag when opening a Game or loading a photo in an editor app.</p>
<p>Lynket also shows title, icons and color just like Google Chrome used to do.</p>
<h4 id="floating-bubbles--background-loading">Floating bubbles + background loading</h4>
<p>Since Custom Tabs are activities, they can’t be really loaded in background. Luckily after long investigations, experiments and refinements, Lynket 2.0 brings background loading to Custom Tabs! It works by launching the URL and <strong>immediately going into background while you continue browsing</strong>. This is the same mechanism Google Chrome used to do when opening a new tab in Merge Tabs and Apps mode.</p>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://cdn-images-1.medium.com/max/2000/1*m5Gy5vWT5GKekA4d37I3TA.gif#center" alt="Background loading with Web Heads" width="100%" />
<div class="mdl-typography--caption caption"><p>Background loading with Web Heads</p>
</div>
</div>
</div>
<p>Here you can see Techcrunch opening and getting minimized. It continues to load in background and when you want it, you can use the bubble to bring it to the front!
Long press the bubble to see active tabs.
This does not answer what intuitive multitasking means right? Read on.</p>
<h3 id="intuitive-multitasking">Intuitive Multitasking</h3>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://cdn-images-1.medium.com/max/2000/1*YqQoa2QTgtyudYEiCfl6LQ.gif" alt="`Minimze` and `Tabs` to quickly multitask" width="100%" />
<div class="mdl-typography--caption caption"><p><code class="language-plaintext highlighter-rouge">Minimze</code> and <code class="language-plaintext highlighter-rouge">Tabs</code> to quickly multitask</p>
</div>
</div>
</div>
<p><strong>Minimize —</strong> Lynket also provides a button to quickly minimize the tab to background. This is a new convenient feature not seen in many apps/browsers. I can hear you thinking “I can do that by using recents screen!”. Yes you can, but do try how Minimize and Tabs works out for you. It’s clear, concise and gets the job done <em>without cognitive load of scanning through opened apps.</em></p>
<p><strong>Tabs -</strong> Something Google’s Merge Tabs and Apps missed was providing a way to access web page tabs alone in the midst of all apps in your Overview screen. Lynket solves that in <strong>Tabs</strong> screen. Here you can see all active tabs opened by Lynket.</p>
<p>I am really excited about background loading, minimize and tabs feature. I had to scour through Android’s API stack to find a way to implement this and I am excited to get feedback!</p>
<h3 id="mobile-first-browser">Mobile First Browser</h3>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://cdn-images-1.medium.com/max/4268/1*OpK7MSooJ30CkSbYK-vwUg.png" alt="" width="100%" />
<div class="mdl-typography--caption caption">
</div>
</div>
</div>
<p>Browsing on mobile is a lot different than desktop. Users expect fast, concise content that works even on slow networks. Lynket has two modes specifically tailored for this. <strong>AMP and Article Mode</strong>.</p>
<p>While I have personally received feedback from users that they don’t like AMP, it still loads much faster on Mobile on a slow network. Before loading an URL, Lynket can find if an AMP version exists and load it instead.</p>
<h4 id="article-mode">Article mode</h4>
<p>Article mode is my favorite feature. Many browsers now have a mode called <strong>Reader mode</strong> which removes unnecessary content and grabs the gist of the article. Article is no different but I think it looks and functions better.</p>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--12-col">
<img class="fill" src="https://cdn-images-1.medium.com/max/3840/1*jBMuqr6p5tklZJd6wQRGgw.png" alt="Article mode with different themes." />
<div class="mdl-typography--caption caption"><p>Article mode with different themes.</p>
</div>
</div>
</div>
<div class="mdl-grid" style="justify-content: center;">
<div class="mdl-cell mdl-cell mdl-cell--6-col mdl-cell--6-col-desktop mdl-cell--6-col-tablet mdl-cell--12-col-phone">
<img src="https://cdn-images-1.medium.com/max/2000/1*MIlPFhmGaKuA0VkTh_ayiQ.gif" alt="Article mode in action." width="100%" />
<div class="mdl-typography--caption caption"><p>Article mode in action.</p>
</div>
</div>
</div>
<p>You can tap the Keywords to do a Google search. Article mode was inspired by <a href="http://klinkerapps.com/">Klinker Apps’</a> Article mode. However Lynket’s implementation is different and works locally without saving data on an external server.</p>
<h3 id="other-features">Other features</h3>
<p>It does not stop here and Lynket has neat little features for your convenience.</p>
<ul>
<li><strong>Secondary browser</strong> — Set a secondary browser to quickly switch to.</li>
<li><strong>Favorite Sharing App</strong> — Set a favorite share app to quickly share to.</li>
<li><strong>Per app settings</strong> — Configure <strong>incognito and secondary browser</strong> individually for each apps.</li>
<li><strong>Quick settings toggle</strong> for Web heads, Incognito Mode, AMP and Article Modes.</li>
<li>Lot of customization features like Animations, toolbar colors etc.</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<p>By using Custom Tabs API in innovative ways, I believe Lynket offers a superior browsing solution. You really don’t have to switch to Lynket completely but you can let it use your favorite browser and be in the same ecosystem (if it supports CCT). Web heads mode has matured a lot and merges bubble browsing and Custom Tabs perfectly. I have spent time on design polishes as well. Article mode is a real pleasure to use on mobile and I have received positive feedback from beta testers. I am excited to know your feedback too!</p>
<p>Best of all, Lynket is <strong>fully free and open source</strong>. You can find the source code <a href="https://github.com/arunkumar9t2/lynket-browser">here.</a> If you are a developer, I would be glad to accept contributions.</p>
<p><a href="https://play.google.com/store/apps/details?id=arun.com.chromer&utm_source=arunkumarsampath.in&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1">
<img alt="Get it on Google Play" class="google-play-img" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png#center" />
</a></p>
<p>Until next time!</p>
<hr />{"twitter"=>"arunkumar_9t2"}This post originally appeared on Medium. I would like to introduce you to my open source Android app Chromer, now called Lynket. In this article, I want to talk about how Lynket does things differently and tries to remain relevant among top browsers such as Chrome, Brave, Firefox, Samsung Internet or Opera etc.