<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Android with Dhina17]]></title><description><![CDATA[Android with Dhina17]]></description><link>https://blog.dhina17.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 07:59:46 GMT</lastBuildDate><atom:link href="https://blog.dhina17.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[LMODroid 5.0 - Where Customization Meets Stability]]></title><description><![CDATA[Let me introduce the fancy new LMODroid update we made for y'all.
LMODroid is a free and open-source AOSP fork, providing optionally de-googled Android experience and top-notch stability along with a bunch of useful features so that you can enjoy usi...]]></description><link>https://blog.dhina17.dev/lmodroid-5-android-14-release</link><guid isPermaLink="true">https://blog.dhina17.dev/lmodroid-5-android-14-release</guid><category><![CDATA[Android]]></category><category><![CDATA[aosp]]></category><category><![CDATA[android development]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Dhina17]]></dc:creator><pubDate>Sun, 26 May 2024 15:26:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1714923956910/1eeb6aca-2c59-4617-8f1c-fa6a134e46af.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let me introduce the fancy new LMODroid update we made for y'all.</p>
<p><strong>LMODroid</strong> is a free and open-source <strong>AOSP</strong> fork, providing optionally de-googled Android experience and top-notch stability along with a bunch of useful features so that you can enjoy using your device.</p>
<p>Let's see the cool features that LMODroid have.</p>
<h3 id="heading-android-14">Android 14</h3>
<p>LMODroid 5.0 is based on the latest Android 14 QPR2 with up-to-date security patches merged.</p>
<p>We keep up our sources with the latest Android releases so you will always run the latest thing and won't miss anything.</p>
<p>Not only that, we are actively fixing bugs and maintaining the source bug-free as much as possible.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716642587513/d16faefc-4f9d-4385-a7ca-cf3a2498ff3e.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-oss-face-unlock">OSS Face unlock</h3>
<p>No proprietary code, 100% open source</p>
<p>LMODroid has an open-source face unlock developed from scratch using TensorFlow.</p>
<p>It uses an open-source model called keras-VGGFace VGG16 model for face verification.</p>
<p>It is fast enough in all devices and even unlocks in all angles.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716649247226/743c37c3-a066-47df-8796-c3719d176a73.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-app-lock">App Lock</h3>
<p>Don't worry about sensitive content being accessed by your friends, siblings, children and other people.</p>
<p>They won't stop but we can stop with our native AppLock support.</p>
<p>Lock away sensitive information with extra protection.</p>
<p>A worthy mention, it also supports biometric unlock.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716646174258/d1528d9b-b26e-4f0a-a29f-5f66136353e3.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-game-space">Game Space</h3>
<p>Ahhh! A call at the final moment of the game.</p>
<p>No more frustration! Let's control your gaming experience with our GameSpace.</p>
<p>GameSpace provides zero interruptions with useful floating actions for the games. It will take your gaming experience to the next level.</p>
<p>Block heads-up, record your super gaming skills, track your device FPS and so on.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716645834059/e17d0032-9127-478d-a756-bc7fc36a4012.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-desktop-mode">Desktop Mode</h3>
<p>Wanna use your phone on the big screen?</p>
<p>Hey, wait! It's not screen mirroring.</p>
<p>LMODroid's desktop mode will give a powerful desktop experience from your phone so you can use your favourite apps on the bigger screen at no performance cost.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716645142140/131fce10-27fd-4b8d-aceb-9d8e1c4e3a5b.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716653523369/ea8fbd33-7f19-4592-b6cd-d8bd76b1bba3.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-parallel-space">Parallel Space</h3>
<p>Yes, it's a pain to sign in / sign out for using different accounts in a game or Messenger.</p>
<p>No more pain, We got you.</p>
<p>With LMODroid's Parallel Space feature, you can run multiple instances of the same application in parallel.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716647274742/b0e14e3a-c989-460f-9a0e-86435fda4772.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-fully-compatible-with-microg">Fully compatible with microG</h3>
<p>You can use GMS / Google Play Services in LMODroid like as usual in custom ROMs.</p>
<p>But you don't like Google trackers and wanna use microG?</p>
<p>No surprise, LMODroid is fully compatible with microG as well.</p>
<p>It's 'Flash and play'.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716653107901/1ae49844-3236-48f1-8ff9-4ef371d5e039.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-more-features">More features</h3>
<p>Not only these features, LMODroid is comprised of tons of useful features that you can't miss in your device.</p>
<p>To learn more, Please visit our website <a target="_blank" href="https://libremobileos.com/lmodroid">here</a>.</p>
<h2 id="heading-for-enterprise">For enterprise</h2>
<p>No worries if the current public operating system doesn’t meet your needs.</p>
<p>We are a dedicated team of developers to provide you with what you want.</p>
<p>We implement all your product software requirements to provide you with a fully-fledged operating system for your device.</p>
<ul>
<li><p>CTS/GTS compatibility</p>
</li>
<li><p>Get your device certified</p>
</li>
<li><p>OEM premium experience</p>
</li>
<li><p>MDM solutions as OS for your organization</p>
</li>
<li><p>and more...</p>
</li>
</ul>
<p><strong>We build the OS. You build the brand.</strong> ❤️🔥</p>
<p>Contact us for more information: <a target="_blank" href="mailto:enterprise@libremobileos.com">enterprise@libremobileos.com</a></p>
<h2 id="heading-source-code">Source code</h2>
<p>Yes, you guessed it right. You can find the complete source code for LMODroid in our self-hosted GitLab.</p>
<p>👉 <a target="_blank" href="https://git.libremobileos.com/LMODroid">https://git.libremobileos.com/LMODroid</a></p>
<p>All the development happens in our Gerrit and it's open for everyone to contribute</p>
<p>👉 <a target="_blank" href="https://gerrit.libremobileos.com">https://gerrit.libremobileos.com</a></p>
<h3 id="heading-useful-links">Useful links</h3>
<p><strong>Find and download LMODroid for your devices on our download page.</strong></p>
<p>👉 <a target="_blank" href="https://get.libremobileos.com">https://get.libremobileos.com</a></p>
<p>If you can't find your device in the list, Unfortunately, your device is not supported at the moment. 💔</p>
<p>👉 <strong>Discussion (Telegram):</strong> <a target="_blank" href="https://t.me/LMODroid_Discussion">https://t.me/LMODroid_Discussion</a></p>
<p>👉 <strong>Website:</strong> <a target="_blank" href="https://libremobileos.com">https://libremobileos.com</a></p>
]]></content:encoded></item><item><title><![CDATA[Use DataStore instead of Shared Preferences]]></title><description><![CDATA[Introduction
You may come across a requirement in your app to store some configuration (not that big to use Room).
For example, the previous UI mode - (Dark/Light)
You might come across Shared Preferences for this requirement. Shared Preferences are ...]]></description><link>https://blog.dhina17.dev/use-datastore-instead-of-shared-preferences</link><guid isPermaLink="true">https://blog.dhina17.dev/use-datastore-instead-of-shared-preferences</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Android Studio]]></category><dc:creator><![CDATA[Dhina17]]></dc:creator><pubDate>Sun, 26 Mar 2023 14:30:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/rH8O0FHFpfw/upload/8e06f136058f5a3d676eb7c8b7806d2b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>You may come across a requirement in your app to store some configuration (not that big to use Room).</p>
<p>For example, the previous UI mode - (Dark/Light)</p>
<p>You might come across Shared Preferences for this requirement. Shared Preferences are used to persist data of key-value pairs.</p>
<p>I agree that shared preferences work fine but are still way too old. Imagine if you want to observe the live changes of a preference value. you can't.</p>
<h2 id="heading-datastore">DataStore</h2>
<p>DataStore is a part of Jetpack libraries that lets you store data of key-value pairs or typed objects in the device.</p>
<h3 id="heading-types">Types</h3>
<ul>
<li><p><strong>Preferences</strong> <strong>DataStore</strong> - Stores key-value pairs</p>
</li>
<li><p><strong>Proto</strong> <strong>DataStore</strong> - Stores typed objects using protocol buffers.</p>
</li>
</ul>
<p>In this guide, we will learn about the Preferences DataStore.</p>
<p>Proto DataStore is for another day.</p>
<h2 id="heading-why-you-should-use-datastore-instead-of-shared-preferences">Why you should use DataStore instead of Shared Preferences?</h2>
<p><img src="https://3.bp.blogspot.com/-Vk_q5hWw6DQ/X00ZfiRrB9I/AAAAAAAAPlo/u-kvBvmMfzgRnNViYLwaAim-E7wq5yxKACLcBGAsYHQ/s1600/Screen%2BShot%2B2020-08-31%2Bat%2B11.25.43%2BAM.png" alt="SharedPreferences" /></p>
<ul>
<li>Credits: <a target="_blank" href="https://android-developers.googleblog.com/2020/09/prefer-storing-data-with-jetpack.html">Android Developers Blog</a></li>
</ul>
<h2 id="heading-guide">Guide</h2>
<h3 id="heading-setup">Setup</h3>
<p>Add the datastore preference in your grade dependency</p>
<pre><code class="lang-kotlin">dependencies {
        implementation <span class="hljs-string">"androidx.datastore:datastore:1.0.0"</span>
}
</code></pre>
<h3 id="heading-build-datastore">Build DataStore</h3>
<p>Create a preference data store as an extension of context using the <code>preferencesDataStore</code> delegate in the top level of a Kotlin file.</p>
<p>The <code>name</code> of the Preferences DataStore is a mandatory parameter.</p>
<p>This ensures that a single instance of DataStore is created for the application process.</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Top level of any kotlin file.</span>
<span class="hljs-keyword">val</span> Context.dataStore: DataStore&lt;Preferences&gt; <span class="hljs-keyword">by</span> preferencesDataStore(name = <span class="hljs-string">"configs"</span>)
</code></pre>
<h3 id="heading-create-preference-keys">Create Preference Keys</h3>
<p>To store key-value pairs, we have to create keys for the preferences.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">object</span> PreferenceKeys {
    <span class="hljs-keyword">val</span> booleanKey = booleanPreferencesKey(name = <span class="hljs-string">"key_for_bool_value"</span>)
    <span class="hljs-keyword">val</span> byteArrayKey = byteArrayPreferencesKey(name = <span class="hljs-string">"key_for_byte_array_value"</span>)
    <span class="hljs-keyword">val</span> doubleKey = doublePreferencesKey(name = <span class="hljs-string">"key_for_double_value"</span>)
    <span class="hljs-keyword">val</span> floatKey = floatPreferencesKey(name = <span class="hljs-string">"key_for_float_value"</span>)
    <span class="hljs-keyword">val</span> intKey = intPreferencesKey(name = <span class="hljs-string">"key_for_int_value"</span>)
    <span class="hljs-keyword">val</span> longKey = longPreferencesKey(name = <span class="hljs-string">"key_for_long_value"</span>)
    <span class="hljs-keyword">val</span> stringKey = stringPreferencesKey(name = <span class="hljs-string">"key_for_string_value"</span>)
    <span class="hljs-keyword">val</span> stringSetKey = stringSetPreferencesKey(<span class="hljs-string">"key_for_string_set_value"</span>)
}
</code></pre>
<h3 id="heading-write-data">Write data</h3>
<p>Write a value to the preferences with the following code.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">suspend</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">writeValue</span><span class="hljs-params">(key: <span class="hljs-type">Preferences</span>.<span class="hljs-type">Key</span>&lt;<span class="hljs-type">Int</span>&gt;, value: <span class="hljs-type">Int</span>)</span></span> {
    context.dataStore.edit { preferences -&gt;
        <span class="hljs-comment">// Set the new value to the preference.</span>
        preferences[key] = value
    }
}
</code></pre>
<p>Write a value from a fragment or activity</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// In an activity or fragment</span>
<span class="hljs-comment">// You can use any CoroutineScope other than lifeCycleScope.</span>
lifeCycleScope.launch {
    writeValue(key = intKey, value = <span class="hljs-number">17</span>)
}
</code></pre>
<h3 id="heading-read-data">Read data</h3>
<p>Read value from the preferences with the following code.</p>
<p>It returns a Flow.</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">readValue</span><span class="hljs-params">(key: <span class="hljs-type">Preferences</span>.<span class="hljs-type">Key</span>&lt;<span class="hljs-type">Int</span>&gt;, defaultValue: <span class="hljs-type">Int</span>)</span></span>: Flow&lt;<span class="hljs-built_in">Int</span>&gt; {
    <span class="hljs-keyword">return</span> context.dataStore.<span class="hljs-keyword">data</span>.map { preferences -&gt;
        preferences[key] ?: defValue
    }
}
</code></pre>
<p>Collect the value from the Flow</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// In an activity or fragment</span>
<span class="hljs-comment">// You can use any CoroutineScope other than lifeCycleScope.</span>
lifeCycleScope.launch {
    readValue(key = intKey, defaultValue = <span class="hljs-number">0</span>).collect { value -&gt;
        <span class="hljs-comment">// Utilize the value here</span>
    }
}
</code></pre>
<h2 id="heading-bonus">Bonus</h2>
<h3 id="heading-extension-function-to-work-with-all-types-of-keys">Extension function to work with all types of keys.</h3>
<pre><code class="lang-kotlin"><span class="hljs-comment">/***
 *
 * An Extension function to DataStore&lt;Preference&gt;.
 * Generic way to getting preference value from the data store.
 * Returns preference value for the <span class="hljs-doctag">@param</span> key if found,
 * Otherwise it returns <span class="hljs-doctag">@param</span> defValue
 *
 * <span class="hljs-doctag">@param</span> key Preference key
 * <span class="hljs-doctag">@param</span> defValue default value if the preference key is not found
 *
 * <span class="hljs-doctag">@return</span> a Flow of type T
 */</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> DataStore<span class="hljs-type">&lt;Preferences&gt;</span>.<span class="hljs-title">getValue</span><span class="hljs-params">(
    key: <span class="hljs-type">Preferences</span>.<span class="hljs-type">Key</span>&lt;<span class="hljs-type">T</span>&gt;,
    defValue: <span class="hljs-type">T</span>
)</span></span>: Flow&lt;T&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">data</span>.map { preferences -&gt;
        preferences[key] ?: defValue
    }
}

<span class="hljs-comment">/***
 *
 * An Extension function to DataStore&lt;Preference&gt;
 * Generic way to editing preference value from the data store.
 * Edit the value of the preference <span class="hljs-doctag">@param</span> key with <span class="hljs-doctag">@param</span> value.
 *
 * <span class="hljs-doctag">@param</span> key Preference key
 * <span class="hljs-doctag">@param</span> value Value need to be set to the given key
 *
 */</span>
<span class="hljs-keyword">suspend</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> DataStore<span class="hljs-type">&lt;Preferences&gt;</span>.<span class="hljs-title">setValue</span><span class="hljs-params">(
    key: <span class="hljs-type">Preferences</span>.<span class="hljs-type">Key</span>&lt;<span class="hljs-type">T</span>&gt;,
    value: <span class="hljs-type">T</span>
)</span></span> {
    edit { preferences -&gt;
        preferences[key] = value
    }
}
</code></pre>
<p>That's it for today.</p>
<hr />
<p>Please do like if it helped you.</p>
<p>Comment if you have any queries.</p>
<p>Share if you want to help others.</p>
<p>For more articles like this, Do follow.</p>
<p>Thanks for reading. &lt;3</p>
]]></content:encoded></item><item><title><![CDATA[Build and Share Android Apps with Ease Using GitHub Actions]]></title><description><![CDATA[Introduction
Imagine you are building a super cool android app!
You want to share it with your friends so you have to generate a signed version APK and share it with your friends yourself.
In the beginning, You will share an initial build with your f...]]></description><link>https://blog.dhina17.dev/build-and-share-android-apps-with-ease-using-github-actions</link><guid isPermaLink="true">https://blog.dhina17.dev/build-and-share-android-apps-with-ease-using-github-actions</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[Dhina17]]></dc:creator><pubDate>Sun, 29 Jan 2023 18:01:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/wX2L8L-fGeA/upload/0726d1f56f8b51508f39edc446385bf1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Imagine you are building a super cool android app!</p>
<p>You want to share it with your friends so you have to generate a signed version APK and share it with your friends yourself.</p>
<p>In the beginning, You will share an initial build with your friends. That won't take much time.</p>
<p>Eventually, you will make many changes in your app and want to share the APK with your friends for each change or each release. You may feel bored or it consumes a lot of time since you have to do the work manually to compile and share the APK. You will be tired of sharing the APK since you have to do the same steps every time.</p>
<p>Don't worry! We will automate the process of building, signing and sharing the APK using <a target="_blank" href="https://github.com/features/actions">GitHub Actions</a>, a feature of GitHub.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>Git</p>
</li>
<li><p>GitHub</p>
</li>
<li><p>Basics of Android App Development</p>
</li>
<li><p>Basics of shell commands (if no, still you can do it my windows users)</p>
</li>
</ul>
<h2 id="heading-basics">Basics</h2>
<h3 id="heading-github-actions"><strong>GitHub Actions</strong></h3>
<p>GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to <strong>automate your build, test, and deployment pipeline</strong>.</p>
<p>You can set up workflows for your project and trigger it based on repository events like push, and pull requests etc.</p>
<p>Workflows are nothing but a set of actions/commands defined by the user that will be executed in the GitHub-provided virtual machines for your project.</p>
<p><a target="_blank" href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#the-components-of-github-actions"><img src="https://docs.github.com/assets/cb-25535/images/help/images/overview-actions-simple.png" alt="Workflow overview" /></a></p>
<p>To learn more about GitHub Actions, <a target="_blank" href="https://docs.github.com/en/actions">Check here</a>.</p>
<h2 id="heading-guide">Guide</h2>
<p>In this guide, you will learn how to set up workflows in your GitHub repository. Make sure you have a GitHub repository that contains your project source files.</p>
<h3 id="heading-setup-workflow-in-your-repository">Setup workflow in your repository</h3>
<p><strong>1)</strong> Open your GitHub repository in your browser</p>
<p><strong>2)</strong> Select the '<strong>Actions</strong>' tab</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675186096669/418fa29e-0c3e-44a8-9ed1-5ed299222043.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>3)</strong> Click '<a target="_blank" href="https://github.com/Dhina17/android_kotlin_app_template/new/main?filename=.github%2Fworkflows%2Fmain.yml&amp;workflow_template=blank">set up a workflow yourself</a>'</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675186110618/1cd183f5-09f3-4a55-b36b-ba9d5b8abddc.png" alt class="image--center mx-auto" /></p>
<p><strong>4)</strong> It will create a new file named main.yml in &lt;project_root&gt;/.github/workflows. You can rename it as your wish.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675186361897/99af2403-2874-4e19-8f6c-5829927d36e3.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>5)</strong> Now, you have to configure the instructions in the file editor to create a workflow.</p>
<p><strong>Note:</strong> YAML language is used here. In case you want to learn more about it, <a target="_blank" href="https://yaml.org/">check here</a>.</p>
<p>We gonna use GitHub workflow syntax to write the YAML code. To learn more about that, check the <a target="_blank" href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions">official doc.</a></p>
<p>Let's write step-by-step.</p>
<p><strong>Note:</strong> Changes in each step happen in the same file. So add code continuously in the same file. You can find the complete YAML code at the end of the guide.</p>
<ul>
<li><p>Set the name of the workflow</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">App</span>
</code></pre>
<p>  It will be shown in the 'Actions' tab.</p>
</li>
<li><p>Configure in which repository event, you have to run your workflow.</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">on:</span>
    <span class="hljs-attr">push:</span>
      <span class="hljs-comment"># Trigger only for main branch.</span>
      <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span> ]
    <span class="hljs-comment"># Allows you to run this workflow manually from the Actions tab</span>
    <span class="hljs-attr">workflow_dispatch:</span>
</code></pre>
<p>  <a target="_blank" href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on"><code>on</code></a> is used to define which event you want to trigger your workflow.</p>
<ul>
<li><p>Here we configured it to trigger the workflow on push commits to the main branch.</p>
</li>
<li><p><code>workflow_dispatch:</code> is enable us to trigger the workflow manually in the actions of the repository.</p>
</li>
</ul>
</li>
<li><p>Now you have to define a job to do our task in the Runner (Virtual Machine).</p>
<p>  <a target="_blank" href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#jobs">Job</a> is a set of steps that will be executed in the Runner (Virtual Machine).</p>
<p>  A step may be a shell script or set of shell commands or an <a target="_blank" href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#actions">action</a>.</p>
<p>  For our task, we gonna define a job with three steps.</p>
<ul>
<li><p>Clone the project</p>
</li>
<li><p>Build the APK</p>
</li>
<li><p>Sign the APK</p>
</li>
<li><p>Send the APK</p>
</li>
</ul>
</li>
</ul>
<p>    First, define our job</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">jobs:</span>
      <span class="hljs-attr">build:</span>
        <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-20.04</span>
</code></pre>
<p>    It will configure a job named <code>build</code> it will be run on a <code>ubuntu-20.04</code> machine.</p>
<p>    Now define our three steps one by one inside the <code>build</code> job</p>
<ul>
<li><p><strong>Clone the project</strong></p>
<pre><code class="lang-yaml">  <span class="hljs-attr">jobs:</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-20.04</span>
      <span class="hljs-comment"># Steps definition starts here</span>
      <span class="hljs-attr">steps:</span>
        <span class="hljs-comment"># Get the app source</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Get</span> <span class="hljs-string">the</span> <span class="hljs-string">source</span>
          <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>
</code></pre>
<p>  Here, we are using GitHub's official <a target="_blank" href="https://github.com/actions/checkout">checkout action</a> to clone our project source code in the runner (VM).</p>
<ul>
<li><p>As we know, <code>name:</code> defines the name of the step</p>
</li>
<li><p><code>uses:</code> - tells which action needs to be run.</p>
</li>
</ul>
</li>
<li><p><strong>Build the APK</strong></p>
<pre><code class="lang-yaml">          <span class="hljs-comment"># Compile the apk</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Generate</span> <span class="hljs-string">Release</span> <span class="hljs-string">APK</span>
          <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">assembleRelease</span>
</code></pre>
<p>  with <code>run:</code> , you can define shell commands to execute in the step.</p>
<p>  Here we build the release APK by using the Gradle wrapper.</p>
</li>
<li><p><strong>Sign the APK</strong></p>
<pre><code class="lang-yaml">        <span class="hljs-comment"># Sign the apk</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Sign</span> <span class="hljs-string">APK</span>
          <span class="hljs-attr">uses:</span> <span class="hljs-string">r0adkll/sign-android-release@v1</span>
          <span class="hljs-comment"># ID used to access action output</span>
          <span class="hljs-attr">id:</span> <span class="hljs-string">sign_app</span>
          <span class="hljs-attr">with:</span>
            <span class="hljs-attr">releaseDirectory:</span> <span class="hljs-string">app/build/outputs/apk/release</span>
            <span class="hljs-attr">signingKeyBase64:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.SIGNING_KEY</span> <span class="hljs-string">}}</span>
            <span class="hljs-attr">alias:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ALIAS</span> <span class="hljs-string">}}</span>
            <span class="hljs-attr">keyStorePassword:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.KEY_STORE_PASSWORD</span> <span class="hljs-string">}}</span>
            <span class="hljs-attr">keyPassword:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.KEY_PASSWORD</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-comment"># override default build-tools version (29.0.3) -- optional</span>
            <span class="hljs-attr">BUILD_TOOLS_VERSION:</span> <span class="hljs-string">"31.0.0"</span>
</code></pre>
<p>  For signing the APK, we gonna use the <a target="_blank" href="https://github.com/r0adkll/sign-android-release">r0adkll/sign-android-release@v1</a> action.</p>
<ul>
<li><p><code>id</code> - Unique ID of the step so we can refer to and get the output of the step in another step.</p>
</li>
<li><p><code>with</code> - It is used to provide the input for the action used in the step.</p>
<p>  In our case, we have to provide the key information to the action to sign the APK.</p>
<p>  Some information is highly confidential so we can't add it to a public file, right? so we gonna use repository secrets for this. Let's see how to do it later.</p>
<p>  For now, just know how to use the repository's secret here.</p>
<p>  <code>${{ secrets.&lt;KEY_NAME&gt; }}</code> - Use this way to get your repository secret.</p>
<p>  The following are the inputs required for this action. Refer <a target="_blank" href="https://github.com/r0adkll/sign-android-release#inputs">here</a>.</p>
<ul>
<li><p><code>releaseDirectory</code> - Release APK directory</p>
</li>
<li><p><code>signingKeyBase64</code> - The base64 encoded signing key used to sign your app</p>
<p>  This action will directly decode this input to a file to sign your release with. You can prepare your key by running this command on *nix systems.</p>
<pre><code class="lang-yaml">  <span class="hljs-string">openssl</span> <span class="hljs-string">base64</span> <span class="hljs-string">&lt;</span> <span class="hljs-string">some_signing_key.jks</span> <span class="hljs-string">|</span> <span class="hljs-string">tr</span> <span class="hljs-string">-d</span> <span class="hljs-string">'\n'</span> <span class="hljs-string">|</span> <span class="hljs-string">tee</span> <span class="hljs-string">some_signing_key.jks.base64.txt</span>
</code></pre>
<p>  Then copy the contents of the <code>.txt</code> file</p>
</li>
<li><p><code>alias</code> - The alias of your signing key</p>
</li>
<li><p><code>keyStorePassword</code> - The password to your signing keystore</p>
</li>
<li><p><code>keyPassword</code> - The private key password for your signing keystore</p>
</li>
<li><p><code>env</code> - This is used to define the environment variables required for a step. In our case, we have defined the <code>BUILD_TOOLS_VERSION</code> (optional).</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>Share the APK</strong></p>
<p>  In the previous step, we generated the signed APK successfully. Now it's time to send the APK somewhere so you share it easily.</p>
<p>  Two cases:</p>
<ul>
<li><p>Directly send the APK</p>
</li>
<li><p>Upload the APK to some file transfer service and send the link</p>
</li>
</ul>
</li>
</ul>
<p>        We gonna see both cases.</p>
<p>        <strong>Case 1:</strong> <strong>Directly send the APK to a messenger like Telegram or Discord.</strong></p>
<p>        You can use any messenger but know about its API.</p>
<p>        Here we gonna send our APK to a Telegram Chat via Bot API. I ain't gonna explain about Telegram Bot API since it's out of track. You can learn it from <a target="_blank" href="https://core.telegram.org/bots/api">here</a>.</p>
<pre><code class="lang-yaml">              <span class="hljs-comment"># Send to the APK to Telegram</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Send</span> <span class="hljs-string">the</span> <span class="hljs-string">APK</span> <span class="hljs-string">to</span> <span class="hljs-string">Telegram</span>
                <span class="hljs-attr">env:</span>
                  <span class="hljs-attr">APK_PATH:</span> <span class="hljs-string">${{steps.sign_app.outputs.signedReleaseFile}}</span>
                  <span class="hljs-attr">BOT_API_KEY:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.BOT_API_KEY</span> <span class="hljs-string">}}</span>
                  <span class="hljs-attr">CHAT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.CHAT_ID</span> <span class="hljs-string">}}</span>
                <span class="hljs-attr">run:</span> <span class="hljs-string">|</span>
                  <span class="hljs-string">curl</span> <span class="hljs-string">-F</span> <span class="hljs-string">chat_id=$CHAT_ID</span> <span class="hljs-string">-F</span> <span class="hljs-string">document=@"${APK_PATH}"</span> <span class="hljs-string">-F</span> <span class="hljs-string">caption="Template-$(date</span> <span class="hljs-string">+"%Y%m%d-%H%M")"</span> <span class="hljs-string">https://api.telegram.org/bot${BOT_API_KEY}/sendDocument</span>
</code></pre>
<ul>
<li><p><code>env</code> - Here we have defined the environmental variables required for this step.</p>
<ul>
<li><p><code>APK_PATH</code> - Get it from the previous step. See the use of <code>id</code> that we defined in the previous step.</p>
</li>
<li><p><code>BOT_API_KEY</code> - Your bot API key that you got from the Bot Father. Get from repository secrets.</p>
</li>
<li><p><code>CHAT_ID</code> - ID of the telegram chat to where you want to send the APK. It can be a group, channel or your ID. Get from repository secrets.</p>
<p>  <strong>Note:</strong> It's not the username of your chat or group. It's a numerical value that you can get by using some other bots.</p>
</li>
</ul>
</li>
<li><p><code>run</code> - It contains the list of shell commands that we need to execute in this step.</p>
<p>  Here, we send the APK by making a request to the Telegram BOT API with cURL tool. Customize on your needs.</p>
</li>
</ul>
<p>        <strong>Case 2: Upload the APK to some file transfer service and send the link</strong></p>
<p>        In this case, we gonna upload our APK to a file transfer service like GoFile, WeTransfer etc and then send the link to a messenger. Here I gonna use Telegram.</p>
<pre><code class="lang-yaml">              <span class="hljs-comment"># Upload the APK to WeTransfer and send link to Telegram</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Send</span> <span class="hljs-string">the</span> <span class="hljs-string">APK</span> <span class="hljs-string">to</span> <span class="hljs-string">Telegram</span>
                <span class="hljs-attr">env:</span>
                  <span class="hljs-attr">APK_PATH:</span> <span class="hljs-string">${{steps.sign_app.outputs.signedReleaseFile}}</span>
                  <span class="hljs-attr">BOT_API_KEY:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.BOT_API_KEY</span> <span class="hljs-string">}}</span>
                  <span class="hljs-attr">CHAT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.CHAT_ID</span> <span class="hljs-string">}}</span>
                <span class="hljs-attr">run:</span> <span class="hljs-string">|
                  # Get the transfer script to upload the apks
                  curl -sL https://git.io/file-transfer | sh
                  # Upload and get the link
                  LINK=$(./transfer wet $APK_PATH | grep "Download Link" | sed 's/Download Link://g')
                  # Create a message
                  MESSAGE="Template $(date +'%Y-%m-%d %H:%M')\n\nLink: ${LINK}"
                  # Can't make the telegram markdown working
                  TEXT=$(echo -e $MESSAGE)
                  # Send the message to telegram
                  curl -F chat_id=$CHAT_ID -F text="$TEXT" -F disable_web_page_preview=true https://api.telegram.org/bot${BOT_API_KEY}/sendMessage</span>
</code></pre>
<p>    Here we are using this <a target="_blank" href="https://github.com/Mikubill/transfer">tool</a> to upload our APK to WeTransfer and then send the link to Telegram chat or channel.</p>
<p><strong>Final workflow code for case 1: [main.yml]</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">App</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-comment"># Trigger only for main branch.</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span> ]
  <span class="hljs-comment"># Allows you to run this workflow manually from the Actions tab</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-20.04</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-comment"># Get the app source</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Get</span> <span class="hljs-string">the</span> <span class="hljs-string">source</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-comment"># Compile the apk</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Generate</span> <span class="hljs-string">Release</span> <span class="hljs-string">APK</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">assembleRelease</span>

      <span class="hljs-comment"># Sign the apk</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Sign</span> <span class="hljs-string">APK</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">r0adkll/sign-android-release@v1</span>
        <span class="hljs-comment"># ID used to access action output</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">sign_app</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">releaseDirectory:</span> <span class="hljs-string">app/build/outputs/apk/release</span>
          <span class="hljs-attr">signingKeyBase64:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.SIGNING_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">alias:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ALIAS</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">keyStorePassword:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.KEY_STORE_PASSWORD</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">keyPassword:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.KEY_PASSWORD</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-comment"># override default build-tools version (29.0.3) -- optional</span>
          <span class="hljs-attr">BUILD_TOOLS_VERSION:</span> <span class="hljs-string">"31.0.0"</span>

      <span class="hljs-comment"># Send to the APK to Telegram</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Send</span> <span class="hljs-string">the</span> <span class="hljs-string">APK</span> <span class="hljs-string">to</span> <span class="hljs-string">Telegram</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">APK_PATH:</span> <span class="hljs-string">${{steps.sign_app.outputs.signedReleaseFile}}</span>
          <span class="hljs-attr">BOT_API_KEY:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.BOT_API_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">CHAT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.CHAT_ID</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|</span>
          <span class="hljs-string">curl</span> <span class="hljs-string">-F</span> <span class="hljs-string">chat_id=$CHAT_ID</span> <span class="hljs-string">-F</span> <span class="hljs-string">document=@"${APK_PATH}"</span> <span class="hljs-string">-F</span> <span class="hljs-string">caption="Template-$(date</span> <span class="hljs-string">+"%Y%m%d-%H%M")"</span> <span class="hljs-string">https://api.telegram.org/bot${BOT_API_KEY}/sendDocument</span>
</code></pre>
<p><strong>Final workflow code for case 2: [main.yml]</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">App</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-comment"># Trigger only for main branch.</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span> ]
  <span class="hljs-comment"># Allows you to run this workflow manually from the Actions tab</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-20.04</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-comment"># Get the app source</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Get</span> <span class="hljs-string">the</span> <span class="hljs-string">source</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-comment"># Compile the apk</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Generate</span> <span class="hljs-string">Release</span> <span class="hljs-string">APK</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">assembleRelease</span>

      <span class="hljs-comment"># Sign the apk</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Sign</span> <span class="hljs-string">APK</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">r0adkll/sign-android-release@v1</span>
        <span class="hljs-comment"># ID used to access action output</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">sign_app</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">releaseDirectory:</span> <span class="hljs-string">app/build/outputs/apk/release</span>
          <span class="hljs-attr">signingKeyBase64:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.SIGNING_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">alias:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ALIAS</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">keyStorePassword:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.KEY_STORE_PASSWORD</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">keyPassword:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.KEY_PASSWORD</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-comment"># override default build-tools version (29.0.3) -- optional</span>
          <span class="hljs-attr">BUILD_TOOLS_VERSION:</span> <span class="hljs-string">"31.0.0"</span>

      <span class="hljs-comment"># Upload the action artifact</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">the</span> <span class="hljs-string">APK</span> <span class="hljs-string">(artifact)</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">Template</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">${{steps.sign_app.outputs.signedReleaseFile}}</span>

      <span class="hljs-comment"># Upload the APK to WeTransfer and send link to Telegram</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Send</span> <span class="hljs-string">the</span> <span class="hljs-string">APK</span> <span class="hljs-string">to</span> <span class="hljs-string">Telegram</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">APK_PATH:</span> <span class="hljs-string">${{steps.sign_app.outputs.signedReleaseFile}}</span>
          <span class="hljs-attr">BOT_API_KEY:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.BOT_API_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">CHAT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.CHAT_ID</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          # Get the transfer script to upload the apks
          curl -sL https://git.io/file-transfer | sh
          # Upload and get the link
          LINK=$(./transfer wet $APK_PATH | grep "Download Link" | sed 's/Download Link://g')
          # Create a message
          MESSAGE="Template $(date +'%Y-%m-%d %H:%M')\n\nLink: ${LINK}"
          # Can't make the telegram markdown working
          TEXT=$(echo -e $MESSAGE)
          # Send the message to telegram
          curl -F chat_id=$CHAT_ID -F text="$TEXT" -F disable_web_page_preview=true https://api.telegram.org/bot${BOT_API_KEY}/sendMessage</span>
</code></pre>
<p><strong>6)</strong> Now commit the changes by clicking the 'Start commit' button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675186380145/bdb44c13-d4b7-495f-8b7e-640b277be748.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>7)</strong> Add repository secrets</p>
<ul>
<li><p>Open the <strong>Settings</strong> tab in the repository</p>
</li>
<li><p>Click <strong>Secrets and Variables</strong> under the 'Security' section</p>
</li>
<li><p>Open <strong>Actions</strong></p>
</li>
<li><p>Add the required secrets with the proper keys used in the workflow (main.yml).</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675186454419/86aa2e43-c37c-466f-9475-54d7b5f7fdf1.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>8)</strong> Now you can verify your action by making repository events like pushing new commits or by manually running it from the <strong>'Action'</strong> Tab.</p>
<h3 id="heading-bonus-multiple-apk">Bonus: Multiple APK</h3>
<p>In case, you are building multiple APKs for your project, then use this workflow code as a reference.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">App</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-comment"># Trigger only for main branch.</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span> ]
  <span class="hljs-comment"># Allows you to run this workflow manually from the Actions tab</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-20.04</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-comment"># Get the app source</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Get</span> <span class="hljs-string">the</span> <span class="hljs-string">source</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-comment"># Compile the apk</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Generate</span> <span class="hljs-string">Release</span> <span class="hljs-string">APK</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">assembleRelease</span>

      <span class="hljs-comment"># Sign the apk</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Sign</span> <span class="hljs-string">APK</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">Dhina17/sign-android-release@master</span>
        <span class="hljs-comment"># ID used to access action output</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">sign_app</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">releaseDirectory:</span> <span class="hljs-string">app/build/outputs/apk/release</span>
          <span class="hljs-attr">signingKeyBase64:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.SIGNING_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">alias:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ALIAS</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">keyStorePassword:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.KEY_STORE_PASSWORD</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">keyPassword:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.KEY_PASSWORD</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-comment"># override default build-tools version (29.0.3) -- optional</span>
          <span class="hljs-attr">BUILD_TOOLS_VERSION:</span> <span class="hljs-string">"31.0.0"</span>

      <span class="hljs-comment"># Upload all APK variants to GoFile and send links to Telegram</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Send</span> <span class="hljs-string">the</span> <span class="hljs-string">APK</span> <span class="hljs-string">to</span> <span class="hljs-string">Telegram</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">APK_VARIANT1:</span> <span class="hljs-string">${{steps.sign_app.outputs.signedReleaseFile0}}</span>
          <span class="hljs-attr">APK_VARIANT2:</span> <span class="hljs-string">${{steps.sign_app.outputs.signedReleaseFile1}}</span>
          <span class="hljs-attr">APK_VARIANT3:</span> <span class="hljs-string">${{steps.sign_app.outputs.signedReleaseFile2}}</span>
          <span class="hljs-attr">APK_VARIANT4:</span> <span class="hljs-string">${{steps.sign_app.outputs.signedReleaseFile3}}</span>
          <span class="hljs-attr">BOT_API_KEY:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.BOT_API_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">CHAT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.CHAT_ID</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          # Get the transfer script to upload the apks
          curl -sL https://git.io/file-transfer | sh
          # Upload and get the link
          LINK1=$(./transfer wet $APK_VARIANT1 | grep "Download Link" | sed 's/Download Link://g')
          LINK2=$(./transfer wet $APK_VARIANT2 | grep "Download Link" | sed 's/Download Link://g')
          LINK3=$(./transfer wet $APK_VARIANT3 | grep "Download Link" | sed 's/Download Link://g')
          LINK4=$(./transfer wet $APK_VARIANT4 | grep "Download Link" | sed 's/Download Link://g')
          # Create a message
          MESSAGE="Template $(date +'%Y-%m-%d %H:%M')\n\narm64: ${LINK1}\narm: ${LINK2}\nx86_64: ${LINK3}\nx86: ${LINK4}"
          # Can't make the telegram markdown working
          TEXT=$(echo -e $MESSAGE)
          # Send the message to telegram
          curl -F chat_id=$CHAT_ID -F text="$TEXT" -F disable_web_page_preview=true https://api.telegram.org/bot${BOT_API_KEY}/sendMessage</span>
</code></pre>
<p>Here, is a small change in the signing APK step, I have used my fork <a target="_blank" href="https://github.com/Dhina17/sign-android-release">Dhina17/sign-android-release</a> since multiple APK signing needs some changes. The original repo is not active to accept the contribution so.</p>
<p>You can understand this with my previous explanations.</p>
<p>I hope you can save your valuable time in building and sharing apps with your friends and testers.</p>
<p>Add your valuable feedback in the comments.</p>
<p>Thanks for reading. &lt;3</p>
]]></content:encoded></item><item><title><![CDATA[How to contribute to Open Source projects - A beginner's guide]]></title><description><![CDATA[Prerequisites
Make sure you know about the following topics.

Basic Programming

Open Source

Git and GitHub


Introduction
What is open source?
Open source projects are a type of project that is open and free for everyone so anyone can view, use and...]]></description><link>https://blog.dhina17.dev/how-to-contribute-to-open-source-projects-a-beginners-guide</link><guid isPermaLink="true">https://blog.dhina17.dev/how-to-contribute-to-open-source-projects-a-beginners-guide</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Dhina17]]></dc:creator><pubDate>Sun, 22 Jan 2023 16:22:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/842ofHC6MaI/upload/eaa64744fb42791f9ff43f8fc6d1d788.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Make sure you know about the following topics.</p>
<ul>
<li><p>Basic Programming</p>
</li>
<li><p>Open Source</p>
</li>
<li><p>Git and GitHub</p>
</li>
</ul>
<h2 id="heading-introduction">Introduction</h2>
<h3 id="heading-what-is-open-source">What is open source?</h3>
<p>Open source projects are a type of project that is open and free for everyone so anyone can view, use and modify it.</p>
<h3 id="heading-benefits-of-open-source-contribution">Benefits of Open Source contribution</h3>
<ul>
<li><p>Gain real-world software development experience and skills.</p>
</li>
<li><p>Learn from other developers.</p>
</li>
<li><p>Network with worldwide developers.</p>
</li>
<li><p>It will increase the value of your career profile.</p>
</li>
<li><p>Give back to the community.</p>
</li>
<li><p>and more.</p>
</li>
</ul>
<h2 id="heading-guide-to-contribute">Guide to Contribute</h2>
<h3 id="heading-1-choose-a-project">1) Choose a project</h3>
<p><strong>Choose a field</strong></p>
<p>Open Source is an ocean. Accept the truth, you can't contribute to every project out there. Choose any field. For example, Android, Web Dev, Cyber Security etc</p>
<p><strong>Find a niche in your field</strong></p>
<p>Now you have chosen a field but it may contain a lot of sub-fields further. Let's say</p>
<ul>
<li><p>Android - It has Android custom ROM development which includes the device-specific, android framework, kernel and so on, and app development which includes app UI, and libraries like custom views, utility etc.</p>
<p>  Example:</p>
<p>  Custom ROMs like LineageOS,</p>
<p>  App Development - Open-source apps and libraries.</p>
</li>
<li><p>Web Development - It has two parts - front end, back end and so on.</p>
<p>  For example, react.js projects, node packages, and whatever.</p>
</li>
<li><p>Other fields - Every other field has open-source projects that you may be interested in and choose it.</p>
</li>
</ul>
<p>Choose one.</p>
<p><strong>Note:</strong> You can contribute to many projects at a time. For beginners, I recommend sticking with one good project to learn.</p>
<p><strong>Consume open-source projects</strong></p>
<p>I always say - "<strong>First, consume then contribute</strong>" In your niche, maybe there are handy tools, libraries, and so on. Go and try them. Maybe you are using some projects already without knowing it's open source. Make a list.</p>
<p><strong>Pick a project</strong></p>
<p>→ Obviously yes, Pick one which you like the most from your list.</p>
<p>Two types of projects:</p>
<p>1) Big project</p>
<ul>
<li><p>More users</p>
</li>
<li><p>Community driven</p>
</li>
</ul>
<p>2) Small project</p>
<ul>
<li><p>Personal project</p>
</li>
<li><p>Fewer users</p>
</li>
<li><p>No Community</p>
</li>
</ul>
<p>→ Pick one big project you like, join the community, learn from the community, and contribute to the project.</p>
<p>→ It's always good to be in a community so you can find mentors, seek help and learn from them.</p>
<p>→ FAQ: Where can I find the community?</p>
<p>Check the project README or project website. You can find the community discord or slack link easily</p>
<p>→ So, Pick one big project and STICK to it.</p>
<p>→ Play the long-run game here.</p>
<p>Keep the above points aside. let's get into the small projects.</p>
<p>→ I didn't say don't contribute to small projects. You can start with small projects but ensure you are in a community-driven project for the long run.</p>
<p><strong>Tips to pick:</strong></p>
<ul>
<li><p>Go for projects in which you have a real use case and consume it. You will know how it works clearly. (User-wise) As much as you consume, you will get ideas and will need more improvements/features in that project.</p>
</li>
<li><p>For example, you may be using an app daily, a Money Manager app. In that case, try to find an open-source alternative, consume and then contribute.</p>
</li>
</ul>
<h3 id="heading-2-involve-and-find-areas-of-improvement-in-the-project">2) Involve and find areas of improvement in the project</h3>
<p>→ Clone/Download the project.</p>
<p>→ Compile or run yourself.</p>
<p>→ You can find errors or warnings. Fix it and contribute to the project.</p>
<p>→ Read the docs. You can find typos and grammar mistakes in the docs. Correct them and contribute to the project.</p>
<p>→ Check the Pull Request of the project. Help the maintainers by:</p>
<ul>
<li>Test, verify the patch, and comment in the PR - CodeReview</li>
</ul>
<p>→ Check the issues of the project.</p>
<ul>
<li>Try to help if you know the workaround or try to fix it on the project side.</li>
</ul>
<p>→ Help in your language translation to the project. Check the README or Project website to know "How to".</p>
<p><strong>Points to remember:</strong></p>
<ul>
<li><p>Don't go for random repo which you don't use but do just for the sake of the word "open source contributions"</p>
</li>
<li><p>Be specific in choosing the project</p>
</li>
<li><p>Join at least one community-driven project for the long run.</p>
</li>
<li><p>Stick to ONE in the beginning. START TODAY.</p>
</li>
<li><p>You will contribute as much as you consume the project. Simple</p>
</li>
<li><p>If you loved the project but don't get any ideas, No worries check the issues tab, try to fix them and contribute to the project.</p>
</li>
</ul>
<h3 id="heading-3-learn-to-track-the-code">3) Learn to track the code</h3>
<p>Imagine, You have chosen a project which fulfills your needs. You have used it enough to know how to use it and how it works from the user mindset. You got some improvements in your mind or took issues from the issues tab.</p>
<p>Now get into tracking the code For example, You want to update a print line (Output) in a CLI tool. In this case, you can easily track the part of the code responsible for printing by doing a quick search for the string in the project.</p>
<p>If you want to track the code for a logical part, let's say a logic or a dynamic content part, you can't track it easily since you can't find the exact words in the output as in the code.</p>
<p>In this case, you have to find the static part associated with the dynamic part. Let's say the title, header, or logs part (Typically strings). Once you find this part in the source code, you can find the dynamic part or logical part in the code easily.</p>
<p>Sometimes tracking is a bit time-consuming and harder when the projects are big and consist of many packages/modules. In this case, one part of the code points to another and another package/module. You need PATIENCE here.</p>
<h3 id="heading-4-learn-to-read-the-code">4) Learn to read the code</h3>
<p>Now you tracked the code successfully, What's next?</p>
<p>You have to read that part of the code and understand how it works. Here you don't need to read and understand the whole project source code to contribute. That's COOL, right?</p>
<p>You have to understand the specific part which you are going to modify. If you are familiar with the project tech stack/language, you can read and understand it easily.</p>
<p>if you are not familiar with it, Don't worry I got you.</p>
<p>→ Since you tracked the code, take that part of the code and google it (Syntax wise). I am sure you can understand it if you have basic programming knowledge. Sometimes it takes time, Be PATIENT!</p>
<p><strong>Note:</strong> Use documentation if the project has one.</p>
<h3 id="heading-5-learn-to-change">5) Learn to change</h3>
<p>You know what are you going to do and what that piece of code is doing there. Now, what's next?</p>
<p>Add your part there. if you are familiar with that project tech stack/language, you can convert your idea into code easily.</p>
<p>if you are not familiar with it, Don't worry I got you again</p>
<p>→ If you have basic programming knowledge, your mind can visualize how you can convert your idea into programming logic.</p>
<p>→ Google the syntax of the language in which you have to write the code</p>
<p>→ Sometimes you have to learn some basics about the framework/stack.</p>
<p>→ Sometimes you have to learn much more. Yeah, depends on your change. if you had come this far, you could have managed this. :-)</p>
<p>Using the above things - Write the code - Run - Verify</p>
<h3 id="heading-6-contribute">6) Contribute</h3>
<p>→ Commit the change and push it to your forked repo.</p>
<p>→ Make a Pull Request to the original project repo.</p>
<p>→ Wait for the maintainer.</p>
<p>Have PATIENCE!</p>
<p>Make sure you followed the contributing guidelines of the project if it has one.</p>
<p>It's good to get feedback about your idea from the maintainers before implementing it. So you will be aligned with the project goals and your contributions have more chances to get accepted.</p>
<p>For example, If you want to redesign or refactor some parts of the project, ask the maintainer, get the confirmation and then start the work.</p>
<p>Note: There are a lot of guides on 'How to use git?' out there. Please refer to them.</p>
<h3 id="heading-7-community">7) Community</h3>
<p>Open Source wouldn't have come this far without collaborations among great minds. If the project that you are contributing to, has a large user base, sure it will have a common online platform where the users and developers interact.</p>
<p>→ Engage with the community members.</p>
<p>→ Talk more about the project.</p>
<p>→ Get help. Give help</p>
<p>→ Make friends. Make friends</p>
<p>→ Learn from them</p>
<p><strong>Points to remember in a community</strong></p>
<p>→ Don't expect a spoon feed from the community members.</p>
<p>→ Avoid asking things that you can find by a simple google search.</p>
<p>→ No "Hello" and "Hi". Ask your question directly.</p>
<p>→ Prefer the common chat over DM. Seek help in public. So you will get help from more people and others will learn from your convo.</p>
<p>→ Learn to wait. People have their own life to live - value others' time.</p>
<p>→ Be kind to others.</p>
<p>Sound rude or not friendly? Trust me, follow the above points blindly. You will see the magic in your community. You will realize soon why it's important.</p>
<h3 id="heading-8-final-note">8) Final Note</h3>
<p>→ This article is written for BEGINNERS to help them to kick-start the open-source journey.</p>
<p>→ Here I used changing a string as an example to make the beginners understand it clearly. You can do big stuff.</p>
<p>→ This article is more focused on contributing with code, but you can also contribute open-source in other ways like helping in translations.</p>
<p>→ OpenSource is for EVERYONE. Just start. Learn on the go.</p>
<p>ALL THE BEST.</p>
<p>OpenSource is &lt;3</p>
<p>I hope it adds some value to you.</p>
<p>Thanks for reading.</p>
]]></content:encoded></item><item><title><![CDATA[Android Navigation - Safely navigate screens on button clicks]]></title><description><![CDATA[All of us use the android navigation component in our project to navigate from one screen to another easily.
In some cases, you may need to navigate to another screen on a button-click event. To do that, you may do like the below code.
binding.naviga...]]></description><link>https://blog.dhina17.dev/safely-navigate-screens-on-button-clicks-android-navigation</link><guid isPermaLink="true">https://blog.dhina17.dev/safely-navigate-screens-on-button-clicks-android-navigation</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Android Studio]]></category><dc:creator><![CDATA[Dhina17]]></dc:creator><pubDate>Sun, 15 Jan 2023 17:56:19 GMT</pubDate><content:encoded><![CDATA[<p>All of us use the android <a target="_blank" href="https://developer.android.com/guide/navigation">navigation component</a> in our project to navigate from one screen to another easily.</p>
<p>In some cases, you may need to navigate to another screen on a button-click event. To do that, you may do like the below code.</p>
<pre><code class="lang-kotlin">binding.navigateButton.setOnClickListener {
          findNavController()
                .navigate(R.id.action_firstFragment_to_secondFragment)
}
</code></pre>
<p>The above code won't cause any issues in simple use cases.</p>
<p>But if you click the button repetitively and your destination screen takes some time to load, you will end up with <code>IllegalArgumentException</code>.</p>
<pre><code class="lang-kotlin">Fatal Exception: java.lang.IllegalArgumentException: Navigation action/destination io.github.dhina17.template:id/action_firstFragment_to_secondFragment cannot be found from the current destination Destination(io.github.dhina17.template:id/secondFragment) label=SecondFragment
</code></pre>
<p>Because, on your first click, the current destination is changed to the target destination (Second Fragment) in the navigation controller but the source screen (First Fragment) is still alive due to the load time of the target destination screen.</p>
<p>So your further clicks on the button lead to telling the navigation controller to do the navigation action i.e <code>action_firstFragment_to_secondFragment</code> from the Second Fragment. But that action <code>action_firstFragment_to_secondFragment</code> is not present in the Second Fragment so using that action causes <code>IllegalArgumentException</code> .</p>
<p>So your app will be crashed.</p>
<p>Let's see how to prevent this issue.</p>
<h2 id="heading-solution-1-use-try-catch">Solution 1 - Use try-catch</h2>
<p>As usual, catching the exception will prevent your app from crashing.</p>
<pre><code class="lang-kotlin">binding.navigateButton.setOnClickListener {
    <span class="hljs-keyword">try</span> {
        findNavController()
            .navigate(R.id.action_firstFragment_to_secondFragment)
    } <span class="hljs-keyword">catch</span> (e: Exception) {
                <span class="hljs-comment">// No need to do anything.</span>
    }
}
</code></pre>
<p>Looks ugly yes? Create a kotlin extension function to the <code>NavController</code> to avoid repeating code.</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> NavController.<span class="hljs-title">safeNavigate</span><span class="hljs-params">(<span class="hljs-meta">@IdRes</span> actionId: <span class="hljs-type">Int</span>)</span></span> {
    <span class="hljs-keyword">try</span> {
        navigate(actionId)
    } <span class="hljs-keyword">catch</span> (e: Exception) {
        <span class="hljs-comment">// No need to do anything.</span>
    }
}

<span class="hljs-comment">// Then use the extension function on the button click</span>
binding.navigateButton.setOnClickListener {   
        findNavController()
            .safeNavigate(R.id.action_firstFragment_to_secondFragment)
}
</code></pre>
<h2 id="heading-solution-2-check-the-current-destination-before-navigating">Solution 2 - Check the current destination before navigating</h2>
<p>Check if the action is present in the current destination or not before navigating by writing an extension function.</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> NavController.<span class="hljs-title">safeNavigate</span><span class="hljs-params">(<span class="hljs-meta">@IdRes</span> actionId: <span class="hljs-type">Int</span>)</span></span> {
    currentDestination?.getAction(actionId)?.run { navigate(direction) }
}

<span class="hljs-comment">// Then use the extension function on the button click</span>
binding.navigateButton.setOnClickListener {   
        findNavController()
            .safeNavigate(R.id.action_firstFragment_to_secondFragment)
}
</code></pre>
<p>Note: I ain't the author of this code. I found it somewhere on the internet.</p>
<p>Thanks for reading. &lt;3</p>
<p>Dhina17</p>
]]></content:encoded></item><item><title><![CDATA[Dependency Injection with Hilt in Android - Part 2]]></title><description><![CDATA[It is a continuous part of our previous article about Dependency Injection with Hilt. Please check it if you haven't yet.
Let's see some common 'How to' in Hilt in this article.
How to field-inject dependencies in non-Android classes?
As we know, To ...]]></description><link>https://blog.dhina17.dev/dependency-injection-with-hilt-in-android-part-2</link><guid isPermaLink="true">https://blog.dhina17.dev/dependency-injection-with-hilt-in-android-part-2</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[dependency injection]]></category><category><![CDATA[Android Studio]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Dhina17]]></dc:creator><pubDate>Sun, 08 Jan 2023 13:58:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/MfnX4XtGnvU/upload/7b7c290a64eece4b647c5b3a63caa49e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It is a continuous part of our <a target="_blank" href="https://dhina17.hashnode.dev/dependency-injection-with-hilt-in-android">previous article</a> about Dependency Injection with Hilt. Please check it if you haven't yet.</p>
<p>Let's see some common 'How to' in Hilt in this article.</p>
<h2 id="heading-how-to-field-inject-dependencies-in-non-android-classes">How to field-inject dependencies in non-Android classes?</h2>
<p>As we know, To field-inject a dependency, we use <strong>@Inject</strong> annotation but it supports only Android classes by default with a predefined entry point <strong>@AndroidEntryPoint.</strong></p>
<p>To field-inject a dependency in non-android classes (not supported by <strong>@AndroidEntryPoint</strong>), we must create a new entry point and access the dependencies through it.</p>
<h3 id="heading-example">Example</h3>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Developer</span></span>() {
    <span class="hljs-comment">// We gonna inject for this</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> cup = Cup()

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">drinkCoffee</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// further steps to drink coffee</span>
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cup</span></span>() {
    <span class="hljs-comment">// A cup</span>
}
</code></pre>
<p>As you can see from the example, we gonna field-inject <code>Cup</code> instance into the <code>Developer</code> class.</p>
<p>First, define a new entry point which provides the Cup instance.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">import</span> dagger.hilt.EntryPoint
<span class="hljs-keyword">import</span> dagger.hilt.InstallIn
<span class="hljs-keyword">import</span> dagger.hilt.components.SingletonComponent

<span class="hljs-meta">@EntryPoint</span>
<span class="hljs-meta">@InstallIn(SingletonComponent::class)</span>
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CupEntryPoint</span> </span>{
    <span class="hljs-keyword">var</span> cup: Cup
}
</code></pre>
<p>Next, access the <code>cup</code> instance from the entry point in the <code>Developer</code> class.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">import</span> dagger.hilt.android.EntryPointAccessors

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Developer</span> </span>{

    <span class="hljs-comment">// Get the entry point</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> entryPoint = EntryPointAccessors.fromApplication(
        applicationContext,
        CupEntryPoint::<span class="hljs-keyword">class</span>.java
    )
    <span class="hljs-comment">// Get the instance</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> cup = entryPoint.cup

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">drinkCoffee</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// further steps to drink coffee</span>
    }
}
</code></pre>
<p>First, get the entry point using the respective method from the EntryPointAccessors.</p>
<p>Here we have used <code>fromApplication()</code> since the CupEntryPoint was created as a singleton component. You can use other methods like <code>fromActivity()</code> based on your needs.</p>
<p>After that, we accessed the dependency via the entry point. Simple.</p>
<h2 id="heading-how-to-provide-multiple-instances-of-the-same-type-in-hilt">How to provide multiple instances of the same type in Hilt?</h2>
<p>In some cases, you may need to inject multiple instances of the same type into a class. In this scenario, you have to differentiate your hilt bindings with Qualifiers.</p>
<h3 id="heading-example-1">Example</h3>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Developer</span> <span class="hljs-meta">@Inject</span> <span class="hljs-keyword">constructor</span></span>(
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> coffeeCup: Cup,
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> teaCup: Cup
) {

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">drinkCoffee</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// further steps to drink coffee</span>
        coffeeCup.useHere()
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">drinkTea</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// further steps to drink tea</span>
        teaCup.useHere()
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cup</span></span>() {
    <span class="hljs-comment">// A cup</span>
}
</code></pre>
<p>From the above example, you may guess the requirements. The <code>Developer</code> class needs two <code>Cup</code> instances i.e one to drink the coffee and another one to drink the tea.</p>
<p>By default, Hilt doesn't about how to differentiate the bindings when you provide the two instances of the Cup. So we have to use Qualifiers to let the hilt know which one is for coffee and which one is for tea.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Qualifier</span>
<span class="hljs-keyword">annotation</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CoffeeCup</span></span>

<span class="hljs-meta">@Qualifier</span>
<span class="hljs-keyword">annotation</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TeaCup</span></span>

<span class="hljs-meta">@Module</span>
<span class="hljs-meta">@InstallIn(SingletonComponent::class)</span>
<span class="hljs-keyword">object</span> DeveloperModule {

   <span class="hljs-meta">@Provides</span>
   <span class="hljs-meta">@CoffeeCup</span>
   <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">provideCoffeeCup</span><span class="hljs-params">()</span></span>: Cup {
     <span class="hljs-comment">// Your way to create the coffee cup</span>
     <span class="hljs-comment">// For simplicity</span>
     <span class="hljs-keyword">return</span> Cup()
   }

   <span class="hljs-meta">@Provides</span>
   <span class="hljs-meta">@TeaCup</span>
   <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">provideTeaCup</span><span class="hljs-params">()</span></span>: Cup {
     <span class="hljs-comment">// Your way to create the tea cup.</span>
     <span class="hljs-comment">// For simplicity</span>
     <span class="hljs-keyword">return</span> Cup()
   }

   <span class="hljs-meta">@Provides</span>
   <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">provideDeveloper</span><span class="hljs-params">(
        <span class="hljs-meta">@CoffeeCup</span> coffeeCup: <span class="hljs-type">Cup</span>,
        <span class="hljs-meta">@TeaCup</span> teaCup: <span class="hljs-type">Cup</span>
   )</span></span>: Developer {
        <span class="hljs-keyword">return</span> Developer(coffeeCup, teaCup)
   }
}
</code></pre>
<p>I have created Two annotation classes - <code>CoffeeCup</code> and <code>TeaCup</code> with annotation <strong>@Qualifiers</strong>.</p>
<p>Then use the qualifiers to differentiate the instances. Simple.</p>
<p><strong>Note:</strong> Here I have used the Cup class with no difference and no constructor parameters for the sake of simplicity. Anyway the same way, you can provide bindings as per your needs easily.</p>
<p>I hope you learned something from this article.</p>
<p>Thanks for reading. &lt;3.</p>
]]></content:encoded></item><item><title><![CDATA[Dependency Injection with Hilt in Android]]></title><description><![CDATA[Basics
What is Dependency Injection?
Consider this example, you are an Android Developer who drinks coffee. To drink coffee, you need a cup (of course xD).
Implement this concept in the code
class Developer() {

    fun drinkCoffee() {
        // ste...]]></description><link>https://blog.dhina17.dev/dependency-injection-with-hilt-in-android</link><guid isPermaLink="true">https://blog.dhina17.dev/dependency-injection-with-hilt-in-android</guid><category><![CDATA[Android]]></category><category><![CDATA[androidappdevelopment ]]></category><category><![CDATA[android development]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Dhina17]]></dc:creator><pubDate>Sun, 01 Jan 2023 14:16:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/080c2b79baddaf7f566782fa558249e2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-basics">Basics</h2>
<h3 id="heading-what-is-dependency-injection">What is Dependency Injection?</h3>
<p>Consider this example, you are an Android Developer who drinks coffee. To drink coffee, you need a cup (of course xD).</p>
<p>Implement this concept in the code</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Developer</span></span>() {

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">drinkCoffee</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// steps to drink coffee</span>
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cup</span></span>() {
    <span class="hljs-comment">// A cup</span>
}
</code></pre>
<p>From this example, you can assume the <mark>Developer</mark> class needs a <mark>Cup</mark> instance to drink coffee.</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Developer</span></span>() {

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">drinkCoffee</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">val</span> cup = Cup()
        <span class="hljs-comment">// further steps to drink coffee</span>
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cup</span></span>() {
    <span class="hljs-comment">// A cup</span>
}
</code></pre>
<p>The above approach creates a new instance of a <mark>Cup</mark> whenever the <mark>Developer</mark> drinks coffee.</p>
<p>Generally, do you buy a new cup whenever you drink coffee? Big No (Except for an insane rich dude)</p>
<p>One person needs only one cup to drink coffee.</p>
<p>Similarly, in programming, creating unwanted instances is costly so you need to properly handle it.</p>
<p>Let's come back to our example, To avoid the creation of the <mark>Cup</mark> instance every time inside the <mark>Developer</mark> class, we gonna provide a <mark>Cup</mark> instance to the <mark>Developer</mark> class. This technique is called Dependency Injection.</p>
<p><mark>Cup</mark> is a dependency of the <mark>Developer</mark> class.</p>
<p>We can inject the dependency in two ways:</p>
<ul>
<li><p>Constructor injection - Provide the dependency via the constructor</p>
</li>
<li><p>Field injection - Provide the dependency via field (setters)</p>
</li>
</ul>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Developer</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> cup: Cup? = <span class="hljs-literal">null</span>) {

    <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> fieldCup: Cup

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">drinkCoffee</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// Use the cup here</span>
        <span class="hljs-comment">// further steps to drink coffee</span>
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cup</span></span>() {
    <span class="hljs-comment">// A cup</span>
}

<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
   <span class="hljs-comment">// Create cup instance</span>
   <span class="hljs-keyword">val</span> cup = Cup()
   <span class="hljs-comment">// Constructor injection</span>
   <span class="hljs-keyword">val</span> developer = Developer(cup)
   <span class="hljs-comment">// Field injection</span>
   <span class="hljs-keyword">val</span> developer = Developer()
   developer.fieldCup = cup
}
</code></pre>
<p>Dependency injection makes our code loosely coupled so easily testable and reusable.</p>
<h3 id="heading-manual-dependency-injection">Manual Dependency Injection</h3>
<p>We can manually handle the dependency by creating and providing the instances as we do in the above code snippet.</p>
<p>It takes more time and is even hard to manage its lifecycle properly.</p>
<p><strong>Example</strong>: Consider the Developer and Cup example, Having the cup all the time at your work table is unwanted, agree? You need the cup only when you drink the coffee.</p>
<p>Similarly, providing dependencies according to the consumer's lifecycle is crucial. For example, creating and providing an instance to an activity should happen only in the activity lifecycle. In this case, holding a singleton instance is not a good way.</p>
<h3 id="heading-hilt">Hilt</h3>
<p><a target="_blank" href="https://developer.android.com/training/dependency-injection/hilt-android">Hilt</a> is a dependency injection library created on top of <a target="_blank" href="https://dagger.dev/">Dagger</a> specifically for Android projects.</p>
<p>Hilt reduces the code boilerplates and makes dependency management easier for developers.</p>
<h2 id="heading-setup-hilt-in-your-project">Setup Hilt in your project</h2>
<h3 id="heading-add-dependencies">Add dependencies</h3>
<p>Add <code>hilt-android-gradle-plugin</code> plugin to your project's root <code>build.gradle</code> file.</p>
<pre><code class="lang-kotlin">buildscript {
    ext {
        ...
        hilt_version = <span class="hljs-string">'2.44.2'</span>
    }

    dependencies {
        ...
        classpath <span class="hljs-string">"com.google.dagger:hilt-android-gradle-plugin:<span class="hljs-variable">$hilt_version</span>"</span>
    }
}
</code></pre>
<p>Apply the plugin and add the following dependencies in your <code>app/build.gradle</code> file.</p>
<pre><code class="lang-kotlin">...
plugins {
  id <span class="hljs-string">'kotlin-kapt'</span>
  id <span class="hljs-string">'com.google.dagger.hilt.android'</span>
}

android {
  ...
}

dependencies {
  implementation <span class="hljs-string">"com.google.dagger:hilt-android:<span class="hljs-variable">$hilt_version</span>"</span>
  kapt <span class="hljs-string">"com.google.dagger:hilt-compiler:<span class="hljs-variable">$hilt_version</span>"</span>
}
</code></pre>
<h3 id="heading-hilt-application-class">Hilt application class</h3>
<pre><code class="lang-kotlin"><span class="hljs-meta">@HiltAndroidApp</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">App</span>: <span class="hljs-type">Application</span></span>()
</code></pre>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">manifest</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>&gt;</span>
    ...
    <span class="hljs-tag">&lt;<span class="hljs-name">application</span>
        <span class="hljs-attr">...</span>
        <span class="hljs-attr">android:name</span>=<span class="hljs-string">".App"</span>
        <span class="hljs-attr">...</span> &gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">application</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">manifest</span>&gt;</span>
</code></pre>
<p><code>HiltAndroidApp</code> annotation creates a hilt application component which generates the base class for your application which acts as the application-level dependency injection container.</p>
<p>It obeys the Application object lifecycle.</p>
<h2 id="heading-using-hilt-in-mvvm-architecture">Using Hilt in MVVM architecture</h2>
<p>Model-View-ViewModel (MVVM) is the <a target="_blank" href="https://developer.android.com/topic/architecture">recommended app architecture</a> by Jetpack/developer docs.</p>
<p>The simple architecture that we use to demonstrate hilt here</p>
<blockquote>
<p>Activity/Fragment ---&gt; ViewModel ---&gt; Repository ---&gt; Datasources</p>
</blockquote>
<p>The above flow indicates the dependency of each class.</p>
<p>In text,</p>
<ul>
<li><p>Datasources (database/network) are needed by the repository</p>
</li>
<li><p>The repository is needed by the ViewModel</p>
</li>
<li><p>The ViewModel is needed by the Activity/Fragment</p>
</li>
</ul>
<h3 id="heading-initial-class-setup">Initial class setup</h3>
<p>MainActivity.kt</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> io.github.dhina17.template.activities

<span class="hljs-keyword">import</span> android.os.Bundle
<span class="hljs-keyword">import</span> androidx.appcompat.app.AppCompatActivity
<span class="hljs-keyword">import</span> androidx.databinding.DataBindingUtil
<span class="hljs-keyword">import</span> io.github.dhina17.template.R
<span class="hljs-keyword">import</span> io.github.dhina17.template.databinding.ActivityMainBinding

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>() {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> binding: ActivityMainBinding

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(<span class="hljs-keyword">this</span>, R.layout.activity_main)
    }
}
</code></pre>
<p>MainViewModel.kt</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> io.github.dhina17.template.activities

<span class="hljs-keyword">import</span> androidx.lifecycle.ViewModel


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainViewModel</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> repository: Repository) : ViewModel() {

}
</code></pre>
<p>Repository.kt</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> io.github.dhina17.template.activities

<span class="hljs-keyword">import</span> io.github.dhina17.template.Database

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Repository</span></span>(database: Database) {

}
</code></pre>
<p>Data sources are nothing but instances (however static) of a database or network service(retrofit).</p>
<h3 id="heading-dependency-injection-using-hilt">Dependency Injection using Hilt</h3>
<pre><code class="lang-kotlin"><span class="hljs-meta">@AndroidEntryPoint</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>() {
}
</code></pre>
<p><strong>@AndroidEntryPoint</strong> - It's a predefined entry point in the hilt library that generates a hilt component for the specified Android class (<a target="_blank" href="https://developer.android.com/training/dependency-injection/hilt-android#android-classes">list of supported classes</a>)</p>
<p>Since we can't use constructor injection in an activity, We will field-inject the MainViewModel in the MainActivity.</p>
<p>We use <strong>@Inject</strong> annotation to inject an instance into a field of a class.</p>
<p><strong>Note</strong>: The field which will be injected cannot be private.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@AndroidEntryPoint</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>() {
    <span class="hljs-meta">@Inject</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> something: Something
}
</code></pre>
<p>However, in the case of ViewModel, we will take the advantage of Hilt extensions to make our work easier. We don't need to provide any binding information to the hilt. We will see about bindings later.</p>
<p>To provide the MainViewModel object, add <strong>@HiltViewModel</strong> annotation and <strong>@Inject</strong> annotation to the constructor of the MainViewModel class.</p>
<p><strong>@Inject</strong> annotation in the constructor provides the binding information to the Hilt to generate an instance. The parameters of an annotated constructor of a class are the dependencies of a class.</p>
<p>Here, MainViewModel has a dependency i.e Repository</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> io.github.dhina17.template.activities

<span class="hljs-keyword">import</span> androidx.lifecycle.ViewModel
<span class="hljs-keyword">import</span> dagger.hilt.android.lifecycle.HiltViewModel
<span class="hljs-keyword">import</span> javax.inject.Inject

<span class="hljs-meta">@HiltViewModel</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainViewModel</span> <span class="hljs-meta">@Inject</span> <span class="hljs-keyword">constructor</span></span>(
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> repository: Repository
): ViewModel() {

}
</code></pre>
<p>Simply, in the MainActivity, get the MainViewModel object by using <strong>viewModels()</strong> delegate from <a target="_blank" href="https://developer.android.com/kotlin/ktx">KTX</a>.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> io.github.dhina17.template.activities

<span class="hljs-keyword">import</span> android.os.Bundle
<span class="hljs-keyword">import</span> androidx.activity.viewModels
<span class="hljs-keyword">import</span> androidx.appcompat.app.AppCompatActivity
<span class="hljs-keyword">import</span> androidx.databinding.DataBindingUtil
<span class="hljs-keyword">import</span> dagger.hilt.android.AndroidEntryPoint
<span class="hljs-keyword">import</span> io.github.dhina17.template.R
<span class="hljs-keyword">import</span> io.github.dhina17.template.databinding.ActivityMainBinding

<span class="hljs-meta">@AndroidEntryPoint</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>() {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> binding: ActivityMainBinding

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> viewModel: MainViewModel <span class="hljs-keyword">by</span> viewModels()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(<span class="hljs-keyword">this</span>, R.layout.activity_main)
    }
}
</code></pre>
<p>We don't need to create any Hilt Module i.e Telling hilt how to create an instance of MainViewModel. Hilt will take care of that for us.</p>
<p>Now, we are injecting the Repository instance into the MainViewModel.</p>
<p>However, hilt doesn't know how to create an instance of a Repository (i.e <a target="_blank" href="https://developer.android.com/training/dependency-injection/hilt-android#android-classes">Other than supported android classes</a>).</p>
<p>To tell hilt about that, we have to provide binding information to the hilt with a Hilt Module.</p>
<p><strong>Note:</strong> We don't need to do anything if the Repository doesn't have any parameters in the constructor. <strong>@Inject</strong> annotation is only enough. Hilt will take care of that.</p>
<p>Create a hilt module for the Repository</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> io.github.dhina17.template.activities

<span class="hljs-keyword">import</span> dagger.Module
<span class="hljs-keyword">import</span> dagger.Provides
<span class="hljs-keyword">import</span> dagger.hilt.InstallIn
<span class="hljs-keyword">import</span> dagger.hilt.android.components.ViewModelComponent

<span class="hljs-meta">@Module</span>
<span class="hljs-meta">@InstallIn(ViewModelComponent::class)</span>
<span class="hljs-keyword">object</span> RepoModule {

    <span class="hljs-meta">@Provides</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">provideRepository</span><span class="hljs-params">(database: <span class="hljs-type">Database</span>)</span></span>: Repository {
        <span class="hljs-keyword">return</span> Repository(database)
    }
}
</code></pre>
<p><strong>@ Module</strong> - Indicates that a Hilt Module</p>
<p><strong>@InstallIn(ViewModelComponent::class)</strong> - To make available the dependencies in the ViewModel scope. For more info, <a target="_blank" href="https://developer.android.com/training/dependency-injection/hilt-android#generated-components">here</a>.</p>
<p>Now, hilt doesn't know how to provide a database instance so we have to tell hilt by creating a module for it again.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> io.github.dhina17.template.activities

<span class="hljs-keyword">import</span> android.content.Context
<span class="hljs-keyword">import</span> dagger.Module
<span class="hljs-keyword">import</span> dagger.hilt.InstallIn
<span class="hljs-keyword">import</span> dagger.hilt.android.qualifiers.ApplicationContext
<span class="hljs-keyword">import</span> dagger.hilt.components.SingletonComponent

<span class="hljs-meta">@Module</span>
<span class="hljs-meta">@InstallIn(SingletonComponent::class)</span>
<span class="hljs-keyword">object</span> DatabaseModule {

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">provideDatabase</span><span class="hljs-params">(<span class="hljs-meta">@ApplicationContext</span> context: <span class="hljs-type">Context</span>)</span></span>: Database {
        <span class="hljs-keyword">return</span> Database(context)
    }
}
</code></pre>
<p><strong>@InstallIn(SingletonComponent::class)</strong> - Hilt will create a singleton instance of the database since we require a single instance for the complete application lifecycle.</p>
<p><strong>@ApplicationContext</strong> - This is a predefined Qualifier in the Hilt. Hilt will provide the application context by itself.</p>
<p>That's it for today. We will see some other extra use cases of Hilt in the next post.</p>
<p>I hope you gained some knowledge here. Share with your friends.</p>
<p>Thanks for reading. <strong>&lt;3</strong></p>
]]></content:encoded></item><item><title><![CDATA[How does an application run on Android OS?]]></title><description><![CDATA[Android APK is a file format(typically zip) containing the app's source code and resources.
Generally, Java/Kotlin code will be compiled into java bytecode. So we need a Virtual Machine to run the bytecode.
Android has an optimized runtime environmen...]]></description><link>https://blog.dhina17.dev/how-does-an-application-run-on-android-os</link><guid isPermaLink="true">https://blog.dhina17.dev/how-does-an-application-run-on-android-os</guid><category><![CDATA[android internals]]></category><category><![CDATA[Android]]></category><category><![CDATA[aosp]]></category><category><![CDATA[android development]]></category><dc:creator><![CDATA[Dhina17]]></dc:creator><pubDate>Sun, 25 Dec 2022 15:50:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/f7a335326cfa5b13df8a89e20a6c3d7a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Android APK is a file format(typically zip) containing the app's source code and resources.</p>
<p>Generally, Java/Kotlin code will be compiled into java bytecode. So we need a Virtual Machine to run the bytecode.</p>
<p>Android has an optimized runtime environment (like a virtual machine) to execute the compiled app instead of a Java VM. So android doesn't accept java byte code. It needs a different format based on the runtime.</p>
<ul>
<li><p>Dalvik VM</p>
</li>
<li><p>Android Runtime (ART)</p>
</li>
</ul>
<p>Let's see about them.</p>
<h3 id="heading-dalvik-vm">Dalvik VM</h3>
<p>Dalvik VM is an optimized virtual machine for android to run android applications. It executes the .dex files.</p>
<p>Android APK contains the compiled app code in DEX format. You can check that by unpacking your apk and seeing the list of .dex files in it.</p>
<p>Your app code conversion happens in the following way.</p>
<blockquote>
<p>.java / .kt (source) ---&gt; .class (bytecode) ---&gt; .dex ---&gt; Dalvik VM ---&gt; machine code</p>
</blockquote>
<ul>
<li><p>Dalvik uses JIT (Just In Time) compilation for translating the dex code into machine code to execute.</p>
</li>
<li><p>Whenever your app runs, Dalvik will JIT compile the part of the code which will run instead of the complete code.</p>
</li>
<li><p>Since Dalvik VM interprets the DEX every time the app runs, it gives a lower performance than native code execution. (like why python is slower than C++).</p>
</li>
<li><p>It's deprecated in Android 5.0</p>
</li>
</ul>
<h3 id="heading-android-runtime-art">Android Runtime (ART)</h3>
<p>ART is introduced to solve the performance issue of Dalvik VM. It enables android applications to run natively.</p>
<p>There is no change in the APK. ART is also using the DEX format under the hood.</p>
<ul>
<li><p>ART uses Ahead-of-time compilation (AOT) to compile apps into an ART-specific file format called oat at the time of app installation.</p>
</li>
<li><p>ART uses the dex2oat tool to compile the oat files for an app.</p>
</li>
<li><p>oat file is an executable file format which contains the AOT compiled code of the app. Technically, it's an ELF file.</p>
</li>
<li><p>After that, the app code will be executed from the compiled oat file directly.</p>
</li>
</ul>
<p>Your app code conversion happens in the following way</p>
<blockquote>
<p>.java/kt (source) ---&gt; .class (bytecode) ---&gt; .dex ---&gt; .oat ---&gt; ART (execution)</p>
</blockquote>
<ul>
<li><p>It has a better performance than Dalvik since it runs the code natively.</p>
</li>
<li><p>It is the default runtime in Android 5.0+.</p>
</li>
</ul>
<h3 id="heading-difference-between-dalvik-and-art">Difference between Dalvik and ART</h3>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/25/ART_view.png/1024px-ART_view.png" alt /></p>
<p>image source: <a target="_blank" href="https://en.wikipedia.org/wiki/Android_Runtime">Wikipedia</a></p>
<p>A lot to learn in Android Runtime. A deep dive guide for another day.</p>
<p>Thanks for Reading ❤️</p>
]]></content:encoded></item><item><title><![CDATA[Android: Validate your form fields using Kotlin StateFlow]]></title><description><![CDATA[Form validation is important in providing users with a good user experience. Let's see how we can implement the form validation using Kotlin StateFlow in a smart and easy way!
Note: This article is about android XML layouts!
Prerequisites

Basic Andr...]]></description><link>https://blog.dhina17.dev/android-validate-your-form-fields-using-kotlin-stateflow</link><guid isPermaLink="true">https://blog.dhina17.dev/android-validate-your-form-fields-using-kotlin-stateflow</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Android Studio]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[android apps]]></category><dc:creator><![CDATA[Dhina17]]></dc:creator><pubDate>Sun, 18 Dec 2022 15:39:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671369346133/O270zKEJk.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Form validation is important in providing users with a good user experience. Let's see how we can implement the form validation using Kotlin StateFlow in a smart and easy way!</p>
<p>Note: This article is about android XML layouts!</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>Basic Android App Development</p>
</li>
<li><p><a target="_blank" href="https://developer.android.com/topic/libraries/data-binding/two-way">Two-way data binding</a></p>
</li>
<li><p>Kotlin <a target="_blank" href="https://developer.android.com/kotlin/coroutines">Coroutines</a> and Flows (<a target="_blank" href="https://developer.android.com/kotlin/flow">Flow</a> / <a target="_blank" href="https://developer.android.com/kotlin/flow/stateflow-and-sharedflow">StateFlow</a>)</p>
</li>
</ul>
<h2 id="heading-1-create-form-layout">1) Create form layout</h2>
<p>Create the XML layout for the form UI</p>
<p>Here we are creating a sign-in form which contains email and password fields.</p>
<ul>
<li><p>Create a 'viewModel' variable to bind the ViewModel reference to the layout.</p>
</li>
<li><p>With the ViewModel reference, we can access the variables in the ViewModel which we gonna hold the field values (text input). (Two-way data binding - Check android:text attribute.)</p>
</li>
</ul>
<p><strong>Note:</strong></p>
<p>We have used <a target="_blank" href="https://m2.material.io/components/text-fields">Material Design Components</a> to create the views since it has a lot of features like error text which is very convenient for us to show errors in the field.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">layout</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
    <span class="hljs-attr">xmlns:tools</span>=<span class="hljs-string">"http://schemas.android.com/tools"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">data</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">variable</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"viewModel"</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"io.github.dhina17.justmyfinance.ui.signin.SignInViewModel"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">data</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">LinearLayout</span>
        <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
        <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"match_parent"</span>
        <span class="hljs-attr">android:gravity</span>=<span class="hljs-string">"center"</span>
        <span class="hljs-attr">android:orientation</span>=<span class="hljs-string">"vertical"</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">com.google.android.material.textfield.TextInputLayout</span>
            <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/emailContainer"</span>
            <span class="hljs-attr">style</span>=<span class="hljs-string">"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"</span>
            <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
            <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
            <span class="hljs-attr">android:hint</span>=<span class="hljs-string">"Email"</span>
            <span class="hljs-attr">android:paddingHorizontal</span>=<span class="hljs-string">"20dp"</span>
            <span class="hljs-attr">android:paddingVertical</span>=<span class="hljs-string">"10dp"</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">com.google.android.material.textfield.TextInputEditText</span>
                <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/emailField"</span>
                <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
                <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
                <span class="hljs-attr">android:inputType</span>=<span class="hljs-string">"textEmailAddress"</span>
                <span class="hljs-attr">android:text</span>=<span class="hljs-string">"@={viewModel.email}"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">com.google.android.material.textfield.TextInputLayout</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">com.google.android.material.textfield.TextInputLayout</span>
            <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/passwordContainer"</span>
            <span class="hljs-attr">style</span>=<span class="hljs-string">"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"</span>
            <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
            <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
            <span class="hljs-attr">android:hint</span>=<span class="hljs-string">"Password"</span>
            <span class="hljs-attr">android:paddingHorizontal</span>=<span class="hljs-string">"20dp"</span>
            <span class="hljs-attr">android:paddingVertical</span>=<span class="hljs-string">"10dp"</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">com.google.android.material.textfield.TextInputEditText</span>
                <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/passwordField"</span>
                <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
                <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
                <span class="hljs-attr">android:inputType</span>=<span class="hljs-string">"textPassword"</span>
                <span class="hljs-attr">android:text</span>=<span class="hljs-string">"@={viewModel.password}"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">com.google.android.material.textfield.TextInputLayout</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">com.google.android.material.button.MaterialButton</span>
            <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/signInButton"</span>
            <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
            <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
            <span class="hljs-attr">android:layout_gravity</span>=<span class="hljs-string">"center"</span>
            <span class="hljs-attr">android:layout_marginHorizontal</span>=<span class="hljs-string">"20dp"</span>
            <span class="hljs-attr">android:layout_marginVertical</span>=<span class="hljs-string">"10dp"</span>
            <span class="hljs-attr">android:paddingVertical</span>=<span class="hljs-string">"15dp"</span>
            <span class="hljs-attr">android:text</span>=<span class="hljs-string">"Sign In"</span>
            <span class="hljs-attr">tools:text</span>=<span class="hljs-string">"Sign In"</span> /&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">LinearLayout</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">layout</span>&gt;</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671376689869/3zZizCZyk.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-2-create-a-fragment-with-a-viewmodel">2) Create a fragment with a ViewModel</h2>
<ul>
<li><p>Create a ViewModel named "SignInViewModel.kt' with two MutableStateFlow&lt;String&gt; fields named 'email' and 'password' to hold the respective text input from the UI (form fields).</p>
</li>
<li><p>We already bound the ViewModel fields to the respective TextInputEditText widget (view). (See the above XML code)</p>
</li>
<li><p>Create a fragment named 'SignInFragment.kt' and inflate the view using data binding.</p>
</li>
<li><p>Initialize the ViewModel and bind it to the layout. (In simple words, assign the created ViewModel reference to the variable present in the XML layout)</p>
</li>
</ul>
<pre><code class="lang-kotlin"><span class="hljs-comment">// SignInViewModel.kt</span>
<span class="hljs-keyword">package</span> io.github.dhina17.justmyfinance.ui.signin

<span class="hljs-keyword">import</span> androidx.lifecycle.ViewModel
<span class="hljs-keyword">import</span> dagger.hilt.android.lifecycle.HiltViewModel
<span class="hljs-keyword">import</span> kotlinx.coroutines.flow.MutableStateFlow
<span class="hljs-keyword">import</span> javax.inject.Inject

<span class="hljs-meta">@HiltViewModel</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SignInViewModel</span> <span class="hljs-meta">@Inject</span> <span class="hljs-keyword">constructor</span></span>() : ViewModel() {
    <span class="hljs-keyword">val</span> email = MutableStateFlow(<span class="hljs-string">""</span>)
    <span class="hljs-keyword">val</span> password = MutableStateFlow(<span class="hljs-string">""</span>)
}
</code></pre>
<pre><code class="lang-kotlin"><span class="hljs-comment">// SignInFragment.kt</span>
<span class="hljs-keyword">package</span> io.github.dhina17.justmyfinance.ui.signin

<span class="hljs-keyword">import</span> android.os.Bundle
<span class="hljs-keyword">import</span> android.view.LayoutInflater
<span class="hljs-keyword">import</span> android.view.View
<span class="hljs-keyword">import</span> android.view.ViewGroup
<span class="hljs-keyword">import</span> androidx.fragment.app.Fragment
<span class="hljs-keyword">import</span> androidx.fragment.app.viewModels
<span class="hljs-keyword">import</span> dagger.hilt.android.AndroidEntryPoint
<span class="hljs-keyword">import</span> io.github.dhina17.justmyfinance.databinding.FragmentSignInBinding

<span class="hljs-meta">@AndroidEntryPoint</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SignInFragment</span> : <span class="hljs-type">Fragment</span></span>() {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> binding: FragmentSignInBinding

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> viewModel: SignInViewModel <span class="hljs-keyword">by</span> viewModels()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreateView</span><span class="hljs-params">(
        inflater: <span class="hljs-type">LayoutInflater</span>,
        container: <span class="hljs-type">ViewGroup</span>?,
        savedInstanceState: <span class="hljs-type">Bundle</span>?
    )</span></span>: View {
        <span class="hljs-comment">// Inflate the view and bind the view model to the UI</span>
        binding = FragmentSignInBinding.inflate(inflater, container, <span class="hljs-literal">false</span>)
        <span class="hljs-comment">// We have create a variable named 'viewModel' in the layout.</span>
        <span class="hljs-comment">// Assign a value to it.</span>
        binding.viewModel = viewModel
        <span class="hljs-keyword">return</span> binding.root
    }
}
</code></pre>
<p><strong>Note:</strong> Here I have used <a target="_blank" href="https://developer.android.com/training/dependency-injection/hilt-android">Hilt for dependency Injection</a> so ViewModel creation is much easier and no boilerplates. Don't confuse with it. You can use your own way to initialize the ViewModel**.**</p>
<p>You can also create an activity instead of a fragment.</p>
<h2 id="heading-3-combine-fields-and-validate">3) Combine fields and validate</h2>
<p>In your 'SignInFragment.kt',</p>
<ul>
<li><p>Combine all the form fields (StateFlow) to create a new boolean flow with producer code containing the form validation logic. This boolean flow named 'isFormValidFlow' emits true if all the form fields are valid otherwise false.</p>
</li>
<li><p>Here I have checked only if the fields are empty or not. You can use your form validation logic here.</p>
</li>
</ul>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> isFormValidFlow: Flow&lt;<span class="hljs-built_in">Boolean</span>&gt; <span class="hljs-keyword">by</span> lazy {
        <span class="hljs-comment">// Combine the fields to create a flow for form validation</span>
        combine(
            viewModel.email,
            viewModel.password
        ) { email, password -&gt;
            <span class="hljs-comment">// Check your validation here</span>
            <span class="hljs-comment">// Here I simply check if it's empty or not</span>
            <span class="hljs-keyword">val</span> isEmailValid = email.isNotEmpty()
            <span class="hljs-keyword">val</span> isPasswordValid = password.isNotEmpty()
            <span class="hljs-comment">// Return the boolean value indicates if the form fields are valid or not</span>
            isEmailValid &amp;&amp; isPasswordValid
        }
    }
</code></pre>
<h2 id="heading-4-collect-the-form-validation-flow">4) Collect the form validation flow</h2>
<p>As we know, the producer code of a flow will run on its collection (terminal operator like collect(), etc ) since it's cold in nature.</p>
<p>In SignInFragment's onViewCreated() method,</p>
<ul>
<li><p>On the sign-in button click, collect the form validation flow so the validation code will run with the current values of the email and password fields.</p>
</li>
<li><p>Depending on the validation value (boolean), proceed with your sign-in process if it's true otherwise simply show the common error for all fields like a toast.</p>
</li>
</ul>
<pre><code class="lang-kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onViewCreated</span><span class="hljs-params">(view: <span class="hljs-type">View</span>, savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {

        binding.signInButton.setOnClickListener {
            lifecycleScope.launch {
                <span class="hljs-comment">// For us, it's enough to run the isFormValidFlow producer code only once on sign in button click.</span>
                <span class="hljs-comment">// since we don't want to collect the values continuously, use first() instead of collect()</span>
                <span class="hljs-keyword">val</span> isFormValid = isFormValidFlow.first()
                <span class="hljs-keyword">if</span> (isFormValid) {
                    <span class="hljs-comment">// Proceed to sign in</span>
                } <span class="hljs-keyword">else</span> {
                    Toast.makeText(requireContext(), <span class="hljs-string">"Form fields are invalid"</span>, Toast.LENGTH_SHORT)
                        .show()
                }
            }
        }
    }
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671376782880/0XTzpHyMZ.png" alt class="image--center mx-auto" /></p>
<p>Now the form validation part is done. However, showing errors via toast is not a good practice for the form fields. We have to show the error on the respective field, right?</p>
<p>Let's do it.</p>
<h2 id="heading-5-show-errors-on-the-fields">5) Show errors on the fields</h2>
<p>As we know we are using Material Design Components to take the advantage of its own error text attr of a TextInputLayout to show the field errors.</p>
<ul>
<li><p>For our convenience, create an extension function for TextInputLayout to set the error text.</p>
</li>
<li><p>In the 'isFormValidFlow' producer code, set the error text based on the field. (Since it will run whenever the Flow is collected)</p>
</li>
</ul>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> isFormValidFlow: Flow&lt;<span class="hljs-built_in">Boolean</span>&gt; <span class="hljs-keyword">by</span> lazy {
        <span class="hljs-comment">// Combine the fields to create a flow for form validation</span>
        combine(
            viewModel.email,
            viewModel.password
        ) { email, password -&gt;
            <span class="hljs-comment">// Check your validation here</span>
            <span class="hljs-comment">// Here I simply check if it's empty or not</span>
            <span class="hljs-keyword">val</span> isEmailValid = email.isNotEmpty()
            <span class="hljs-keyword">val</span> isPasswordValid = password.isNotEmpty()
            with(binding) {
                <span class="hljs-keyword">val</span> errorText = <span class="hljs-string">"Please fill out this field."</span>
                <span class="hljs-comment">// See the 'not' operator</span>
                <span class="hljs-comment">// since if the field is valid, error is false (vice versa)</span>
                emailContainer.setFieldError(!isEmailValid, errorText)
                passwordContainer.setFieldError(!isPasswordValid, errorText)
            }
            <span class="hljs-comment">// Return the boolean value indicates if the form fields are valid or not</span>
            isEmailValid &amp;&amp; isPasswordValid
        }
    }

    <span class="hljs-comment">/**
     * Set the given error text to the TextInputLayout based on isError.
     *
     * <span class="hljs-doctag">@param</span> errorText Error Text which need to be set.
     * <span class="hljs-doctag">@param</span> isError If it's error or not.
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> TextInputLayout.<span class="hljs-title">setFieldError</span><span class="hljs-params">(isError: <span class="hljs-type">Boolean</span>, errorText: <span class="hljs-type">String</span>)</span></span> {
        error = <span class="hljs-keyword">if</span> (isError) errorText <span class="hljs-keyword">else</span> <span class="hljs-literal">null</span>
    }
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671376813422/LeId1ent8.png" alt class="image--center mx-auto" /></p>
<p><strong>Note:</strong></p>
<p>We have validated only if the field is empty or not. You can use your own validation and set error text based on that easily. Like this way.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> isFormValidFlow: Flow&lt;<span class="hljs-built_in">Boolean</span>&gt; <span class="hljs-keyword">by</span> lazy {
        <span class="hljs-comment">// Combine the fields to create a flow for form validation</span>
        combine(
            viewModel.email,
            viewModel.password
        ) { email, password -&gt;
            <span class="hljs-comment">// Check your validation here</span>
            <span class="hljs-comment">// Here I simply check if it's empty or not</span>
            <span class="hljs-keyword">val</span> isEmailValid = email.isNotEmpty()
            <span class="hljs-keyword">val</span> isPasswordFilled = password.isNotEmpty()

            <span class="hljs-comment">// Check if the password length is less than 8.</span>
            <span class="hljs-keyword">val</span> isPasswordValidLength = password.length &gt;= <span class="hljs-number">8</span>

            with(binding) {
                <span class="hljs-keyword">val</span> errorText = <span class="hljs-string">"Please fill out this field."</span>
                <span class="hljs-keyword">val</span> passwordLengthError = <span class="hljs-string">"Password must be 8 or more characters"</span>
                <span class="hljs-comment">// See the 'not' operator</span>
                <span class="hljs-comment">// since if the field is valid, error is false (vice versa)</span>
                emailContainer.setFieldError(!isEmailValid, errorText)
                passwordContainer.setFieldError(!isPasswordFilled, errorText)
                <span class="hljs-comment">// Check if the password field is filled or not. then proceed for length check.</span>
                <span class="hljs-comment">// Here use your own logic to show the error properly.</span>
                <span class="hljs-keyword">if</span> (isPasswordFilled) {
                    <span class="hljs-comment">// Set error for password length</span>
                    passwordContainer.setFieldError(!isPasswordValidLength, passwordLengthError)
                }
            }
            <span class="hljs-comment">// Return the boolean value indicates if the form fields are valid or not</span>
            isEmailValid &amp;&amp; isPasswordFilled &amp;&amp; isPasswordValidLength
        }
    }
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671376843165/_ZwDMJmbv.png" alt class="image--center mx-auto" /></p>
<p><strong>Note:</strong> Use your logic to show the error properly without overriding one error with another error.</p>
<p>Now you can remove the toast since we show the error in the fields itself.</p>
<p>You may notice that, even after filling something in the field, still the error shows until you click the sign-in button again, right?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671376914894/xucJDO_mp.png" alt class="image--center mx-auto" /></p>
<p>It's fine for most cases.</p>
<p>However, If you want to show real-time updates to the form fields after clicking the sign-in button for the first time,</p>
<ul>
<li><p>In the onViewCreated() method, Collect the 'isFormValidFlow' outside the sign-in button click listener lambda and store the validation boolean value in a local variable.</p>
</li>
<li><p>Lazily start the collection job once after the sign-in button clicked for the first time. In this way, you can see real-time updates to the fields.</p>
</li>
</ul>
<pre><code class="lang-kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onViewCreated</span><span class="hljs-params">(view: <span class="hljs-type">View</span>, savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">val</span> validationJob = lifecycleScope.launch(start = CoroutineStart.LAZY) {
            isFormValidFlow.collectLatest { isValid -&gt;
                isFormValid = isValid
            }
        }

        binding.signInButton.setOnClickListener {
            <span class="hljs-comment">// Start the job only on click</span>
            validationJob.start()
            <span class="hljs-keyword">if</span> (isFormValid) {
                <span class="hljs-comment">// Proceed to sign in</span>
            } <span class="hljs-keyword">else</span> {
                Toast.makeText(requireContext(), <span class="hljs-string">"Form fields are invalid"</span>, Toast.LENGTH_SHORT)
                    .show()
            }
        }
    }
</code></pre>
<p>Now you know how to validate the form fields in a simple way.</p>
<p>If you have any doubts or you know of any other way to validate the form fields, please let me know in the comment section.</p>
<p>Thanks for reading! ❤️</p>
<p>Dhina17</p>
]]></content:encoded></item><item><title><![CDATA[How to start android custom ROM development?]]></title><description><![CDATA[I know there is no good guide to starting Android custom ROM Development. You can learn about building.
Note: This guide doesn't contain any technical tutorials. This will show you a good way to start your AOSP / Android Custom ROM development journe...]]></description><link>https://blog.dhina17.dev/how-to-start-android-custom-rom-development</link><guid isPermaLink="true">https://blog.dhina17.dev/how-to-start-android-custom-rom-development</guid><category><![CDATA[Android]]></category><category><![CDATA[aosp]]></category><category><![CDATA[android development]]></category><category><![CDATA[community]]></category><dc:creator><![CDATA[Dhina17]]></dc:creator><pubDate>Sun, 11 Dec 2022 19:10:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670785504676/G6APoG7Ay.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I know there is no good guide to starting Android custom ROM Development. You can learn about building.</p>
<p><strong>Note: This guide doesn't contain any technical tutorials. This will show you a good way to start your AOSP / Android Custom ROM development journey in a community-oriented way.</strong></p>
<h3 id="heading-1-get-an-android-device-which-has-good-community-support">1) Get an android device which has good community support</h3>
<p>→ I recommend buying a device with a good community if you don't have any prior experience with android modding/custom ROMs.</p>
<p>→ If you have prior knowledge of custom ROMs, you can go for any good device.</p>
<p>→ How to know if a device has a good community? Check Telegram and XDA (More detail in the next point).</p>
<p><strong>Why not an emulator?</strong></p>
<p>You can start learning AOSP (framework, internals) using an emulator but I want you to involve in the android ROMs community and learn from them so the actual device is the best way.</p>
<h3 id="heading-2-join-your-device-community">2) Join your device community</h3>
<p>→ Nowadays, every device has a community in Telegram. You can find it simply by searching your device name or codename in Telegram.</p>
<p>→ How to know my device codename? A simple google search "&lt;device_name&gt; codename" will do the work.</p>
<p><img src="https://pbs.twimg.com/media/FgVHv51VUAAreMd?format=jpg&amp;name=900x900" alt /></p>
<p><img src="https://pbs.twimg.com/media/FgVHwIdVsAAlXGj?format=jpg&amp;name=900x900" alt /></p>
<p>→ Check XDA Developers Forum for your device forums. There you can find threads (posts) for your device.</p>
<p>Search "&lt;your device name&gt; xda" in google. You can find your device forum easily (only if your device has forums in XDA)</p>
<p><img src="https://pbs.twimg.com/media/FgVHwh5VQAECKBB?format=jpg&amp;name=large" alt /></p>
<p><strong>Now you found your device community, what's next?</strong></p>
<p>→ Engage with the community members.</p>
<p>→ Talk more about your device.</p>
<p>→ Get help. Give help</p>
<p>→ Make friends. Make friends</p>
<p>→ Learn from them</p>
<p><strong>Points to remember in a community</strong></p>
<p>→ Don't expect a spoon feed from the community members.</p>
<p>→ Avoid asking things that you can find by a simple google search.</p>
<p>→ No "Hello" and "Hi". Ask your question directly.</p>
<p>→ Prefer the common chat over DM. Seek help in public. So you will get help from more people and others will learn from your convo.</p>
<p>→ Learn to wait. People have their own life to live - value others' time.</p>
<p>→ Be kind to others.</p>
<p>Sound rude or not friendly? Trust me, follow the above points blindly. You will see the magic in your community. You will realize soon why it's important.</p>
<h3 id="heading-3-use-custom-roms-on-your-device">3) Use custom ROMs on your device</h3>
<p>You joined your device community, Now let's get into the core.</p>
<p><strong>Note: Make sure you know what you are doing.</strong></p>
<p>→ Get the basics of android custom ROM and its related terms like ADB, bootloader, recovery and so on.</p>
<p>→ Unlock the bootloader</p>
<p>→ Install a custom recovery</p>
<p>→ Flash a custom ROM via recovery</p>
<p>Properly follow the instructions mentioned in the ROM thread/post.</p>
<p>→ Get help from the community. Don't follow anything blindly or your device will become unusable (worst case).</p>
<p>→ Enjoy the perks of custom ROMs. You will love it for sure.</p>
<p>In this phase, Explore custom ROMs and become familiar with them.</p>
<p>There is a lot to mention. But I am sure you will learn all the things eventually from the community.</p>
<h3 id="heading-4-build-a-custom-rom-for-your-device">4) Build a custom ROM for your device</h3>
<p>You are a pro user now, let's do something more.</p>
<p>Let's compile an Android ROM for your device.</p>
<p>To build an Android ROM, you need a good specs PC.</p>
<p>Check the requirements: <a target="_blank" href="https://source.android.com/docs/setup/start/requirements">https://source.android.com/docs/setup/start/requirements</a></p>
<p>Most people (like me) can't have a machine that meets the requirements. I got you.</p>
<p><strong>What you can do about that?</strong></p>
<p>→ Try to get a free trial from services like Google Cloud Platform (GCP), Azure or AWS. Then create a virtual machine (VM)</p>
<p>→ Ask for help from your community friends. They may help you.</p>
<p>Now you have the machine to build.</p>
<p><strong>How to build?</strong></p>
<p>I ain't gonna write it about in detail since there are a lot of guides out there. (a thread for another day)</p>
<p>Lineage wiki (device-specific): <a target="_blank" href="https://wiki.lineageos.org/devices/onclite/build">https://wiki.lineageos.org/devices/onclite/build</a> Official doc: <a target="_blank" href="https://source.android.com/docs/setup/start">https://source.android.com/docs/setup/start</a></p>
<p>From the lineage wiki, up to this <a target="_blank" href="https://wiki.lineageos.org/devices/onclite/build#download-the-source-code">https://wiki.lineageos.org/devices/onclite/build#download-the-source-code</a> are common for all devices.</p>
<p>For the next step</p>
<p>→ You have to download/clone your device sources - Device config tree, kernel tree and vendor tree.</p>
<p>→ Lunch your device</p>
<p>→ Start the build</p>
<p>I recommend you get the details from your community. Learn from them. Get help from them. (no spoonfeeding)</p>
<p><strong>Build errors:</strong> Try to get help after trying something yourself. Don't go straight to the community without any try. (a thread for another day)</p>
<p>I hope, Now you know how to get started in Android custom ROM development.</p>
]]></content:encoded></item></channel></rss>