Jekyll2021-01-13T18:40:25+00:00https://androidexplained.github.io//feed.xmlAndroid ExplainedAndroid app development articles for beginner and advanced learners. Posts covering everything related to android.Cleaning up resources in RecyclerView ViewHolder2020-11-09T19:50:30+00:002020-11-09T19:50:30+00:00https://androidexplained.github.io//android/ui/2020/11/09/cleaning-up-resources-in-view-holder<p>In your journey you might have often wondered when exactly to clean up resources in a <strong>RecyclerView ViewHolder</strong>.<br />
The topic isn’t easy and like most you probably tried to use <strong>onViewAttachedToWindow</strong> & <strong>onViewDetachedFromWindow</strong>, only to find it’s not working and gave up hoping you’ll get away with
no resource freeing mechanism.<br />
Sure there is this 1% of users of your app who get <strong>OutofMemoryException</strong> from time to time and on older phones scrolling through a list of items looks more like playing Tetris. <br />
You didn’t really care until you get a negative review with 500 upvotes saying that your app is almost unusable.</p>
<p>But worry not, in this article we will look at how to cleanup resources in <strong>RecyclerView ViewHolder</strong> properly.</p>
<h2 id="release-the-resources">Release the… resources</h2>
<p>There are couple of methods that will be important to us in this article</p>
<ul>
<li><a href="https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#onBindViewHolder(VH,%20int)">RecyclerView.Adapter#onBindViewHolder</a> - called to display data at specified position, should update the contents of <strong>ViewHolder</strong> to reflect an item at position</li>
<li><a href="https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#onViewRecycled(VH)">RecyclerView.Adapter#onViewRecycled</a> - called when <strong>View</strong> is recycled and put into the pool, since the <strong>View</strong> might sit in the pool for a while it might be a good idea to release heavy resources like bitmaps in this method</li>
</ul>
<p>Lets look at a situation when user is scrolling downwards in a <strong>RecyclerView.</strong> <br />
We will assume that there are two ViewHolders of the same type displayed (<strong>1 & 2</strong>).<br /></p>
<ul>
<li>items are displayed on the screen - <strong>onBindViewHolder</strong> will be called for both <strong>ViewHolder 1 & 2</strong></li>
<li>user scrolls down and the top <strong>ViewHolder 1</strong> disappears, it will be put into the pool, after which the <strong>onViewRecycled</strong> method will be called on that ViewHolder</li>
<li>at the same time new ViewHolder appears on the bottom - since we have <strong>ViewHolder 1</strong> in the pool it will be reused and <strong>onBindViewHolder</strong> will be called again to reflect the new position</li>
<li>and so on and so on…</li>
</ul>
<p>Therefore we can basically utilize these two above methods to initialise resources (<strong>onBindViewHolder</strong>) and cleanup them up (<strong>onViewRecycled</strong>) accordingly.</p>
<p>The below code can serve as an example of how this would look in practice</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MovieAdapter</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">movies</span><span class="p">:</span> <span class="nc">Array</span><span class="p"><</span><span class="nc">String</span><span class="p">>)</span> <span class="p">:</span>
<span class="nc">RecyclerView</span><span class="p">.</span><span class="nc">Adapter</span><span class="p"><</span><span class="nc">MovieAdapter</span><span class="err">.</span><span class="nc">ViewHolder</span><span class="p">>()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onBindViewHolder</span><span class="p">(</span><span class="n">viewHolder</span><span class="p">:</span> <span class="nc">ViewHolder</span><span class="p">,</span> <span class="n">position</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">=</span>
<span class="n">viewHolder</span><span class="p">.</span><span class="nf">onBind</span><span class="p">(</span><span class="n">movies</span><span class="p">[</span><span class="n">position</span><span class="p">])</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">getItemCount</span><span class="p">()</span> <span class="p">=</span> <span class="n">movies</span><span class="p">.</span><span class="n">size</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreateViewHolder</span><span class="p">(</span><span class="n">parent</span><span class="p">:</span> <span class="nc">ViewGroup</span><span class="p">,</span> <span class="n">viewType</span><span class="p">:</span> <span class="nc">Int</span><span class="p">):</span> <span class="nc">ViewHolder</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">view</span> <span class="p">=</span> <span class="nc">LayoutInflater</span><span class="p">.</span><span class="nf">from</span><span class="p">(</span><span class="n">parent</span><span class="p">.</span><span class="n">context</span><span class="p">)</span>
<span class="p">.</span><span class="nf">inflate</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">movie_item</span><span class="p">,</span> <span class="n">parent</span><span class="p">,</span> <span class="k">false</span><span class="p">)</span>
<span class="k">return</span> <span class="nc">ViewHolder</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onViewRecycled</span><span class="p">(</span><span class="n">holder</span><span class="p">:</span> <span class="nc">ViewHolder</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onViewRecycled</span><span class="p">(</span><span class="n">holder</span><span class="p">)</span>
<span class="n">holder</span><span class="p">.</span><span class="nf">cleanup</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">ViewHolder</span><span class="p">(</span><span class="n">view</span><span class="p">:</span> <span class="nc">View</span><span class="p">)</span> <span class="p">:</span> <span class="nc">RecyclerView</span><span class="p">.</span><span class="nc">ViewHolder</span><span class="p">(</span><span class="n">view</span><span class="p">)</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">onBind</span><span class="p">(</span><span class="n">movie</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// initialise resources and adjust the view to reflect position in the RecyclerView</span>
<span class="p">}</span>
<span class="k">fun</span> <span class="nf">cleanup</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// release heavy resources like bitmaps etc.</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In this example we could for instance download a movie cover image and display it in the <strong>onBindViewHolder</strong> after which we would release the downloaded Bitmap in <strong>onViewRecycled</strong> method to cleanup this heavy object so it does not sit unused in the pool.</p>
<p>In this article we have learned how to clean up resources in <strong>RecyclerView.ViewHolder</strong>!</p>In your journey you might have often wondered when exactly to clean up resources in a RecyclerView ViewHolder. The topic isn’t easy and like most you probably tried to use onViewAttachedToWindow & onViewDetachedFromWindow, only to find it’s not working and gave up hoping you’ll get away with no resource freeing mechanism. Sure there is this 1% of users of your app who get OutofMemoryException from time to time and on older phones scrolling through a list of items looks more like playing Tetris. You didn’t really care until you get a negative review with 500 upvotes saying that your app is almost unusable.RecyclerView optimization using RecycledViewPool2020-11-09T19:50:30+00:002020-11-09T19:50:30+00:00https://androidexplained.github.io//android/ui/2020/11/09/recycler-view-optimization-pool<p>In the previous post we have looked at how to clean up resources in <strong>RecyclerView.ViewHolder</strong> which makes sure we don’t waste memory unnecessarily.
But that’s not all of the things you can do to make <strong>RecyclerView</strong> scrolling more smooth, there are number of things we can still improve.</p>
<p>In this post we will specifically look at optimizations related to <strong>RecyclerView.RecycledViewPool.</strong></p>
<h2 id="recycledviewpool">RecycledViewPool</h2>
<p>So first a bit of theory.
Essentially whenever a downward scroll occurs inside <strong>RecyclerView</strong> the views that disappear on the top are recycled into the <strong>RecycledViewPool</strong> and then reused for the views appearing on the bottom.</p>
<p>Below are a couple of <strong>RecycledViewPool</strong> tricks you can use to optimize <strong>RecyclerView</strong>.</p>
<h2 id="reusing-recycledviewpool-with-many-recyclerviews">Reusing RecycledViewPool with many RecyclerViews</h2>
<p>Lets imagine we have a couple of <strong>RecyclerViews</strong> on the screen displaying the same type of the ViewHolder such as horizontally displayed apps on <strong>Google Play</strong>.</p>
<p><img src="/images/multiple_recyclerviews.png" style="width: 50%; height: 50%" /></p>
<p>By default both visible RecyclerViews will have their own pool which means they are not sharing views.
This leads to many views being unnecessarily being inflated and possibly resulting in laggy behaviour of the horizontal scroll.</p>
<p>In order to avoid that we care share the pool using <a href="https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView#setRecycledViewPool(androidx.recyclerview.widget.RecyclerView.RecycledViewPool)">RecyclerView#setRecycledViewPool</a> method.</p>
<p>There are couple of additional things that we will need to keep in mind</p>
<ul>
<li>
<p>shared ViewHolders need to have the same <strong>ViewType</strong> <br />
This is because ViewHolder is picked from the RecycledViewPool based on the <strong>ViewType</strong> returned from <a href="https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#getItemViewType(int)">RecyclerView.Adapter#getItemViewType</a></p>
</li>
<li>
<p>every <strong>RecyclerView.LayoutManager</strong> that shares the pool needs to have <a href="https://developer.android.com/reference/kotlin/androidx/recyclerview/widget/LinearLayoutManager#setrecyclechildrenondetach">recycleChildrenOnDetach</a> flag set to <strong>true</strong> <br />
As we can read in official documentation <br />
<em>If you are using a RecyclerView.RecycledViewPool, it might be a good idea to set this flag to true so that views will be available to other RecyclerViews immediately.</em></p>
</li>
</ul>
<h2 id="setting-the-optimal-recycledviewpool-size">Setting the optimal RecycledViewPool size</h2>
<p>By default RecycledViewPool can hold 5 ViewHolders of the same ViewType.</p>
<p>Since you might have more items of the same ViewType on the screen it might make sense to increase that number.
For a <strong>ViewHolder</strong> that is more rare and appears on the screen only once you could set the size of the pool to 1.
Otherwise, sooner or later, the pool will be filled with 5 of those rare ViewHolders, 4 of which will just sit unused, wasting precious memory.</p>
<p>We can set the pool size in the following way</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">recyclerView</span><span class="p">.</span><span class="n">recycledViewPool</span><span class="p">.</span><span class="nf">setMaxRecycledViews</span><span class="p">(</span><span class="nc">VIEW_TYPE</span><span class="p">,</span> <span class="nc">POOL_SIZE</span><span class="p">)</span>
</code></pre></div></div>
<p><br /> <br />
That’s all! You can finally enjoy the smooth scrolling experience in your app!<br />
In this article we have learned how to optimize <strong>RecyclerView</strong> using <strong>RecyclerView.RecycledViewPool</strong>!</p>In the previous post we have looked at how to clean up resources in RecyclerView.ViewHolder which makes sure we don’t waste memory unnecessarily. But that’s not all of the things you can do to make RecyclerView scrolling more smooth, there are number of things we can still improve.Improving TextView setText performance2020-10-21T19:50:30+00:002020-10-21T19:50:30+00:00https://androidexplained.github.io//android/ui/2020/10/21/improving-textview-settext-performance<p>In previous post we have looked at how we can improve performance when working with <strong>TextView</strong> and <strong>Spannables</strong>.</p>
<p>In this post we will explore ways to make setting of text on <strong>TextView</strong> faster.</p>
<p>If you look at the source code of <code class="language-plaintext highlighter-rouge">setText</code> method you can see that it does a lot of heavy lifting - there is measuring, drawing and object allocations, all of which run on the main thread. <br />
That is why setting a large amounts of text on a <strong>TextView</strong> might result in UI stutters and hangs, not the smooth experience the user expects.</p>
<p>Fortunately for us there is something that can help us to remediate that - <a href="https://developer.android.com/reference/android/text/PrecomputedText">PrecomputedText</a>, in this post we will look at how we can use it to improve <strong>TextView</strong> performance.</p>
<h2 id="precomputedtext">PrecomputedText</h2>
<p>In simple terms <strong>PrecomputedText</strong> is a text object that contains both the text and layout measurements. When we use it to set text on a <strong>TextView</strong> no further layout calculations are required which is why it will be faster.</p>
<h3 id="textview">TextView</h3>
<p>In order to create <strong>PrecomputedText</strong> we need to pass the text and <strong>TextMetricsParams</strong> of the target <strong>TextView</strong> to <code class="language-plaintext highlighter-rouge">PrecomputedTextCompat.create</code> method.
Since it does heavy text layout measurements its best to call it from a background thread.</p>
<p>In the below code snippet we can see a full working example using <a href="https://github.com/Kotlin/kotlinx.coroutines">Kotlin coroutines</a></p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nc">TextView</span><span class="p">.</span><span class="nf">setTextAsync</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">textView</span> <span class="p">=</span> <span class="k">this</span>
<span class="p">(</span><span class="n">context</span> <span class="k">as</span><span class="p">?</span> <span class="nc">AppCompatActivity</span><span class="p">)</span><span class="o">?.</span><span class="n">lifecycleScope</span><span class="o">?.</span><span class="nf">launch</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">params</span> <span class="p">=</span> <span class="nc">TextViewCompat</span><span class="p">.</span><span class="nf">getTextMetricsParams</span><span class="p">(</span><span class="n">textView</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">precomputedText</span> <span class="p">=</span> <span class="nf">withContext</span><span class="p">(</span><span class="nc">Dispatchers</span><span class="p">.</span><span class="nc">Default</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">PrecomputedTextCompat</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span>
<span class="p">}</span>
<span class="nc">TextViewCompat</span><span class="p">.</span><span class="nf">setPrecomputedText</span><span class="p">(</span><span class="n">textView</span><span class="p">,</span> <span class="n">precomputedText</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now I haven’t measured the performance differences myself.
However according to <a href="https://android-developers.googleblog.com/2018/07/whats-new-for-text-in-android-p.html">Google</a> measuring text can take up to 90% of the time required to set the text.<br />
Thanks to the above code snippet we can do all of that in the background without blocking the main thread.</p>
<h3 id="edittext">EditText</h3>
<p>Since <strong>EditText</strong> is a thin layer on top of <strong>TextView</strong> you might expect the above to work on <strong>EditText</strong> as well.
Well you are wrong.</p>
<p><strong>PrecomputedText</strong> appears to work only with <strong>TextView</strong> and <strong>StaticLayout</strong>. You might try to use <strong>EditText</strong> with the above code snippet but the result will be exactly like calling <code class="language-plaintext highlighter-rouge">setText</code>.</p>
<p>Looking at the source code of <strong>TextView</strong> there is a check in the <code class="language-plaintext highlighter-rouge">setText</code> method</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">type</span> <span class="p">==</span> <span class="nc">BufferType</span><span class="p">.</span><span class="nc">EDITABLE</span> <span class="p">||</span> <span class="nf">getKeyListener</span><span class="p">()</span> <span class="p">!=</span> <span class="k">null</span>
<span class="p">||</span> <span class="n">needEditableForNotification</span><span class="p">)</span> <span class="p">{</span>
<span class="o">..</span><span class="p">.</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If the <code class="language-plaintext highlighter-rouge">type</code> is <code class="language-plaintext highlighter-rouge">BufferType.EDITABLE</code> (<strong>BufferType</strong> used by <strong>EditText</strong>) then the <code class="language-plaintext highlighter-rouge">precomputedText</code> will not be used.</p>
<p>Thanks for reading! <br />
In this post we have learned have to make <code class="language-plaintext highlighter-rouge">setText</code> method of <strong>TextView</strong> faster!</p>In previous post we have looked at how we can improve performance when working with TextView and Spannables.Unit Testing objects with Android API references2020-10-21T19:50:30+00:002020-10-21T19:50:30+00:00https://androidexplained.github.io//android/testing/2020/10/21/android-methods-testing<p>In an ideal world every class in the old codebase would be ready for testing - the dependencies would be provided via the constructor, there would be no Android APIs used inside Presenters…<br />
Sorry to wake you up just now but you’re not living in an ideal world… <br />
The Global pandemic is a thing and people are writing code like there is no tomorrow!</p>
<p>In this article we will talk about what you can do when encountering <strong>Android API</strong> classes inside objects under test.</p>
<h2 id="refactor">Refactor</h2>
<p>The best solution and the one that needs to be considered first is trying to refactor the class itself.
Having <strong>Android API</strong> classes in objects that need to be unit tested is usually a code smell - references to <strong>Android SDK</strong> should, in most circumstances, reside in a given <strong>View</strong> class (<strong>Fragment/Activity</strong>).</p>
<p>If you’re familiar with <strong>MVP</strong> or <strong>MVVM</strong> architecture you probably know that <strong>ViewModels</strong> and <strong>Presenters</strong> should not need to know about details of showing the UI.
This allows for a separation of concerns and more testable, easier to understand code.</p>
<p>So in most cases you should try to refactor the code so that it doesn’t contain any Android framework references.
It shouldn’t be too hard and you will thank me later on.</p>
<h2 id="workaround">Workaround</h2>
<p>As I mentioned earlier we’re not living in the ideal world and in some circumstances you won’t be able to refactor the class itself.<br />
I remember that at one point in my career I was tasked with improving the test coverage of our codebase.
Despite argumentation management did not allow for refactoring, they just wanted to improve testing coverage fast, probably so it looks good on slides.<br />
In these cases well… you need to suck it up and get your hands dirty!</p>
<p>For the purpose of the article I will use a very simple <strong>MainViewModel</strong> class along with <strong>android.util.Base64</strong> reference.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">android.util.Base64</span>
<span class="kd">class</span> <span class="nc">MainViewModel</span> <span class="p">:</span> <span class="nc">ViewModel</span><span class="p">()</span> <span class="p">{</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">mode</span> <span class="p">=</span> <span class="nc">Base64</span><span class="p">.</span><span class="nc">DEFAULT</span>
<span class="k">fun</span> <span class="nf">encodeData</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">ByteArray</span> <span class="p">{</span>
<span class="k">return</span> <span class="nc">Base64</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">text</span><span class="p">.</span><span class="nf">toByteArray</span><span class="p">(),</span> <span class="n">mode</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The unit test will look like the following</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">java.util.Base64</span>
<span class="k">internal</span> <span class="kd">class</span> <span class="nc">MainViewModelTest</span> <span class="p">{</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">mainViewModel</span> <span class="p">=</span> <span class="nc">MainViewModel</span><span class="p">()</span>
<span class="nd">@Test</span>
<span class="k">fun</span> <span class="nf">`test</span> <span class="nf">encoding`</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">text</span> <span class="p">=</span> <span class="s">"text"</span>
<span class="kd">val</span> <span class="py">encodedText</span> <span class="p">=</span> <span class="n">mainViewModel</span><span class="p">.</span><span class="nf">encodeData</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">decodedText</span> <span class="p">=</span> <span class="nc">String</span><span class="p">(</span><span class="nc">Base64</span><span class="p">.</span><span class="nf">getDecoder</span><span class="p">().</span><span class="nf">decode</span><span class="p">(</span><span class="n">encodedText</span><span class="p">))</span>
<span class="nf">assert</span><span class="p">(</span><span class="n">text</span> <span class="p">==</span> <span class="n">decodedText</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now if you try to run the code you might see the following errors</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">java</span><span class="p">.</span><span class="n">lang</span><span class="p">.</span><span class="nc">NullPointerException</span><span class="p">:</span> <span class="nc">Base64</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">text</span><span class="p">.</span><span class="nf">toByteArray</span><span class="p">(),</span> <span class="n">mode</span><span class="p">)</span> <span class="n">must</span> <span class="n">not</span> <span class="n">be</span> <span class="k">null</span>
</code></pre></div></div>
<p>or</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">java</span><span class="p">.</span><span class="n">lang</span><span class="p">.</span><span class="nc">NoClassDefFoundError</span><span class="p">:</span> <span class="n">android</span><span class="p">/</span><span class="n">util</span><span class="p">/</span><span class="nc">Base64</span>
</code></pre></div></div>
<p>That happens because tests don’t have access to Android framework classes. <br />
Now lets see whether there is something we can do to fix the error without refactoring <strong>MainViewModel</strong>.</p>
<h3 id="create-local-version-of-the-android-api-reference">Create local version of the Android API reference</h3>
<p>As mentioned by default the test throws an error when some code under test is not available.
However we can workaround this by simply creating our local version of the class that will return what we want.</p>
<p>In our case the missing class is <strong>android.util.Base64</strong> and its <strong>encode</strong> method.</p>
<p>Therefore we can simply create <strong>Base64</strong> class inside <code class="language-plaintext highlighter-rouge">android.util</code> package which we will place under test directory.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="nn">android.util;</span>
<span class="k">public</span> <span class="kd">class</span> <span class="nc">Base64</span> <span class="p">{</span>
<span class="k">public</span> <span class="n">static</span> <span class="n">byte</span><span class="p">[]</span> <span class="nf">encode</span><span class="p">(</span><span class="n">byte</span><span class="p">[]</span> <span class="n">input</span><span class="p">,</span> <span class="n">int</span> <span class="n">flags</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">java</span><span class="p">.</span><span class="n">util</span><span class="p">.</span><span class="nc">Base64</span><span class="p">.</span><span class="nf">getEncoder</span><span class="p">().</span><span class="nf">encode</span><span class="p">(</span><span class="n">input</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now the test will pass since the class will be available.
That’s it!</p>
<p>In this article we’ve learned what to do when encountering Android API references inside objects under test.</p>In an ideal world every class in the old codebase would be ready for testing - the dependencies would be provided via the constructor, there would be no Android APIs used inside Presenters… Sorry to wake you up just now but you’re not living in an ideal world… The Global pandemic is a thing and people are writing code like there is no tomorrow!Hexadecimal color code for transparency2020-10-12T19:50:30+00:002020-10-12T19:50:30+00:00https://androidexplained.github.io//android/ui/2020/10/12/hex-color-code-transparency<p>During the lifetime of every android developer there comes a time when he has to use color transparency on android.
Whether it’s to add an overlay to bring something into focus or simply create an ugly custom view that is a part of business requirements.</p>
<p>In this post we’ll cover how the color transparency works on android.</p>
<h2 id="understanding-color-transparency">Understanding color transparency</h2>
<p>On android the color can be declared in the following format <br />
#<strong>AA</strong>RRGGBB</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">AA</code> - is the bit that’s of most interest to us in this article, it stands for <strong>alpha</strong> channel</li>
<li><code class="language-plaintext highlighter-rouge">RR GG BB</code> - <strong>red</strong>, <strong>green</strong> & <strong>blue</strong> channels respectively</li>
</ul>
<p>It is also worth noting that each channel is represented as a hexadecimal number.</p>
<p>Now in order to add transparency to our color we need to prepend it with hexadecimal value representing the alpha (transparency).</p>
<p>For example if you want to set <strong>80%</strong> transparency value to black color (<strong>#000000</strong>) you need to prepend it with <strong>CC</strong>, as a result we end up with the following color resource <strong>#CC000000</strong>.</p>
<p>Below please find a list of all hexadecimal values for 0 to 100% transparency.</p>
<style type="text/css">
.tg {border-collapse:collapse;border-spacing:0;width: 40%}
.tg td{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
overflow:hidden;padding:10px 5px;word-break:normal;}
.tg th{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px;
font-weight:normal;overflow:hidden;padding:10px 5px;word-break:normal;}
.tg .tg-c3ow{border-color:inherit;text-align:center;vertical-align:top}
.tg .tg-dvpl{border-color:inherit;text-align:center;vertical-align:top}
</style>
<table class="tg">
<thead>
<tr>
<th class="tg-dvpl"><b>Transparency</b></th>
<th class="tg-c3ow"><b>Hex value</b></th>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-dvpl">100%</td>
<td class="tg-c3ow"><span>FF</span></td>
</tr>
<tr>
<td class="tg-dvpl">99%</td>
<td class="tg-c3ow"><span>FC</span></td>
</tr>
<tr>
<td class="tg-dvpl">98%</td>
<td class="tg-c3ow"><span>FA</span></td>
</tr>
<tr>
<td class="tg-dvpl">97%</td>
<td class="tg-c3ow"><span>F7</span></td>
</tr>
<tr>
<td class="tg-dvpl">96%</td>
<td class="tg-c3ow"><span>F5</span></td>
</tr>
<tr>
<td class="tg-dvpl">95%</td>
<td class="tg-c3ow"><span>F2</span></td>
</tr>
<tr>
<td class="tg-dvpl">94%</td>
<td class="tg-c3ow"><span>F0</span></td>
</tr>
<tr>
<td class="tg-dvpl">93%</td>
<td class="tg-c3ow"><span>ED</span></td>
</tr>
<tr>
<td class="tg-dvpl">92%</td>
<td class="tg-c3ow"><span>EB</span></td>
</tr>
<tr>
<td class="tg-dvpl">91%</td>
<td class="tg-c3ow"><span>E8</span></td>
</tr>
<tr>
<td class="tg-dvpl">90%</td>
<td class="tg-c3ow"><span>E6</span></td>
</tr>
<tr>
<td class="tg-dvpl">89%</td>
<td class="tg-c3ow"><span>E3</span></td>
</tr>
<tr>
<td class="tg-dvpl">88%</td>
<td class="tg-c3ow"><span>E0</span></td>
</tr>
<tr>
<td class="tg-dvpl">87%</td>
<td class="tg-c3ow"><span>DE</span></td>
</tr>
<tr>
<td class="tg-dvpl">86%</td>
<td class="tg-c3ow"><span>DB</span></td>
</tr>
<tr>
<td class="tg-dvpl">85%</td>
<td class="tg-c3ow"><span>D9</span></td>
</tr>
<tr>
<td class="tg-dvpl">84%</td>
<td class="tg-c3ow"><span>D6</span></td>
</tr>
<tr>
<td class="tg-dvpl">83%</td>
<td class="tg-c3ow"><span>D4</span></td>
</tr>
<tr>
<td class="tg-dvpl">82%</td>
<td class="tg-c3ow"><span>D1</span></td>
</tr>
<tr>
<td class="tg-dvpl">81%</td>
<td class="tg-c3ow"><span>CF</span></td>
</tr>
<tr>
<td class="tg-dvpl">80%</td>
<td class="tg-c3ow"><span>CC</span></td>
</tr>
<tr>
<td class="tg-dvpl">79%</td>
<td class="tg-c3ow"><span>C9</span></td>
</tr>
<tr>
<td class="tg-dvpl">78%</td>
<td class="tg-c3ow"><span>C7</span></td>
</tr>
<tr>
<td class="tg-dvpl">77%</td>
<td class="tg-c3ow"><span>C4</span></td>
</tr>
<tr>
<td class="tg-dvpl">76%</td>
<td class="tg-c3ow"><span>C2</span></td>
</tr>
<tr>
<td class="tg-dvpl">75%</td>
<td class="tg-c3ow"><span>BF</span></td>
</tr>
<tr>
<td class="tg-dvpl">74%</td>
<td class="tg-c3ow"><span>BD</span></td>
</tr>
<tr>
<td class="tg-dvpl">73%</td>
<td class="tg-c3ow"><span>BA</span></td>
</tr>
<tr>
<td class="tg-dvpl">72%</td>
<td class="tg-c3ow"><span>B8</span></td>
</tr>
<tr>
<td class="tg-dvpl">71%</td>
<td class="tg-c3ow"><span>B5</span></td>
</tr>
<tr>
<td class="tg-dvpl">70%</td>
<td class="tg-c3ow"><span>B3</span></td>
</tr>
<tr>
<td class="tg-dvpl">69%</td>
<td class="tg-c3ow"><span>B0</span></td>
</tr>
<tr>
<td class="tg-dvpl">68%</td>
<td class="tg-c3ow"><span>AD</span></td>
</tr>
<tr>
<td class="tg-dvpl">67%</td>
<td class="tg-c3ow"><span>AB</span></td>
</tr>
<tr>
<td class="tg-dvpl">66%</td>
<td class="tg-c3ow"><span>A8</span></td>
</tr>
<tr>
<td class="tg-dvpl">65%</td>
<td class="tg-c3ow"><span>A6</span></td>
</tr>
<tr>
<td class="tg-dvpl">64%</td>
<td class="tg-c3ow"><span>A3</span></td>
</tr>
<tr>
<td class="tg-dvpl">63%</td>
<td class="tg-c3ow"><span>A1</span></td>
</tr>
<tr>
<td class="tg-dvpl">62%</td>
<td class="tg-c3ow"><span>9E</span></td>
</tr>
<tr>
<td class="tg-dvpl">61%</td>
<td class="tg-c3ow"><span>9C</span></td>
</tr>
<tr>
<td class="tg-dvpl">60%</td>
<td class="tg-c3ow"><span>99</span></td>
</tr>
<tr>
<td class="tg-dvpl">59%</td>
<td class="tg-c3ow"><span>96</span></td>
</tr>
<tr>
<td class="tg-dvpl">58%</td>
<td class="tg-c3ow"><span>94</span></td>
</tr>
<tr>
<td class="tg-dvpl">57%</td>
<td class="tg-c3ow"><span>91</span></td>
</tr>
<tr>
<td class="tg-dvpl">56%</td>
<td class="tg-c3ow"><span>8F</span></td>
</tr>
<tr>
<td class="tg-dvpl">55%</td>
<td class="tg-c3ow"><span>8C</span></td>
</tr>
<tr>
<td class="tg-dvpl">54%</td>
<td class="tg-c3ow"><span>8A</span></td>
</tr>
<tr>
<td class="tg-dvpl">53%</td>
<td class="tg-c3ow"><span>87</span></td>
</tr>
<tr>
<td class="tg-dvpl">52%</td>
<td class="tg-c3ow"><span>85</span></td>
</tr>
<tr>
<td class="tg-dvpl">51%</td>
<td class="tg-c3ow"><span>82</span></td>
</tr>
<tr>
<td class="tg-dvpl">50%</td>
<td class="tg-c3ow"><span>80</span></td>
</tr>
<tr>
<td class="tg-dvpl">49%</td>
<td class="tg-c3ow"><span>7D</span></td>
</tr>
<tr>
<td class="tg-dvpl">48%</td>
<td class="tg-c3ow"><span>7A</span></td>
</tr>
<tr>
<td class="tg-dvpl">47%</td>
<td class="tg-c3ow"><span>78</span></td>
</tr>
<tr>
<td class="tg-dvpl">46%</td>
<td class="tg-c3ow"><span>75</span></td>
</tr>
<tr>
<td class="tg-dvpl">45%</td>
<td class="tg-c3ow"><span>73</span></td>
</tr>
<tr>
<td class="tg-dvpl">44%</td>
<td class="tg-c3ow"><span>70</span></td>
</tr>
<tr>
<td class="tg-dvpl">43%</td>
<td class="tg-c3ow"><span>6E</span></td>
</tr>
<tr>
<td class="tg-dvpl">42%</td>
<td class="tg-c3ow"><span>6B</span></td>
</tr>
<tr>
<td class="tg-dvpl">41%</td>
<td class="tg-c3ow"><span>69</span></td>
</tr>
<tr>
<td class="tg-dvpl">40%</td>
<td class="tg-c3ow"><span>66</span></td>
</tr>
<tr>
<td class="tg-dvpl">39%</td>
<td class="tg-c3ow"><span>63</span></td>
</tr>
<tr>
<td class="tg-dvpl">38%</td>
<td class="tg-c3ow"><span>61</span></td>
</tr>
<tr>
<td class="tg-dvpl">37%</td>
<td class="tg-c3ow"><span>5E</span></td>
</tr>
<tr>
<td class="tg-dvpl">36%</td>
<td class="tg-c3ow"><span>5C</span></td>
</tr>
<tr>
<td class="tg-dvpl">35%</td>
<td class="tg-c3ow"><span>59</span></td>
</tr>
<tr>
<td class="tg-dvpl">34%</td>
<td class="tg-c3ow"><span>57</span></td>
</tr>
<tr>
<td class="tg-dvpl">33%</td>
<td class="tg-c3ow"><span>54</span></td>
</tr>
<tr>
<td class="tg-dvpl">32%</td>
<td class="tg-c3ow"><span>52</span></td>
</tr>
<tr>
<td class="tg-dvpl">31%</td>
<td class="tg-c3ow"><span>4F</span></td>
</tr>
<tr>
<td class="tg-dvpl">30%</td>
<td class="tg-c3ow"><span>4D</span></td>
</tr>
<tr>
<td class="tg-dvpl">29%</td>
<td class="tg-c3ow"><span>4A</span></td>
</tr>
<tr>
<td class="tg-dvpl">28%</td>
<td class="tg-c3ow"><span>47</span></td>
</tr>
<tr>
<td class="tg-dvpl">27%</td>
<td class="tg-c3ow"><span>45</span></td>
</tr>
<tr>
<td class="tg-dvpl">26%</td>
<td class="tg-c3ow"><span>42</span></td>
</tr>
<tr>
<td class="tg-dvpl">25%</td>
<td class="tg-c3ow"><span>40</span></td>
</tr>
<tr>
<td class="tg-dvpl">24%</td>
<td class="tg-c3ow"><span>3D</span></td>
</tr>
<tr>
<td class="tg-dvpl">23%</td>
<td class="tg-c3ow"><span>3B</span></td>
</tr>
<tr>
<td class="tg-dvpl">22%</td>
<td class="tg-c3ow"><span>38</span></td>
</tr>
<tr>
<td class="tg-dvpl">21%</td>
<td class="tg-c3ow"><span>36</span></td>
</tr>
<tr>
<td class="tg-dvpl">20%</td>
<td class="tg-c3ow"><span>33</span></td>
</tr>
<tr>
<td class="tg-dvpl">19%</td>
<td class="tg-c3ow"><span>30</span></td>
</tr>
<tr>
<td class="tg-dvpl">18%</td>
<td class="tg-c3ow"><span>2E</span></td>
</tr>
<tr>
<td class="tg-dvpl">17%</td>
<td class="tg-c3ow"><span>2B</span></td>
</tr>
<tr>
<td class="tg-dvpl">16%</td>
<td class="tg-c3ow"><span>29</span></td>
</tr>
<tr>
<td class="tg-dvpl">15%</td>
<td class="tg-c3ow"><span>26</span></td>
</tr>
<tr>
<td class="tg-dvpl">14%</td>
<td class="tg-c3ow"><span>24</span></td>
</tr>
<tr>
<td class="tg-dvpl">13%</td>
<td class="tg-c3ow"><span>21</span></td>
</tr>
<tr>
<td class="tg-dvpl">12%</td>
<td class="tg-c3ow"><span>1F</span></td>
</tr>
<tr>
<td class="tg-dvpl">11%</td>
<td class="tg-c3ow"><span>1C</span></td>
</tr>
<tr>
<td class="tg-dvpl">10%</td>
<td class="tg-c3ow"><span>1A</span></td>
</tr>
<tr>
<td class="tg-dvpl">9%</td>
<td class="tg-c3ow"><span>17</span></td>
</tr>
<tr>
<td class="tg-dvpl">8%</td>
<td class="tg-c3ow"><span>14</span></td>
</tr>
<tr>
<td class="tg-dvpl">7%</td>
<td class="tg-c3ow"><span>12</span></td>
</tr>
<tr>
<td class="tg-dvpl">6%</td>
<td class="tg-c3ow"><span>0F</span></td>
</tr>
<tr>
<td class="tg-dvpl">5%</td>
<td class="tg-c3ow"><span>0D</span></td>
</tr>
<tr>
<td class="tg-dvpl">4%</td>
<td class="tg-c3ow"><span>0A</span></td>
</tr>
<tr>
<td class="tg-dvpl">3%</td>
<td class="tg-c3ow"><span>08</span></td>
</tr>
<tr>
<td class="tg-dvpl">2%</td>
<td class="tg-c3ow"><span>05</span></td>
</tr>
<tr>
<td class="tg-dvpl">1%</td>
<td class="tg-c3ow"><span>03</span></td>
</tr>
<tr>
<td class="tg-dvpl">0%</td>
<td class="tg-c3ow"><span>00</span></td>
</tr>
</tbody>
</table>
<p>In this post we’ve learned how color transparency works on android.</p>During the lifetime of every android developer there comes a time when he has to use color transparency on android. Whether it’s to add an overlay to bring something into focus or simply create an ugly custom view that is a part of business requirements.Improving TextView & Spannable Performance2020-10-12T19:50:30+00:002020-10-12T19:50:30+00:00https://androidexplained.github.io//android/ui/2020/10/12/improving-spannable-performance<p>You probably encountered <strong>Spannables</strong> at some point in your android journey but just to remind you, they are used in order to apply styling to text.</p>
<p>A fact that you might not have known is that there are a lot of span types such as</p>
<ul>
<li><a href="https://developer.android.com/reference/android/text/style/ForegroundColorSpan">ForegroundColorSpan</a></li>
<li><a href="https://developer.android.com/reference/android/text/style/ImageSpan">ImageSpan</a></li>
<li><a href="https://developer.android.com/reference/android/text/style/ClickableSpan">ClickableSpan</a></li>
<li>and <a href="https://developer.android.com/reference/android/text/style/package-summary">many more …</a></li>
</ul>
<p>In this article we will look at how to use <strong>Spannables</strong> in a more optimized way!</p>
<h3 id="the-problem">The Problem</h3>
<p>In one of my apps named <a href="https://play.google.com/store/apps/details?id=web.dassem.websiteanalyzer">HTML/CSS Website Inspector</a> I’ve had to implement a search feature - the user could search through website’s source code and a given query would be highlighted.<br />
In order to highlight search results I’ve decided to use <a href="https://developer.android.com/reference/android/text/style/BackgroundColorSpan">BackgroundColorSpan</a>.</p>
<p>The problem that I encountered was that updating <strong>Spannable</strong> on search query change was very slow and caused UI lags.<br />
This was probably because I was working with a lot of text - the website’s source code can sometimes be massive. In order to view source code of <strong>google.com</strong> you can simply type <code class="language-plaintext highlighter-rouge">view-source:https://www.google.com/</code> in <strong>Chrome</strong> browser address bar. And yes that’s a lot of text!</p>
<p>It appears that the below code was the one to blame</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">editText</span><span class="p">.</span><span class="nf">setText</span><span class="p">(</span><span class="n">updatedSpannable</span><span class="p">)</span>
</code></pre></div></div>
<p>On every search query change I would update <code class="language-plaintext highlighter-rouge">editText</code> using <code class="language-plaintext highlighter-rouge">setText</code> method passing my updated <strong>Spannable</strong> string which caused UI hangs. <br /></p>
<h3 id="the-solution">The Solution</h3>
<p>But worry not, there is a more optimal way to do this.</p>
<p>But first some explanation, whenever we call <code class="language-plaintext highlighter-rouge">setText</code> there is a lot happening behind the scenes - measuring, drawing and extra objects are being created.<br />
In order to improve performance we basically can’t call it too often and we don’t need to really - here is how we can work with <strong>Spannable</strong> and use <code class="language-plaintext highlighter-rouge">setText</code> method only once.</p>
<p>First we need to set the text as <strong>Spannable</strong> to <code class="language-plaintext highlighter-rouge">editText</code></p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">editText</span><span class="p">.</span><span class="nf">setText</span><span class="p">(</span><span class="n">spannableString</span><span class="p">,</span> <span class="nc">BufferType</span><span class="p">.</span><span class="nc">SPANNABLE</span><span class="p">)</span>
</code></pre></div></div>
<p>After that whenever we need to edit a <strong>Span</strong> we can simply retrieve it from <code class="language-plaintext highlighter-rouge">editText</code> by calling</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">spannable</span> <span class="p">=</span> <span class="n">editText</span><span class="p">.</span><span class="n">text</span> <span class="k">as</span> <span class="nc">Spannable</span>
</code></pre></div></div>
<p>Any changes to the Spannable object will automatically be reflected in the UI - in my case <code class="language-plaintext highlighter-rouge">editText</code>. <br />
That’s it! No need to call <code class="language-plaintext highlighter-rouge">setText</code> again!</p>
<p>In this post we have learned how to use <strong>Spannables</strong> in a more optimized way.</p>You probably encountered Spannables at some point in your android journey but just to remind you, they are used in order to apply styling to text.Indie Dev App Launch Checklist2020-10-07T19:50:30+00:002020-10-07T19:50:30+00:00https://androidexplained.github.io//android/2020/10/07/indie-dev-app-checklist<p>This post will be a little different… <br />
I won’t make this a secret - I am an indie developer. In my lifetime I have created probably around 35 apps.<br />
You can have a look at <a href="https://play.google.com/store/apps/dev?id=6178411479367698437">my developer account</a> - right now I have closer to 10 apps since not all of them proved successful enough for me to keep supporting them.</p>
<p>After making all these apps I have found that some things worked for me and some didn’t.<br />
In this post I will share all of the things I’m doing whenever launching an app.</p>
<p>Since there is a lot of things I want to talk about I will group them into two main categories</p>
<ul>
<li>App Implementation Practices - things that will make the user want to continue using the app</li>
<li>ASO (App Store Optimization) - basically all of the things that will get users to install it in the first place</li>
</ul>
<h2 id="right-idea">Right Idea</h2>
<p>So you might think you have an great and original idea for an app…<br />
First thing I would advise is to check out the potential competition - if there are already apps with similar functionality your app might not be a great success.<br />
However don’t give up just yet, maybe the existing apps are limited or you have an idea on how to do things better - if that’s the case I encourage you to turn your idea into an app!<br />
Don’t get discouraged because the existing apps already have millions of downloads, if your app is better it will eventually surface, it will just take time depending on how saturated a given market is.</p>
<h2 id="app-implementation-practices">App Implementation Practices</h2>
<p>These are the things that I do for every project - usually at the start I will have an MVP (Minimum viable product) functionality and use below practices in order to increase overall app’s quality.</p>
<h3 id="app-icon">App Icon</h3>
<p>The app icon is the first thing the user sees when he browses the Playstore. If it looks poor the user will be less likely to visit the Playstore listing and effectively install the app.</p>
<p>For a <strong>Tool</strong> type of an app I recommend choosing a <a href="https://material.io/resources/icons/?style=baseline">Material Design Icon</a> along with primary app color for the background.<br />
For creation of the in-app launcher icon I am usually using <a href="https://developer.android.com/studio/write/image-asset-studio">Image Asset Studio</a>, since it already supports adaptive icons which are recommended for Android Oreo and above. <br />
For the Playstore listing itself I encourage you to use <a href="https://romannurik.github.io/AndroidAssetStudio/icons-launcher.html">Android Asset Studio</a>, this will allow you to add a nice-looking shadow to your app icon.</p>
<p>Now for a <strong>Game</strong> I would probably go with something more custom, if you have no graphic design skills you can probably pay someone couple of bucks to create the icon for you. For that you can use websites such as <a href="https://www.upwork.com">Upwork</a> or <a href="https://www.freelancer.com">Freelancer</a>.</p>
<h3 id="splash-screen">Splash screen</h3>
<p>Did you know that there is some time between app launch and when the user can start interacting with the user interface?<br />
It’s called a <strong>Cold Start</strong> - a great way to give the appearance of faster loading time and to improve the UX is to show a splash screen during that time.
It doesn’t have to be anything fancy - a simple centered app logo will do the trick.</p>
<p>It also requires very little effort to implement and you can learn how to do it <a href="https://medium.com/@shishirthedev/the-right-way-to-implement-a-splash-screen-in-android-acae0e52949a">the right way here</a>.</p>
<h3 id="material-design-user-interface">Material Design User interface</h3>
<p>No matter how good the app is if the UI is poor the users will be reluctant to use it.<br />
I’m a programmer so I know very little about design. Fortunately for us developers Google has created an <a href="https://material.io/">Material Design guide</a> on how to build user interfaces. Whenever there is a piece of UI I need to develop I simply visit their website - they have covered most of the common use-cases that you will encounter.</p>
<h3 id="dark-theme">Dark theme</h3>
<p>There are not many things that annoy me more than an app that doesn’t support dark theme.<br />
Sometimes I’m using my phone during the evening and there it is, this one app that doesn’t support dark theme - I am instantly blinded and lose sight for couple of seconds. Following my survival instincts I’m immediately uninstalling the aggressor.</p>
<p>You might think it is not that important but trust me on this, just do it.<br />
I wrote a couple of articles on how to add <strong>Dark Theme</strong> support to your app</p>
<ul>
<li><a href="https://androidexplained.github.io/ui/android/material-design/2020/09/24/dark-mode.html">App Dark Theme Support</a></li>
<li><a href="https://androidexplained.github.io/ui/android/material-design/2020/09/24/dark-mode-webview.html">Webview Dark Theme Support</a></li>
</ul>
<h3 id="obfuscation">Obfuscation</h3>
<p>Now I don’t care that you haven’t used Proguard/R8 before - obfuscating your code requires so little effort, especially if your project is small, that there are literally no excuses for not doing it.</p>
<p>If your app gets successful you’ll thank me for it later on - expect to have 15 clones on the store in no time. Obfuscation might not solve the problem entirely but at least it will discourage 90% of copycats out there.</p>
<p>Oh and did I mention it will also make your app smaller and faster?<br />
Yeah you get all that for a humble couple lines of code added to your <code class="language-plaintext highlighter-rouge">build.gradle</code></p>
<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">buildTypes</span> <span class="o">{</span>
<span class="n">release</span> <span class="o">{</span>
<span class="n">minifyEnabled</span> <span class="kc">true</span>
<span class="n">shrinkResources</span> <span class="kc">true</span>
<span class="n">proguardFiles</span> <span class="nf">getDefaultProguardFile</span><span class="o">(</span><span class="s1">'proguard-android-optimize.txt'</span><span class="o">),</span> <span class="s1">'proguard-rules.pro'</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="aso---app-store-optimization">ASO - App Store Optimization</h2>
<p>After going through above practices you can be sure that your app is simply awesome. This doesn’t mean we’re finished yet. Since this is a new app the biggest problem you will face is getting users to notice it. Fortunately for you I will cover this as well.</p>
<h3 id="store-listing">Store Listing</h3>
<p>Store listing is basically what the user sees after going to your app details page on the Playstore.
It consists of the app description, screenshots, promo graphic etc.</p>
<h4 id="keywords">Keywords</h4>
<p>In order for your app to get discovered you need to use keywords relevant to your app functionality. <br />
The basic idea is to use the most important keywords in your app description <strong>4-5 times</strong>.
Additionally you can try to include one main keyword in your app name.<br />
When it comes to finding the right keywords I use <a href="https://keywordtool.io/">KeywordTool</a> & <a href="https://searchman.com/">SearchMan</a>, both have some limited functionality for free which should be enough for one app.</p>
<h4 id="screenshots">Screenshots</h4>
<p>Play listing screenshots allow the user to see how your app looks like without actually installing it. <br />
Without graphic designer skills it is very hard to get this part right. However there are some free tools out there that can allow you to create a very nice screenshot graphics. <br />
I really recommend <a href="https://app-mockup.com/android/#">App-mockup</a>, in my opinion it is the best mockup tool out there for free.</p>
<h3 id="landing-page">Landing Page</h3>
<p>To increase your visibility on the web I recommend to creating an app landing page. <br />
Now as an indie developer it’s natural that I don’t like spending money on an app that might never become a success.
Don’t worry, there are ways you can set up an landing page for free and without too much effort.<br />
You probably can find ways to do this on-line, personally I very much like using <a href="https://pages.github.com/">Github Pages</a> along with <a href="https://jekyllrb.com/">Jekyll</a>. You can find many app landing page themes on <a href="https://jekyllthemes.io/jekyll-landing-page-themes">Jekyll Themes Website</a>.</p>
<h3 id="marketing">Marketing</h3>
<p>After your app is released it will probably take some time till it shows up in any Playstore searches. To help it gain visibility faster we need to help it a little by posting the app all over the internet. <br />
The places that I always think about</p>
<ul>
<li>Forums & Subreddits relevant to your app or related to app development in general</li>
<li>Answering questions on Quora & StackOverflow if there is a problem that your app solves</li>
<li>Social media - I don’t use it as much now since I’ve abused this way in the past and some of my friends delicately suggested that they are not fine with me annoying them with a new app each day</li>
</ul>
<h4 id="localization">Localization</h4>
<p>Now you may already have some userbase. If you see that you have a lot of users in some particular country you may want to localize your play store listing and the app itself to gain even stronger foothold in that location.
Currently for localization I’m using <a href="https://support.google.com/googleplay/android-developer/answer/3125566?hl=en">app translation service</a> from Google Developer Console, however you might find it cheaper to use <a href="https://www.upwork.com">Upwork</a> or <a href="https://www.freelancer.com">Freelancer</a>.</p>
<h2 id="summary">Summary</h2>
<p>After going through all that I can ensure you that you did almost all you can in order to ensure your app’s success. The rest is in the hands of lady Fortune.</p>
<p>If there is one last thing I can advise it would be “not to put all eggs in one basket” - by releasing many apps you increase your chances of succeeding.</p>
<p>In this post we have learned what to focus on when releasing a new app.</p>This post will be a little different… I won’t make this a secret - I am an indie developer. In my lifetime I have created probably around 35 apps. You can have a look at my developer account - right now I have closer to 10 apps since not all of them proved successful enough for me to keep supporting them.Room Database - Backup & Restore Features2020-10-03T11:50:30+00:002020-10-03T11:50:30+00:00https://androidexplained.github.io//android/room/2020/10/03/room-backup-restore<p>Not that long ago I wanted to implement Backup & Restore features in my new application called <a href="https://play.google.com/store/apps/details?id=com.notepad.notably">Notably Notepad</a>. <br />
I thought such basic features will be well-documented and supported - I mean every app containing a database would want to allow the user to move his data to a new phone. <br />
Yet again I was wrong, there is very little information about how to perform backup of <strong>Room Database</strong> and nothing in official android documentation.<br /></p>
<p>The information that is available is incomplete, at least in my case, it wasn’t working - I had to compile information from many sources in order to get my code working. <br /> <br />
Which why I wrote this article, hopefully someone will find it helpful.</p>
<h2 id="backup-database">Backup Database</h2>
<p>In order for the backup feature to work as expected the following is necessary</p>
<ul>
<li>Database is created in <strong>Truncated</strong> journal mode</li>
<li><strong>Checkpoint query</strong> has been executed on the database</li>
<li>Backup file has been saved on in shared storage</li>
</ul>
<h3 id="database-is-created-in-truncated-journal-mode">Database is created in <strong>Truncated</strong> journal mode</h3>
<p>First thing that needs to be done is to create the database with appropriate journal mode.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Room</span><span class="p">.</span><span class="nf">databaseBuilder</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="nc">AppDatabase</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="nf">setJournalMode</span><span class="p">(</span><span class="nc">RoomDatabase</span><span class="p">.</span><span class="nc">JournalMode</span><span class="p">.</span><span class="nc">TRUNCATE</span><span class="p">)</span>
<span class="p">.</span><span class="nf">build</span><span class="p">()</span>
</code></pre></div></div>
<p>When it comes to journal modes there are three options available:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">JournalMode.AUTOMATIC</code> - default mode, <strong>Room</strong> will automatically choose journal modes based on the API and available device RAM</li>
<li><code class="language-plaintext highlighter-rouge">JournalMode.TRUNCATE</code> - stores the database in one file</li>
<li><code class="language-plaintext highlighter-rouge">JournalMode.WRITE_AHEAD_LOGGING</code> - in this mode your database is stored inside two files - a database file and write ahead log file (database_name.wal) file making it impossible for you to back up your database to one file</li>
</ul>
<p>It is important that the database is created with <code class="language-plaintext highlighter-rouge">JournalMode.TRUNCATE</code> mode, since other modes might use two files for database storage - for user & implementation convenience it is much easier to work with one file.</p>
<h3 id="checkpoint-query-has-been-executed-on-the-database"><strong>Checkpoint query</strong> has been executed on the database</h3>
<p>When the option to create a database backup is chosen by the user the following <strong>checkpoint query</strong> needs to be executed to ensure all of the pending transactions are applied.</p>
<p>For this a following method needs to be added to the database <code class="language-plaintext highlighter-rouge">Dao</code> interface</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">UserDao</span> <span class="p">{</span>
<span class="nd">@RawQuery</span>
<span class="k">fun</span> <span class="nf">checkpoint</span><span class="p">(</span><span class="n">supportSQLiteQuery</span><span class="p">:</span> <span class="nc">SupportSQLiteQuery</span><span class="p">?):</span> <span class="nc">Single</span><span class="p"><</span><span class="nc">Int</span><span class="p">></span>
<span class="p">}</span>
</code></pre></div></div>
<p>Then the method needs to be called with the following <strong>SQL query</strong></p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">userDao</span><span class="p">.</span><span class="nf">checkpoint</span><span class="p">((</span><span class="nc">SimpleSQLiteQuery</span><span class="p">(</span><span class="s">"pragma wal_checkpoint(full)"</span><span class="p">)))</span>
</code></pre></div></div>
<p>Once the checkpoint method has succeeded the database backup file can finally be saved.</p>
<h3 id="backup-file-has-been-saved-on-in-shared-storage">Backup file has been saved on in shared storage</h3>
<p>The database backup file can be retrieved using the following code</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">File</span><span class="p">(</span><span class="n">database</span><span class="p">.</span><span class="n">openHelper</span><span class="p">.</span><span class="n">writableDatabase</span><span class="p">.</span><span class="n">path</span><span class="p">)</span>
</code></pre></div></div>
<p>The file then needs to be copied into the location picked by the user.</p>
<h2 id="restore-database">Restore Database</h2>
<p>Now that the database is backed up to a file it is possible to restore it.</p>
<p>There are number of ways you can allow the user to pick the backup file (any file picker library, Storage Access Framework). <br />
In my case <a href="https://developer.android.com/guide/topics/providers/document-provider">Storage Access Framework</a> was used</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">tryOpenFile</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">intentType</span> <span class="p">=</span> <span class="s">"application/octet-stream"</span>
<span class="kd">val</span> <span class="py">intent</span> <span class="p">=</span> <span class="nc">Intent</span><span class="p">(</span><span class="nc">Intent</span><span class="p">.</span><span class="nc">ACTION_OPEN_DOCUMENT</span><span class="p">).</span><span class="nf">apply</span> <span class="p">{</span>
<span class="nf">addCategory</span><span class="p">(</span><span class="nc">Intent</span><span class="p">.</span><span class="nc">CATEGORY_OPENABLE</span><span class="p">)</span>
<span class="n">type</span> <span class="p">=</span> <span class="n">intentType</span>
<span class="nf">putExtra</span><span class="p">(</span><span class="nc">Intent</span><span class="p">.</span><span class="nc">EXTRA_MIME_TYPES</span><span class="p">,</span> <span class="nf">arrayOf</span><span class="p">(</span><span class="n">intentType</span><span class="p">))</span>
<span class="p">}</span>
<span class="n">activity</span><span class="p">.</span><span class="nf">startActivityForResult</span><span class="p">(</span><span class="n">intent</span><span class="p">,</span> <span class="nc">OPEN_FILE_CODE</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The important part would be to set the intent <code class="language-plaintext highlighter-rouge">type</code> and pass <code class="language-plaintext highlighter-rouge">EXTRA_MIME_TYPES</code> to limit files visible by the user to those that are an <code class="language-plaintext highlighter-rouge">application/octet-stream</code>, which is the type used for database files.</p>
<p>The last step is to replace the database file</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">File</span><span class="p">(</span><span class="n">database</span><span class="p">.</span><span class="n">openHelper</span><span class="p">.</span><span class="n">writableDatabase</span><span class="p">.</span><span class="n">path</span><span class="p">)</span>
</code></pre></div></div>
<p>with the file that the user picked.</p>
<p>That’s all you need to add <strong>backup and restore</strong> features to your application!</p>
<p>In this article we have learned how to <strong>backup and restore Room database.</strong></p>
<p>Resources:
<a href="https://developer.android.com/guide/topics/providers/document-provider">https://developer.android.com/guide/topics/providers/document-provider</a></p>Not that long ago I wanted to implement Backup & Restore features in my new application called Notably Notepad. I thought such basic features will be well-documented and supported - I mean every app containing a database would want to allow the user to move his data to a new phone. Yet again I was wrong, there is very little information about how to perform backup of Room Database and nothing in official android documentation.Android 11 Scoped Storage - Saving Files To Shared Storage2020-09-29T19:50:30+00:002020-09-29T19:50:30+00:00https://androidexplained.github.io//android/android11/scoped-storage/2020/09/29/file-saving-android-11<p>By now probably all of you had to hear about the dreaded changes to file storage on Android 11. <br />
So what exactly changed? <br />
According to Google apps now <strong><em>“have access only to the app-specific directory on external storage, as well as specific types of media that the app has created.”</em></strong></p>
<p>In other words if your application freely allows the users to save files in shared storage, I hate to be the one that tells you this - you may have a problem sir. <br />
This is the case especially if you’ve been using
<code class="language-plaintext highlighter-rouge">Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_*)</code> in your code. <br />
The <code class="language-plaintext highlighter-rouge">getExternalStoragePublicDirectory</code> no longer returns a path to a publicly accessible folder so if you save a file in that location the user simply won’t see it.</p>
<p>But worry not, there are some alternatives which I will talk about in this article.</p>
<p>There are two recommended approaches when it comes to shared storage file saving on Android 11</p>
<ul>
<li>for <strong>Media Files (Images, Videos, Audio, Downloads)</strong> use <a href="https://developer.android.com/reference/kotlin/android/provider/MediaStore">MediaStore</a></li>
<li>for <strong>Documents and Other Files</strong> use <a href="https://developer.android.com/guide/topics/providers/document-provider">Storage Access Framework</a> (this is simply a system file picker)</li>
</ul>
<p>If you’re already using them you should be fine and you can simply skip reading further.</p>
<h3 id="mediastore-api">MediaStore API</h3>
<p>Let’s assume you want to save a bitmap as an image named <code class="language-plaintext highlighter-rouge">screenshot.jpg</code> inside the pictures directory. <br />
In order to do that using <code class="language-plaintext highlighter-rouge">MediaStore</code> API you can use the below snippet.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Suppress</span><span class="p">(</span><span class="s">"DEPRECATION"</span><span class="p">)</span>
<span class="k">private</span> <span class="k">fun</span> <span class="nf">saveImageToStorage</span><span class="p">(</span>
<span class="n">bitmap</span><span class="p">:</span> <span class="nc">Bitmap</span><span class="p">,</span>
<span class="n">filename</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">"screenshot.jpg"</span><span class="p">,</span>
<span class="n">mimeType</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">"image/jpeg"</span><span class="p">,</span>
<span class="n">directory</span><span class="p">:</span> <span class="nc">String</span> <span class="p">=</span> <span class="nc">Environment</span><span class="p">.</span><span class="nc">DIRECTORY_PICTURES</span><span class="p">,</span>
<span class="n">mediaContentUri</span><span class="p">:</span> <span class="nc">Uri</span> <span class="p">=</span> <span class="nc">MediaStore</span><span class="p">.</span><span class="nc">Images</span><span class="p">.</span><span class="nc">Media</span><span class="p">.</span><span class="nc">EXTERNAL_CONTENT_URI</span>
<span class="p">)</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">imageOutStream</span><span class="p">:</span> <span class="nc">OutputStream</span>
<span class="k">if</span> <span class="p">(</span><span class="nc">Build</span><span class="p">.</span><span class="nc">VERSION</span><span class="p">.</span><span class="nc">SDK_INT</span> <span class="p">>=</span> <span class="nc">Build</span><span class="p">.</span><span class="nc">VERSION_CODES</span><span class="p">.</span><span class="nc">Q</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">values</span> <span class="p">=</span> <span class="nc">ContentValues</span><span class="p">().</span><span class="nf">apply</span> <span class="p">{</span>
<span class="nf">put</span><span class="p">(</span><span class="nc">MediaStore</span><span class="p">.</span><span class="nc">Images</span><span class="p">.</span><span class="nc">Media</span><span class="p">.</span><span class="nc">DISPLAY_NAME</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>
<span class="nf">put</span><span class="p">(</span><span class="nc">MediaStore</span><span class="p">.</span><span class="nc">Images</span><span class="p">.</span><span class="nc">Media</span><span class="p">.</span><span class="nc">MIME_TYPE</span><span class="p">,</span> <span class="n">mimeType</span><span class="p">)</span>
<span class="nf">put</span><span class="p">(</span><span class="nc">MediaStore</span><span class="p">.</span><span class="nc">Images</span><span class="p">.</span><span class="nc">Media</span><span class="p">.</span><span class="nc">RELATIVE_PATH</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">contentResolver</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">uri</span> <span class="p">=</span>
<span class="n">contentResolver</span><span class="p">.</span><span class="nf">insert</span><span class="p">(</span><span class="n">mediaContentUri</span><span class="p">,</span> <span class="n">values</span><span class="p">)</span>
<span class="o">?:</span> <span class="k">return</span>
<span class="n">imageOutStream</span> <span class="p">=</span> <span class="nf">openOutputStream</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span> <span class="o">?:</span> <span class="k">return</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">imagePath</span> <span class="p">=</span> <span class="nc">Environment</span><span class="p">.</span><span class="nf">getExternalStoragePublicDirectory</span><span class="p">(</span><span class="n">directory</span><span class="p">).</span><span class="n">absolutePath</span>
<span class="kd">val</span> <span class="py">image</span> <span class="p">=</span> <span class="nc">File</span><span class="p">(</span><span class="n">imagePath</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>
<span class="n">imageOutStream</span> <span class="p">=</span> <span class="nc">FileOutputStream</span><span class="p">(</span><span class="n">image</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">imageOutStream</span><span class="p">.</span><span class="nf">use</span> <span class="p">{</span> <span class="n">bitmap</span><span class="p">.</span><span class="nf">compress</span><span class="p">(</span><span class="nc">Bitmap</span><span class="p">.</span><span class="nc">CompressFormat</span><span class="p">.</span><span class="nc">JPEG</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="n">it</span><span class="p">)</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In a case when saving other type of media file you might need to modify <code class="language-plaintext highlighter-rouge">filename</code>, <code class="language-plaintext highlighter-rouge">mimeType</code> and <code class="language-plaintext highlighter-rouge">mediaContentUri</code> accordingly. <br />
It is worth noting that the function will work both on the <strong>scoped</strong> as well as <strong>legacy</strong> storage systems.</p>
<h3 id="storage-access-framework-saf">Storage Access Framework (SAF)</h3>
<p>As mentioned above you should generally use <strong>SAF</strong> for documents and other types of files. <br />
The following snippet can be used to launch system file picker and allow the user to pick a directory where the file will be stored.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kd">val</span> <span class="py">CREATE_FILE</span> <span class="p">=</span> <span class="mi">1</span>
<span class="k">private</span> <span class="k">fun</span> <span class="nf">createFile</span><span class="p">(</span><span class="n">pickerInitialUri</span><span class="p">:</span> <span class="nc">Uri</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">intent</span> <span class="p">=</span> <span class="nc">Intent</span><span class="p">(</span><span class="nc">Intent</span><span class="p">.</span><span class="nc">ACTION_CREATE_DOCUMENT</span><span class="p">).</span><span class="nf">apply</span> <span class="p">{</span>
<span class="nf">addCategory</span><span class="p">(</span><span class="nc">Intent</span><span class="p">.</span><span class="nc">CATEGORY_OPENABLE</span><span class="p">)</span>
<span class="n">type</span> <span class="p">=</span> <span class="s">"application/pdf"</span>
<span class="nf">putExtra</span><span class="p">(</span><span class="nc">Intent</span><span class="p">.</span><span class="nc">EXTRA_TITLE</span><span class="p">,</span> <span class="s">"invoice.pdf"</span><span class="p">)</span>
<span class="c1">// Optionally, specify a URI for the directory that should be opened in</span>
<span class="c1">// the system file picker before your app creates the document.</span>
<span class="nf">putExtra</span><span class="p">(</span><span class="nc">DocumentsContract</span><span class="p">.</span><span class="nc">EXTRA_INITIAL_URI</span><span class="p">,</span> <span class="n">pickerInitialUri</span><span class="p">)</span>
<span class="p">}</span>
<span class="nf">startActivityForResult</span><span class="p">(</span><span class="n">intent</span><span class="p">,</span> <span class="nc">CREATE_FILE</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It’s not over yet!<br />
After the user has picked a directory we still need to handle the result <code class="language-plaintext highlighter-rouge">Uri</code> in on <code class="language-plaintext highlighter-rouge">onActivityResult</code> method.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>override fun onActivityResult(
requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == CREATE_FILE && resultCode == Activity.RESULT_OK) {
// The result data contains a URI for directory that
// the user selected.
resultData?.data?.also { uri ->
// save your data using the `uri`
}
}
}
</code></pre></div></div>
<p>Yes that’s it!<br />
Essentially use the above two approaches whenever you want to save files in directories that you want the user to have access to.</p>
<p>In this post we have learned how to save files in shared storage on Android 11.</p>
<p>Resources:
<a href="https://developer.android.com/guide/topics/data">https://developer.android.com/guide/topics/data</a></p>By now probably all of you had to hear about the dreaded changes to file storage on Android 11. So what exactly changed? According to Google apps now “have access only to the app-specific directory on external storage, as well as specific types of media that the app has created.”Editing AndroidManifest.xml File2020-09-28T19:50:30+00:002020-09-28T19:50:30+00:00https://androidexplained.github.io//android/gradle/2020/09/28/editing-manifest<p>In this article we will look at something that is not that commonly done - editing of the merged manifest file.
You might wonder why would you need to do that in the first place? <br />
If you’re working just on your simple app and do not use many 3rd party libraries you have all the control when it comes to your <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> - you don’t need to edit it since it contains only your app’s components.</p>
<p>Situation becomes more complicated when you add a 3rd party library to your project that also contains an <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> - as part of the build <code class="language-plaintext highlighter-rouge">Gradle</code> will merge your manifest with the one from the library. <br />
Now you end up with some dodgy permissions & services in your <strong>manifest</strong> file - this is exactly the reason you might want to edit the <strong>merged manifest</strong> file, to get rid of those nasty things.</p>
<h2 id="viewing-merged-manifest">Viewing Merged Manifest</h2>
<p>Before going into editing of the merged manifest you might want to have a look at it. <br />
After all if it doesn’t contain anything suspicious maybe there is no reason to edit it? <br /> <br />
You have two options here:</p>
<ul>
<li>Android Studio - if you open your <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> file on the bottom you can find two tabs - <strong>Text & Merged Manifest</strong>, clicking on the <strong>Merged Manifest</strong> option should bring you to a window where you can see your manifest</li>
</ul>
<p><img src="/images/merged_manifest.png" /></p>
<ul>
<li>file in build directory - as part of every build merged manifest is generated and saved in <code class="language-plaintext highlighter-rouge">$projectDir/app/build/intermediates/merged_manifests/$flavor/AndroidManifest.xml</code></li>
</ul>
<h2 id="editing-merged-manifest">Editing Merged Manifest</h2>
<p>Now that we know how to find it, we can finally start editing our <strong>merged manifest!</strong> <br />
For the purpose of this article we will do something very simple - we’ll replace the application name with our custom name, in my case it will be <code class="language-plaintext highlighter-rouge">Broccoli</code></p>
<p>In order to do that we will create a <strong>groovy script</strong> inside <code class="language-plaintext highlighter-rouge">app/build.gradle</code> file.</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">android</span><span class="o">.</span><span class="na">applicationVariants</span><span class="o">.</span><span class="na">all</span> <span class="o">{</span> <span class="n">variant</span> <span class="o">-></span>
<span class="n">variant</span><span class="o">.</span><span class="na">outputs</span><span class="o">.</span><span class="na">each</span> <span class="o">{</span> <span class="n">output</span> <span class="o">-></span>
<span class="kt">def</span> <span class="n">processManifest</span> <span class="o">=</span> <span class="n">output</span><span class="o">.</span><span class="na">getProcessManifestProvider</span><span class="o">().</span><span class="na">get</span><span class="o">()</span>
<span class="n">processManifest</span><span class="o">.</span><span class="na">doLast</span> <span class="o">{</span> <span class="n">task</span> <span class="o">-></span>
<span class="kt">def</span> <span class="n">outputDir</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="na">getManifestOutputDirectory</span><span class="o">()</span>
<span class="n">File</span> <span class="n">outputDirectory</span>
<span class="nf">if</span> <span class="o">(</span><span class="n">outputDir</span> <span class="k">instanceof</span> <span class="n">File</span><span class="o">)</span> <span class="o">{</span>
<span class="n">outputDirectory</span> <span class="o">=</span> <span class="n">outputDir</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">outputDirectory</span> <span class="o">=</span> <span class="n">outputDir</span><span class="o">.</span><span class="na">get</span><span class="o">().</span><span class="na">asFile</span>
<span class="o">}</span>
<span class="n">File</span> <span class="n">manifestOutFile</span> <span class="o">=</span> <span class="n">file</span><span class="o">(</span><span class="s2">"$outputDirectory/AndroidManifest.xml"</span><span class="o">)</span>
<span class="k">if</span> <span class="o">(</span><span class="n">manifestOutFile</span><span class="o">.</span><span class="na">exists</span><span class="o">()</span> <span class="o">&&</span> <span class="n">manifestOutFile</span><span class="o">.</span><span class="na">canRead</span><span class="o">()</span> <span class="o">&&</span> <span class="n">manifestOutFile</span><span class="o">.</span><span class="na">canWrite</span><span class="o">())</span> <span class="o">{</span>
<span class="kt">def</span> <span class="n">newManifest</span> <span class="o">=</span> <span class="n">manifestOutFile</span><span class="o">.</span><span class="na">getText</span><span class="o">().</span><span class="na">replace</span><span class="o">(</span><span class="s2">"@string/app_name"</span><span class="o">,</span> <span class="s2">"Broccoli"</span><span class="o">)</span>
<span class="n">manifestOutFile</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">newManifest</span><span class="o">,</span> <span class="s1">'UTF-8'</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The code is not obvious so you deserve some explanation here</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">processManifest</code> is a step during <strong>Gradle</strong> build that generates the manifest file - <code class="language-plaintext highlighter-rouge">doLast</code> tells <strong>Gradle</strong> that we want to execute the code inside the block immediately after the step finishes</li>
<li>after <code class="language-plaintext highlighter-rouge">processManifest</code> finishes merged manifest directory path is fetched via <code class="language-plaintext highlighter-rouge">getManifestOutputDirectory</code></li>
<li>the manifest is loaded into <code class="language-plaintext highlighter-rouge">manifestOutFile</code></li>
<li>after we make sure that it exists and everything is okay we replace <code class="language-plaintext highlighter-rouge">@string/app_name</code> with <code class="language-plaintext highlighter-rouge">Broccoli</code> and save the file</li>
</ul>
<p>That’s basically it, if you run the application now you will see that its name is <strong>Broccoli.</strong></p>
<p>This is a very simple example I know, however with this groovy script you can do whatever you want with <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> file. <br />
If you’re planning to do some heavy editing I encourage you to use <strong>Groovy</strong> <code class="language-plaintext highlighter-rouge">XmlParser</code> instead of working with simple text, it should be more suited for that purpose.</p>
<p>In this article we have looked at how we can edit <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> file as part of the build.</p>In this article we will look at something that is not that commonly done - editing of the merged manifest file. You might wonder why would you need to do that in the first place? If you’re working just on your simple app and do not use many 3rd party libraries you have all the control when it comes to your AndroidManifest.xml - you don’t need to edit it since it contains only your app’s components.