<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Amsavarthan Lv on Medium]]></title>
        <description><![CDATA[Stories by Amsavarthan Lv on Medium]]></description>
        <link>https://medium.com/@amsavarthan?source=rss-f500f15cb4bf------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*dO7lKFInyELWWe6XwJrX6w.jpeg</url>
            <title>Stories by Amsavarthan Lv on Medium</title>
            <link>https://medium.com/@amsavarthan?source=rss-f500f15cb4bf------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 07 Jul 2026 05:01:34 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@amsavarthan/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[flatMapConcat vs flatMapMerge vs flatMapLatest — Explained]]></title>
            <link>https://towardsdev.com/flatmapconcat-vs-flatmapmerge-vs-flatmaplatest-explained-31afd66ea693?source=rss-f500f15cb4bf------2</link>
            <guid isPermaLink="false">https://medium.com/p/31afd66ea693</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Amsavarthan Lv]]></dc:creator>
            <pubDate>Wed, 03 Jul 2024 19:05:34 GMT</pubDate>
            <atom:updated>2024-07-04T15:44:10.134Z</atom:updated>
            <content:encoded><![CDATA[<h3>flatMapConcat vs flatMapMerge vs flatMapLatest — Explained</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*CJncdqwVl8oYDboR" /><figcaption>Photo by <a href="https://unsplash.com/@sigmund?utm_source=medium&amp;utm_medium=referral">rivage</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Imagine you have a magical coloring book (the original flow) that can create new coloring pages (inner flows) whenever you want. You have three different ways to color these pages (operators):</p><h4>1. flatMapConcat: One Page at a Time</h4><p>This is like coloring one page at a time. Even if the magical book gives you a new page, you’ll finish coloring the current page before moving on to the next one. This way, you color all the pages in the exact order they were given to you.</p><p><a href="https://pl.kotl.in/x4S8Y-jc6">Kotlin Playground: Edit, Run, Share Kotlin Code Online</a></p><pre>import kotlinx.coroutines.flow.*<br>import kotlinx.coroutines.runBlocking<br>import kotlinx.coroutines.delay<br><br>fun main() = runBlocking {<br>    val originalFlow = flow {<br>        emit(1)<br>        delay(100)<br>        emit(2)<br>        delay(100)<br>        emit(3)<br>    }<br><br>    originalFlow.flatMapConcat { value -&gt;<br>        flow {<br>            emit(&quot;Page $value: Start coloring&quot;)<br>            delay(200)<br>            emit(&quot;Page $value: Finish coloring&quot;)<br>        }<br>    }.collect { result -&gt;<br>        println(result)<br>    }<br>}<br><br>/*****<br>Result<br><br>Page 1: Start coloring<br>Page 1: Finish coloring<br>Page 2: Start coloring<br>Page 2: Finish coloring<br>Page 3: Start coloring<br>Page 3: Finish coloring<br><br>*****/<br></pre><h4>2. flatMapLatest: Always Color the Newest Page</h4><p>This is like always wanting to color the newest page. If you’re coloring a page and the magical book gives you a new one, you’ll immediately leave the current page, even if it’s not finished, and start coloring the new page. This way, you only focus on the latest page given to you.</p><p><a href="https://pl.kotl.in/UHxwUyMke">Kotlin Playground: Edit, Run, Share Kotlin Code Online</a></p><pre>import kotlinx.coroutines.flow.*<br>import kotlinx.coroutines.runBlocking<br>import kotlinx.coroutines.delay<br><br>fun main() = runBlocking {<br>    val originalFlow = flow {<br>        emit(1)<br>        delay(100)<br>        emit(2)<br>        delay(100)<br>        emit(3)<br>    }<br><br>    originalFlow.flatMapLatest { value -&gt;<br>        flow {<br>            emit(&quot;Page $value: Start coloring&quot;)<br>            delay(200)<br>            emit(&quot;Page $value: Finish coloring&quot;)<br>        }<br>    }.collect { result -&gt;<br>        println(result)<br>    }<br>}<br><br>/*****<br>Result<br><br>Page 1: Start coloring<br>Page 2: Start coloring<br>Page 3: Start coloring<br>Page 3: Finish coloring<br><br>*****/</pre><h4>3. flatMapMerge: Coloring Multiple Pages Simultaneously</h4><p>This is like being able to color multiple pages at the same time. If the magical book gives you several pages, you can start coloring all of them at once, switching between pages as you wish. This way, you can work on many pages together, finishing them faster.</p><p><a href="https://pl.kotl.in/Fitfa2ffj">Kotlin Playground: Edit, Run, Share Kotlin Code Online</a></p><pre>import kotlinx.coroutines.flow.*<br>import kotlinx.coroutines.runBlocking<br>import kotlinx.coroutines.delay<br><br>fun main() = runBlocking {<br>    val originalFlow = flow {<br>        emit(1)<br>        delay(100)<br>        emit(2)<br>        delay(100)<br>        emit(3)<br>    }<br><br>    originalFlow.flatMapMerge(concurrency = 2) { value -&gt;<br>        flow {<br>            emit(&quot;Page $value: Start coloring&quot;)<br>            delay(200)<br>            emit(&quot;Page $value: Finish coloring&quot;)<br>        }<br>    }.collect { result -&gt;<br>        println(result)<br>    }<br>}<br><br>/*****<br>Result<br>​<br>Page 1: Start coloring<br>Page 2: Start coloring<br>Page 1: Finish coloring<br>Page 3: Start coloring<br>Page 2: Finish coloring<br>Page 3: Finish coloring<br><br>*****/</pre><p>Thanks for reading this article. You can learn more about me from <a href="https://amsavarthan.dev/">here</a></p><blockquote><em>If you found this article helpful, please recommend it by hitting the clap icon as many times you wish 👏 Let’s enable each other with the power of knowledge.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=31afd66ea693" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/flatmapconcat-vs-flatmapmerge-vs-flatmaplatest-explained-31afd66ea693">flatMapConcat vs flatMapMerge vs flatMapLatest — Explained</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Taking screenshot of a composable — Composable to Bitmap]]></title>
            <link>https://towardsdev.com/taking-screenshot-of-a-composable-composable-to-bitmap-4ca1db29bb51?source=rss-f500f15cb4bf------2</link>
            <guid isPermaLink="false">https://medium.com/p/4ca1db29bb51</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[android-development]]></category>
            <category><![CDATA[technology]]></category>
            <dc:creator><![CDATA[Amsavarthan Lv]]></dc:creator>
            <pubDate>Wed, 07 Feb 2024 09:21:43 GMT</pubDate>
            <atom:updated>2024-02-09T18:41:45.893Z</atom:updated>
            <content:encoded><![CDATA[<h3>Taking screenshot of a composable — Composable to Bitmap</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VwZTJ0kt1jNmsxaT" /><figcaption>Photo by <a href="https://unsplash.com/@tomspro17?utm_source=medium&amp;utm_medium=referral">Tom Spross</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In Android, everything that appears on the screen undergoes three phases - <strong><em>layout, measurement, and drawing</em></strong>. Ultimately, all views are rendered onto the screen using a <a href="https://developer.android.com/reference/android/graphics/Canvas"><strong><em>Canvas</em></strong></a>. We can utilize the Canvas to create our own custom views in Android. When it comes to images, Android treats them all as <a href="https://developer.android.com/reference/android/graphics/Bitmap"><strong><em>Bitmaps</em></strong></a>, essentially representations of each pixel on the screen. Think of a bitmap as a blank canvas where the artist creates their art. Fortunately, Android offers basic functionalities for drawing on the canvas, such as <em>drawLine</em>, <em>drawRect</em>, and <em>drawPath</em>. I won’t delve into the specifics of the canvas in this blog post as it’s beyond the scope.</p><p>A bitmap can be created with specified height and width, and we can use the canvas object to draw onto the bitmap. We can then convert it into a shareable image, storing it either in publicly accessible directories or our app’s cache.</p><pre>fun createBitmapFromView(View v): Bitmap {<br>    val b = Bitmap.createBitmap(v.getLayoutParams().width, v.getLayoutParams().height, Bitmap.Config.ARGB_8888);                <br>    val c = new Canvas(b);<br>    v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());<br>    v.draw(c);<br>    return b;<br>}<br><br>//Custom class [repo: amsavarthan/bmi-calculator]<br>private val cache = Cache(context)<br><br>fun saveToCacheAndGetUri(<br>    bitmap: Bitmap,<br>    fileNameWithExtension: String<br>): Uri {<br>    return cache.saveAndGetUri(bitmap, fileNameWithExtension)<br>}</pre><h4>The Compose Way</h4><p>While the <em>onSizeChanged</em> modifier in Jetpack Compose offers one approach to achieve similar functionality, a more efficient method is available using the <a href="https://developer.android.com/reference/kotlin/android/graphics/Picture"><strong><em>Picture</em></strong></a> class, which has been accessible since API Level 1. As outlined in the documentation</p><blockquote>A Picture records drawing calls and can then play them back into Canvas. For most content (e.g. text, lines, rectangles), drawing a sequence from a picture can be faster than the equivalent API calls…</blockquote><pre>class CaptureIntoPicture(<br>    private val picture: Picture,<br>    private val drawContent: Boolean = true,<br>) : DrawCacheModifier {<br><br>    private var width: Int = 0<br>    private var height: Int = 0<br><br>    override fun onBuildCache(params: BuildDrawCacheParams) {<br>        width = params.size.width.toInt()<br>        height = params.size.height.toInt()<br>    }<br><br>    override fun ContentDrawScope.draw() {<br>        val pictureCanvas = androidx.compose.ui.graphics.Canvas(<br>            picture.beginRecording(width, height)<br>        )<br><br>        draw(this, layoutDirection, pictureCanvas, size) internalDraw@{<br>            this@draw.drawContent()<br>        }<br><br>        picture.endRecording()<br><br>        if (!drawContent) return<br>        drawIntoCanvas { canvas -&gt;<br>            canvas.nativeCanvas.drawPicture(picture)<br>        }<br>    }<br>}<br><br>fun Modifier.captureIntoPicture(picture: Picture, drawContent: Boolean = true): Modifier {<br>    return this.then(CaptureIntoPicture(picture, drawContent))<br>}</pre><p>By simply adding the modifier to the parent of the composable you wish to capture, you can record all draw commands replayed into the bitmap’s canvas, thus obtaining the desired image.</p><p>Additionally, you have the option to create a custom composable that won’t be rendered on the screen but will solely serve the purpose of sharing. By setting the drawContent parameter of the modifier to false, you gain the flexibility to distinguish and apply various styles, while also enabling support for previews. This approach enhances customization possibilities and facilitates better management of sharing functionalities within your app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1010/1*357rdjXPvJ7dr3T7he7p1A.png" /><figcaption>Compose preview of Shareable content.</figcaption></figure><p>Sample: <a href="https://github.com/amsavarthan/bmi-calculator">https://github.com/amsavarthan/bmi-calculator</a></p><p>In the repository, you’ll find the complete implementation of the custom composable and its integration for sharing purposes, providing a comprehensive example for your reference.</p><p>That concludes this article for now. Stay tuned for more intriguing topics in the next blog. Until then, Your friendly neighbourhood Android Developer 👨🏻‍💻💚</p><p>Thanks for reading this article. You can learn more about me from <a href="https://amsavarthan.dev/">here</a></p><blockquote><strong>As of</strong> <strong>February 7, 2023 - </strong>There exists a bug where the drawText function fails to be recorded within the Picture, resulting in the absence of text in the resulting bitmap. <a href="https://issuetracker.google.com/issues/323403942">https://issuetracker.google.com/issues/323403942</a></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4ca1db29bb51" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/taking-screenshot-of-a-composable-composable-to-bitmap-4ca1db29bb51">Taking screenshot of a composable — Composable to Bitmap</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stealing API keys — Security in Android]]></title>
            <link>https://towardsdev.com/stealing-api-keys-security-in-android-495dd5285892?source=rss-f500f15cb4bf------2</link>
            <guid isPermaLink="false">https://medium.com/p/495dd5285892</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[reverse-engineering]]></category>
            <dc:creator><![CDATA[Amsavarthan Lv]]></dc:creator>
            <pubDate>Fri, 17 Nov 2023 12:32:23 GMT</pubDate>
            <atom:updated>2024-02-09T18:42:51.347Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_VmDAZFwZtibs7tT" /><figcaption>Photo by <a href="https://unsplash.com/@arget?utm_source=medium&amp;utm_medium=referral">Arget</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Stealing API keys — Security in Android</h3><blockquote><strong>Disclaimer:<br></strong>For educational purposes only, I request that you refrain from imitation, and I explicitly state that I will not assume responsibility for any potential issues arising from such actions.</blockquote><p>In this blog post, I’ll demonstrate the steps I took to gain access to a vulnerable application and obtain control of its API. To follow along, make sure you have the following tools installed on your system:</p><h4><strong>Prerequisite:</strong></h4><ol><li>apktool</li><li>jadx-gui</li><li>Charles</li><li>Android Studio (emulator, sdk command line tools)</li></ol><h4>Backstory:</h4><p>I have always been curious about how Instagram post downloaders functioned, especially in the early days when appending “<strong>?__a=1</strong>” to an Instagram URL would yield the complete JSON response, allowing access to the media URLs. However, nowadays, this endpoint simply redirects to the login page. Recently, I discovered that adding “<strong>?__a=1&amp;__d=bis</strong>” still provides the desired response, but it is restricted to 200 calls per hour and subsequently requires authentication.</p><p>While exploring on GitHub, I came across a Python library called <a href="https://github.com/instaloader/instaloader"><strong><em>instaloader</em></strong></a>, and upon inspecting its code, I realized it also followed the same procedure mentioned above. Initially, I gave up on my quest, but then I stumbled upon certain web applications and Android apps that could achieve the same goal without requiring login credentials. Intrigued, I sought to understand how they operated.</p><p>Analysing network calls proved to be a straightforward task by opening the console in the web browser. However, this approach did not yield a solution due to the use of CORS policies and proxies for internal API calls. As an Android developer, I wished for a tool similar to the network monitor in the web console to scrutinise network requests. This led me to discover Charles Proxy, which allows monitoring of all network calls, even for third-party applications.</p><p>Despite multiple attempts, I failed to uncover the inner workings of these tools but unexpectedly embarked on a new learning path in Android development. Join me on this journey, and I will elaborate on the steps in detail.</p><h3>Overview of the steps:</h3><h4>1. Configuring Charles proxy</h4><p>Download the tool from the<a href="https://www.charlesproxy.com/"> official website</a>. The installation process is straightforward and doesn’t demand extensive knowledge. After installation, launch the application. To stop recording system requests, deactivate your computer’s proxy. On macOS, you can achieve this by navigating to <strong>Proxy -&gt; macOS Proxy (disable it)</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/796/1*zj057DeV3alj8A1-1f7W6A.png" /><figcaption>Uncheck the macOS Proxy</figcaption></figure><h4>2. Connecting to proxy</h4><p>If you don’t have Android studio installed <a href="https://developer.android.com/studio">get the latest version from here</a>. Follow the installation steps and configure a new emulator with any android version. You can <a href="https://developer.android.com/studio/run/emulator#avd">find the documentation here</a>.</p><p>Obtaining our APK file is simple; just install it on your phone. Navigate to the <strong>Apps section in</strong> <strong>Google Files</strong>, and <strong>share it</strong> via email or messaging apps. Afterward, you can <strong>transfer it to your system</strong>. Drag and drop the apk into the emulator to get it installed.</p><p>Go to your Wi-Fi settings in your computer and find your public IP address. Making note of it open the network settings in the emulator and modify the network <strong>AndroidWifi </strong>by clicking the edit icon at the top.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/422/1*sGSua10qXvxtn_b78nyViw.png" /><figcaption>Configuring the proxy of AndroidWifi</figcaption></figure><p>To confirm whether you’ve done it correctly, you can validate it through the connection request from the Charles Proxy.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/891/0*05JIt2UTjOlrmzpd.png" /></figure><p>If everything is working you can now see requests being made from the emulator showing up. Try using chrome and navigate to any website for confirmation.</p><h4>3. Enabling SSL proxying</h4><p>Let’s open the victim application and make a request. You could capture the request but when expanding into it all the data would be encrypted. You will need to decrypt before looking into the content of the request and response.</p><p><strong>a. Decompiling the apk</strong></p><p>Once you have apktool installed you can use the command to decompile and obtain all of its resources which we will be modifying.</p><pre>apktool d &lt;app_name&gt;.apk</pre><p><strong>b. Modifying the network configuration</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/867/1*PWmhX3q5QaTIrynPi786cg.png" /><figcaption>Export the charles root certificate</figcaption></figure><p>Save the root certification into the decompiled apk directory <strong>app_name -&gt; res -&gt; raw</strong>. Rename the file as <strong><em>charles.pem</em></strong> for convenience. Create another file named <strong>network_config.xml</strong> inside <strong>res -&gt; xml </strong>folder, If it does not exists.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dda71c96ec1424eac379ef788fa1b6f5/href">https://medium.com/media/dda71c96ec1424eac379ef788fa1b6f5/href</a></iframe><p>Modify the AndroidManifest.xml as following by adding the network configuration</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/327021da246db72834bc76df8e1c6745/href">https://medium.com/media/327021da246db72834bc76df8e1c6745/href</a></iframe><p><strong>c. Building the apk</strong></p><p>Once the above configuration is done we can build the apk using the source by executing the following commands.</p><pre>apktool b &lt;app_name&gt;</pre><p>You can find the generated apk from <strong>&lt;app_name&gt; -&gt; dist -&gt; &lt;app_name&gt;.apk</strong>. When you try to install the apk you might get into the following error</p><blockquote>Error code: ‘INSTALL_PARSE_FAILED_NO_CERTIFICATES’, message=’INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl1372524174.tmp/app.apk: Attempt to get length of null array’</blockquote><p><strong>Solution:</strong></p><p>You need to execute the following command which aligns and signs the apk using the default debug keystore.</p><pre>zipalign 4 &lt;build_apk&gt; aligned.apk</pre><pre>apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android aligned.apk</pre><p>Once installed you can monitor the requests made from the application once again but now you can enable the SSL proxy to decrypt the requests of the application. To do so you just right click on the request and click <strong>Enable SSL Proxy</strong>.</p><p><strong>4. Stealing the API Secret</strong></p><p>Using the jadx cli tool we can look into the compiled code by executing the following command.</p><pre>jadx-gui &lt;app_name&gt;.apk</pre><p>Since we now know the api endpoint by monitoring using Charles, We can make a global search to find it. And if you are lucky you could find the secret key hiding over there.</p><p>On searching such way I found the application’s auth key (which was constant 🥲). Performing the same for another application I found that they were using JWT keys for authorization but the secret used to generate JWT was easily available.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yEoKnqxPn9vbMuleN5jQxw.png" /></figure><blockquote><strong>Certain sensitive data has been obfuscated in the above image until the respective owner has applied the resolution I provided to them to prevent such issue.</strong></blockquote><p>In the end, it was a mutually beneficial situation. I discovered how they could extract media links from public Instagram URLs, and they became aware of a security flaw, which will be the focus of my next blog. Until then, Your friendly neighbourhood Android Developer 👨🏻‍💻💚</p><p>Thanks for reading this article. You can learn more about me from <a href="https://amsavarthan.dev/">here</a></p><blockquote><em>If you found this article helpful, please recommend it by hitting the clap icon as many times you wish 👏 Let’s enable each other with the power of knowledge.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=495dd5285892" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/stealing-api-keys-security-in-android-495dd5285892">Stealing API keys — Security in Android</a> was originally published in <a href="https://towardsdev.com">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Choosing the Right Abstraction: Sealed Classes, Enum Classes, or Sealed Interfaces?]]></title>
            <link>https://medium.com/@amsavarthan/sealed-classes-vs-enum-classes-vs-sealed-interfaces-when-to-use-which-98c11f103c9d?source=rss-f500f15cb4bf------2</link>
            <guid isPermaLink="false">https://medium.com/p/98c11f103c9d</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Amsavarthan Lv]]></dc:creator>
            <pubDate>Fri, 09 Jun 2023 12:13:53 GMT</pubDate>
            <atom:updated>2024-02-09T18:43:39.353Z</atom:updated>
            <content:encoded><![CDATA[<p>Choosing the Right Abstraction: Sealed Classes, Enum Classes, or Sealed Interfaces?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wyiZQBU-aba9r_Z0" /><figcaption>Photo by <a href="https://unsplash.com/@neonbrand?utm_source=medium&amp;utm_medium=referral">Kenny Eliason</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Many developers frequently ask about the usage of sealed classes and enums and when to use them. To address this question, it is important to understand the concept of sealed classes.</p><p>Sealed classes are a specialized form of abstract classes that allow the definition of abstract functions. They enable other classes to inherit from them and create their own class definitions, but with the constraint that these derived classes must remain within the scope of the sealed class. This characteristic makes sealed classes especially useful during the compilation process. Here is an example of sealed classes</p><pre>sealed class Month(val shortName: String, val numberOfDays: Int) {<br>    data object January : Month(&quot;Jan&quot;, 31)<br>    data object February : Month(&quot;Feb&quot;, 28)<br>    data object March : Month(&quot;Feb&quot;, 31)<br>    //Other months<br>}</pre><p>On the other hand, enums are used to define constants by providing names for different months. Here is an example of an enum class:</p><pre>enum class MonthEnum(val shortName: String, val numberOfDays: Int) {<br>    January(&quot;Jan&quot;, 31),<br>    February(&quot;Feb&quot;, 28),<br>    March(&quot;Mar&quot;, 31)<br>    //Other months<br>}</pre><p>Both sealed classes and enums can be accessed using “when” statements, and suggestions are provided for each case, such as the quick action to add remaining branches.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CJBC7zhnYUI35lySVKTppw.png" /><figcaption>Quick action to add remaining branches</figcaption></figure><blockquote>What is the difference if everything is the same?</blockquote><p>When using sealed classes, you have more flexibility compared to enums. In addition to creating objects, you can also create data classes inside them, allowing you to provide class-specific parameters, which can be very useful. Here’s an example (inspired from this <a href="https://www.youtube.com/watch?v=kLJRZpRhX1o">video</a>):</p><pre>sealed class HttpError(val code: Int) {<br>    data class Unauthorized(val reason: String) : HttpError(401)<br>    object NotFound : HttpError(404)<br>}</pre><p>However, this is not possible with enum classes. Therefore, sealed classes are preferred in such cases.</p><p>Additionally, one notable distinction between enums and sealed classes is in accessing their values. Enums inherently offer direct access to all their values as a list, simplifying iteration. This convenience arises from their nature as constants. Conversely, sealed classes require the explicit definition of a function to obtain the list of declared classes. This function retrieves the classes within the sealed class hierarchy.</p><pre>//This provides an array of all the values in the enum<br>MonthEnum.values()</pre><blockquote>What about sealed interface?</blockquote><p>According to the official Kotlin documentation, sealed interfaces are not significantly more complex to understand. The only difference is that classes implementing the interface must be in the same file. The decision of when to use them is straightforward. Sealed interfaces are suitable when the parent classes within the sealed class do not have any constructor parameters. Here’s a sample use case commonly employed in many applications:</p><pre>sealed interface Result&lt;out T&gt; {<br><br>    data class Success&lt;T&gt;(val data: T) : Result&lt;T&gt;<br><br>    data class Error(val exception: Throwable) : Result&lt;Nothing&gt;<br><br>}</pre><p>Based on the different cases, here are the suggested preferences for usage. When easy iteration over values is desired and all cases share a common parameter, it is recommended to use enum classes. On the other hand, for scenarios where flexibility is required, sealed classes or interfaces can be chosen based on the specific use case.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=98c11f103c9d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google’s Apps Script — The Orchestrator]]></title>
            <link>https://medium.com/@amsavarthan/googles-apps-script-the-orchestrator-5f12cdb8bd2e?source=rss-f500f15cb4bf------2</link>
            <guid isPermaLink="false">https://medium.com/p/5f12cdb8bd2e</guid>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[google-apps-script]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[useful]]></category>
            <category><![CDATA[tips]]></category>
            <dc:creator><![CDATA[Amsavarthan Lv]]></dc:creator>
            <pubDate>Mon, 29 May 2023 09:56:29 GMT</pubDate>
            <atom:updated>2023-05-29T09:56:29.128Z</atom:updated>
            <content:encoded><![CDATA[<h3>Google’s Apps Script — The Orchestrator</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sjaMbU0My7H4kz3I" /><figcaption>Photo by <a href="https://unsplash.com/@gwundrig?utm_source=medium&amp;utm_medium=referral">Manuel Nägeli</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Recently, I launched a campaign called “<strong>WebWonders</strong>” where I offered website building services <strong>starting from just ₹599</strong>. However, as an individual running this campaign, I encountered some challenges in effectively managing clients and providing personalized pricing for their specific requirements. I had set clear and impactful goals for my management process:</p><ol><li>Upon submission of the form, I aimed to curate a personalized price based on the responses, particularly for questions that required a yes/no answer.</li><li>The curated price would then be sent directly to the user’s email, ensuring a seamless and efficient communication flow.</li></ol><p>Despite my initial lack of knowledge on how to achieve those goals, I decided to use <strong>Google Forms </strong>due to its seamless integration with <strong>Google Sheets</strong> for gathering client requirements. However, during the process of creating the form, I stumbled upon something intriguing</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/342/1*UFLY4YsAAm4CWstQEECufg.png" /><figcaption>Script Editor</figcaption></figure><p>Yes, I understand. Even though the “<strong>Script Editor</strong>” feature has been around for a while, it can still be quite intriguing when you first come across it. When I encountered it for the first time, with all these ideas running through my mind, I couldn’t help but ponder the various possibilities of how I could leverage its potential.</p><p>However, upon clicking it, I realized that the interface was unfamiliar to me, and I wasn’t sure what steps to take next. But after going through the <a href="https://developers.google.com/apps-script/reference">documentation</a> and exploring resources available online, I was able to gain a better understanding of how to utilize the “Script Editor” effectively. It provided me with a powerful tool to extend the functionality of Google Forms and achieve my goals.</p><h4>Curating a personalized price for an Individual</h4><pre> function onFormSubmit() {<br>  var sheet = SpreadsheetApp.openById(&quot;sheet-id&quot;).getSheetByName(&quot;sheet-name&quot;);<br>  if (sheet!=null) { <br>    const orderNumber = insertOrderNumber(sheet)<br>    Logger.log(&quot;Processing order &quot;+ orderNumber)<br><br>    const lastRow = sheet.getLastRow();<br>    const lastColumn = sheet.getLastColumn()<br>    const lastEntry = sheet.getRange(lastRow, 1, 1, lastColumn).getValues()[0];<br>    const price = calculatePrice(lastEntry)<br>    Logger.log(&quot;Calculated price is &quot;+ price)<br>  }<br>}<br><br>function insertOrderNumber(sheet){<br>  const orderNumber = sheet.getLastRow()-1; <br>  const lastCell = sheet.getRange(`A${orderNumber+1}`);<br>  lastCell.setValue(orderNumber)<br>  return orderNumber<br>}<br><br>function calculatePrice(entry){<br>  const isCodeHandoverRequired = entry[12] === &quot;Yes&quot;<br>  const isQuickDeliveryRequired = entry[13]<br><br>  //Calculate the cost<br>  let baseFare = 599<br>  if(isQuickDeliveryRequired) baseFare+=499<br>  if(isCodeHandoverRequired) baseFare+=999<br><br>  return baseFare<br>}</pre><p>This function, onFormSubmit, is triggered when a form is submitted. It assumes there is a Google Spreadsheet with a specific ID (&quot;sheet-id&quot;) and a sheet name (&quot;sheet-name&quot;). It performs the following steps:</p><ol><li>Open the spreadsheet</li><li>Insert the order number</li><li>Get the entries from the sheet and calculate the price accordingly</li></ol><h4>Sending the email</h4><pre>function sendEmail(name, email, orderNumber, price){<br><br>    if(MailApp.getRemainingDailyQuota()===0){<br>      Logger.log(&quot;Mail quota exhausted&quot;)<br>      return false<br>    }<br><br>    Logger.log(&quot;Sending mail...&quot;)<br><br>    var template = HtmlService.createTemplateFromFile(&quot;MailTemplate&quot;)<br>    template.orderNumber = orderNumber<br>    template.name = name<br>    template.price = price<br>    var message = template.evaluate().getContent()<br><br>    //Send from my alias mail<br>    var alias = GmailApp.getAliases()<br>    GmailApp.sendEmail(email ,`Webwonders Order #${orderNumber}`,message,{from:alias[0],name:&quot;Amsavarthan LV&quot;,htmlBody:message, noReply:true})<br><br>    //Send mail without alias<br>    //GmailApp.sendEmail(email ,`Webwonders Order #${orderNumber}`,message)<br><br>    Logger.log(`Remaining quota : ${MailApp.getRemainingDailyQuota()}`)<br>    Logger.log(`Mail sent to ${name} for order id ${orderNumber}`)<br>    return true<br>}</pre><pre>&lt;!DOCTYPE html&gt;<br>&lt;html&gt;<br>  &lt;head&gt;<br>    &lt;base target=&quot;_top&quot;&gt;<br>    &lt;title&gt;WebWonders Order #&lt;?= orderNumber ?&gt;&lt;/title&gt;<br>  &lt;/head&gt;<br>  &lt;body&gt;<br>    &lt;p&gt;Dear &lt;?= name ?&gt;,&lt;/p&gt;<br>    &lt;p&gt;I am pleased to inform you that your response has been received.&lt;/p&gt;<br>    &lt;p&gt;Your personalized website service costs &lt;b&gt;₹&lt;?= cost?&gt;&lt;/b&gt;.&lt;/p&gt;<br>    &lt;p&gt;Best Regards,&lt;br/&gt;Your friendly neighbourhood developer&lt;/p&gt;<br>    &lt;p&gt;Amsavarthan LV&lt;/p&gt;<br>  &lt;/body&gt;<br>&lt;/html&gt;</pre><p>This function checks the remaining email quota, creates an HTML template, populates it with order information, and sends the email using GmailApp. The function logs the remaining quota and the successful email sending.</p><p>By leveraging Google Forms and the “Script Editor,” I overcame initial challenges, improved client management, and provided personalized pricing. The “WebWonders” campaign benefitted from these streamlined processes, enhancing its overall success.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5f12cdb8bd2e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Coroutine 101: Crafted for Android Developers ]]></title>
            <link>https://medium.com/@amsavarthan/coroutine-101-crafted-for-android-developers-474913c0d151?source=rss-f500f15cb4bf------2</link>
            <guid isPermaLink="false">https://medium.com/p/474913c0d151</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin-coroutines]]></category>
            <category><![CDATA[guides-and-tutorials]]></category>
            <category><![CDATA[best-practices]]></category>
            <dc:creator><![CDATA[Amsavarthan Lv]]></dc:creator>
            <pubDate>Thu, 30 Mar 2023 11:04:50 GMT</pubDate>
            <atom:updated>2023-03-30T11:04:50.163Z</atom:updated>
            <content:encoded><![CDATA[<p>A coroutine is a type of computation that can be paused and resumed at specific points in its execution. It can be thought of as similar to a thread, as it allows for concurrent execution of code, but unlike a thread, it is not tied to a particular thread and can suspend its execution in one thread and continue in another.</p><p>In this blog we will be discussing on the following topics:</p><ul><li>Suspend modifier</li><li>Coroutine scopes</li><li>Coroutine context</li><li>Coroutine builders</li></ul><p>Whether you are looking to review or update your existing knowledge, or learn something completely new, this guide will be a valuable resource for you.</p><h3>The Suspend Modifier</h3><p>a suspend function is a function that can be paused and resumed at specific points during its execution, without blocking the thread it is running on. To create a suspend function, you simply need to add the suspend modifier before the function signature.</p><p>For example, let’s say we have a function that performs a time-consuming task, such as fetching data from a remote server:</p><pre>fun fetchDataFromServer() {<br>    // Perform time-consuming task here<br>}</pre><p>If we want to make this function suspendable, we simply add the suspend modifier like this:</p><pre>suspend fun fetchDataFromServer() {<br>    // Perform time-consuming task here<br>}</pre><h4>What does the suspend modifier tells us?</h4><p>When we use suspending functions we don’t block the underlying thread. Each suspendable function is responsible for specific computation which may takes some time to be completed so we allow to pause its execution for some time to release the thread for other tasks and when the computation it’s completed we resume it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/1*_YqtHUC9Y84I46ygsFHfnw.gif" /></figure><p><strong>Points to remember:</strong></p><ul><li>Suspend functions can be invoked only inside another suspend function or a coroutine builders</li><li>A suspend function will <strong>only return once its execution is complete</strong>, meaning that any code following a call to a suspend function will not be executed until the function has finished running.</li></ul><p><strong>Best practices in Android:</strong></p><ul><li>Do not expose a suspend function to UI (Activities/Fragments) from ViewModels.</li></ul><h3>Coroutine Scopes</h3><p>When working with coroutines, it’s important to keep track of their lifecycle and ensure that they are properly managed.</p><p>This is where coroutine scopes come in. A coroutine scope is a way to manage the lifecycle of a set of coroutines. It provides a structured concurrency model that allows you to launch and cancel coroutines as a group.</p><p>In Android we have the following scopes available to use</p><ul><li>GlobalScope — This is a scope that is used to launch top-level coroutines which are operating on the <strong>whole application lifetime and are not cancelled prematurely</strong>.</li><li>LifecycleScope — This is a scope that is tied to the <strong>lifecycle of a specific component</strong>, such as an activity or fragment. It provides a way to launch coroutines that are <strong>automatically cancelled when the component is destroyed</strong>.</li><li>ViewModelScope — This is a scope that is tied to the <strong>lifecycle of a ViewModel</strong>. It is designed to be used in ViewModel classes, and provides a way to launch coroutines that need to run for the duration of the ViewModel’s lifecycle.</li></ul><p><strong>Points to remember:</strong></p><ul><li>When ever you create a coroutine it gets attached to a special instance of coroutine scope. Regardless of being a parent or a child coroutine each one has its own unique coroutine scope.</li></ul><pre>runBlocking { //this refers to an instance of Coroutine Scope<br>  println(&quot;$this&quot;)<br>  launch { //this refers to another instance of Coroutine Scope<br>    println(&quot;$this&quot;)<br>  }<br>  <br>  async { //this refers to another instance of Coroutine Scope <br>    println(&quot;$this&quot;)<br>  }<br>}<br><br>// Output:<br>//   BlockingCoroutine{Active}@1f89ab83<br>//   StandaloneCoroutine{Active}@64cee07<br>//   DeferredCoroutine{Active}@6f79caec</pre><p><strong>Best practices in Android:</strong></p><ul><li><strong>Do not make use of GlobalScope</strong> unless required. It is easy to accidentally create resource or memory leaks when GlobalScope is used.</li></ul><h3>Coroutine Context</h3><p>Think of it as a set of rules that tell the coroutine where and how to run. One of the most important rules is the dispatcher, which determines the thread that the coroutine runs on.</p><p>By default, there are several dispatchers like Dispatchers.Default, Dispatchers.IO, and Dispatchers.Main, which can be used depending on the requirements of the application.</p><p>In addition to the dispatcher, other rules can be added to the CoroutineContext, such as the job that the coroutine is doing and how to handle any exceptions that might occur.</p><h4>When to use specific dispatchers?</h4><p>Here are some guidelines to help you decide which dispatcher to use:</p><ol><li>Dispatchers.Default : This is the dispatcher that is used by default if you don’t specify any other dispatcher. It is optimized for CPU-intensive work and is best suited for computations, sorting, and other similar tasks.</li><li>Dispatchers.IO : This dispatcher is optimized for IO-bound tasks such as network requests and disk operations. It is best used when you are doing anything that involves waiting for a result from an external system.</li><li>Dispatchers.Main : This is the dispatcher that is used for UI-related work. It ensures that the coroutine runs on the main thread of the application, which is necessary for updating the UI and handling user input.</li></ol><p><strong>Best practices in Android:</strong></p><ul><li><strong>IO dispatcher</strong> for any network requests or disk operations</li><li><strong>Default dispatcher</strong> for any CPU-intensive work</li><li><strong>Main dispatcher </strong>for any UI-related work.</li></ul><h3>Coroutine Builders</h3><p>To invoke a suspend function, it must be called from another suspend function. This creates a chain of suspending functions that must be started with a coroutine builder. The coroutine builder acts as a bridge, allowing for the execution of suspending functions within a coroutine, and connecting the normal and suspending worlds of code.</p><p>There are several coroutine builders available each with its own unique features and use cases. Here are some of the most commonly used coroutine builders:</p><ol><li>launch: This coroutine builder launches a new coroutine and returns a Job object that can be used to control the coroutine&#39;s lifecycle.</li><li>async: This coroutine builder launches a new coroutine and returns a Deferred object that represents a value that may not be available yet. The Deferred object can be used to retrieve the value once it becomes available, and can also be used to control the coroutine&#39;s lifecycle.</li><li>withContext: This coroutine builder switches the coroutine&#39;s context to a different thread or thread pool. It can be used to perform operations that may block the current thread, such as file I/O or network requests.</li><li>runBlocking: This coroutine builder blocks the current thread until the coroutine it launches is completed.</li></ol><h4>Differences between launch and async</h4><ul><li>launch is used for running a coroutine <strong>without returning any result</strong>, while async is used for running a coroutine that returns a result.</li><li>launch is<strong> non-blocking</strong> and doesn&#39;t wait for the coroutine to finish, while async is <strong>blocking</strong> and waits for the coroutine to finish before moving on.</li><li>async provides an easy way to handle exceptions thrown by a coroutine, while launch does not.</li></ul><p><strong>Points to remember:</strong></p><ul><li>In launch, any exception thrown by a coroutine is thrown immediately, whereas with async, the exception is not thrown until await is called.</li></ul><p><strong>Best practices in Android:</strong></p><ul><li>runBlocking — Should be used only for unit tests and not in production.</li><li>If you have multiple requests that are independent of each other, you can run them concurrently using async to improve the performance.</li></ul><p>Here’s an example that includes all the concepts we’ve discussed so far:</p><pre>class MainActivity : AppCompatActivity() {<br><br>    override fun onCreate(savedInstanceState: Bundle?) {<br>        super.onCreate(savedInstanceState)<br><br>        // Start a new coroutine with IO thread<br>        lifecycleScope.launch(Dispatchers.IO) {<br>            // Perform a time-consuming operation<br>            val result = performTimeConsumingOperation()<br>    <br>            // Switch back to the UI thread<br>            withContext(Dispatchers.Main) {<br>                // Update the UI with the result<br>                updateUI(result)<br>            }<br>        }<br>    }<br>}<br><br>suspend fun performTimeConsumingOperation(): String {<br>    // Perform a time-consuming operation<br>    delay(5000)<br><br>    // Return the result<br>    return &quot;Operation completed&quot;<br>}<br><br>fun updateUI(result: String) {<br>    // Update the UI with the result<br>}</pre><p>In this example, we start a new coroutine using the launch coroutine builder, and then calls the performTimeConsumingOperation() function. This function is marked as suspendable, so it can be called from within a coroutine. It uses the delay function to simulate a time-consuming operation, and then returns a result.</p><p>Once the result is available, the coroutine switches back to the main thread using the withContext coroutine builder, and calls the updateUI() function to update the UI with the result.</p><h3>Additional Resources</h3><ul><li><a href="https://kotlinlang.org/docs/coroutines-overview.html">https://kotlinlang.org/docs/coroutines-overview.html</a></li><li><a href="https://developer.android.com/kotlin/coroutines">https://developer.android.com/kotlin/coroutines</a></li><li><a href="https://developer.android.com/kotlin/coroutines/coroutines-best-practices">https://developer.android.com/kotlin/coroutines/coroutines-best-practices</a></li><li><a href="https://developer.android.com/kotlin/coroutines/additional-resources">https://developer.android.com/kotlin/coroutines/additional-resources</a></li></ul><p>Thanks for reading. You can connect with me on <a href="https://www.linkedin.com/in/lvamsavarthan/">LinkedIn</a>, <a href="https://twitter.com/lvamsavarthan">Twitter</a>, and <a href="https://www.instagram.com/lvamsavarthan/">Instagram</a>.</p><blockquote><em>If you found this guide helpful, please recommend it by hitting the clap icon as many times you wish 👏</em></blockquote><blockquote><em>Let’s enable each other with the power of knowledge.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=474913c0d151" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Win Friends and Influence Code]]></title>
            <link>https://medium.com/@amsavarthan/how-to-win-friends-and-influence-code-with-solid-principles-in-kotlin-701382845dfe?source=rss-f500f15cb4bf------2</link>
            <guid isPermaLink="false">https://medium.com/p/701382845dfe</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[clean-code]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[kotlin]]></category>
            <dc:creator><![CDATA[Amsavarthan Lv]]></dc:creator>
            <pubDate>Fri, 24 Feb 2023 09:47:26 GMT</pubDate>
            <atom:updated>2024-02-09T18:39:11.880Z</atom:updated>
            <content:encoded><![CDATA[<h3>How to Win Friends and Influence Code with SOLID Principles in Kotlin</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-sy3yUCkCHw9uOZm" /><figcaption>Photo by <a href="https://unsplash.com/@hanness?utm_source=medium&amp;utm_medium=referral">Jo Jo</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Have you ever inherited a codebase that made you want to pull your hair out? Or have you ever written code that you knew was going to be a nightmare to maintain? As developers, we’ve all been there.</p><p>One of the keys to writing maintainable, flexible, and reusable code is to follow the <strong>SOLID</strong> principles. In this blog post, we’ll explore how <strong>SOLID</strong> principles can help you write better code in Kotlin, and we’ll walk through some code examples to show you how to apply these principles in practice.</p><p>By the end of this article, you’ll have a solid understanding of the <strong>SOLID</strong> principles and how they can help you become a better developer. So let’s get started!</p><h4>S — Single Responsibility Principle</h4><p>The <strong>Single Responsibility Principle (SRP)</strong> states that each class or module should have only one responsibility. This means that a class or module should do one thing and do it well. If a class or module has more than one responsibility, it becomes harder to maintain and change over time.</p><p>Here’s an example of a class that violates the SRP:</p><pre>class UserService {<br>    fun registerUser(username: String, password: String) {<br>        // validation logic<br>        // database insert logic<br>        // email sending logic<br>    }<br>}</pre><p>This class has three responsibilities — validating user input, inserting the user into the database, and sending an email. This violates the SRP because if any one of these responsibilities needs to be changed, the entire class needs to be modified.</p><p>Here’s an example of how to refactor this class to follow the SRP:</p><pre>class UserValidator {<br>    fun validate(username: String, password: String): Boolean {<br>        // validation logic<br>    }<br>}<br><br>class UserRepository {<br>    fun saveUser(username: String, password: String) {<br>        // database insert logic<br>    }<br>}<br><br>class EmailService {<br>    fun sendEmail(to: String, subject: String, body: String) {<br>        // email sending logic<br>    }<br>}<br><br>class UserService(<br>    private val userValidator: UserValidator,<br>    private val userRepository: UserRepository,<br>    private val emailService: EmailService<br>) {<br>    fun registerUser(username: String, password: String) {<br>        if (!userValidator.validate(username, password)) {<br>            // handle validation error<br>            return<br>        }<br><br>        userRepository.saveUser(username, password)<br><br>        emailService.sendEmail(username, &quot;Welcome to our app&quot;, &quot;Thanks for registering!&quot;)<br>    }<br>}</pre><p>In this refactored code, each class has only one responsibility — validating user input, inserting the user into the database, and sending an email. The UserService class now depends on these three classes instead of having all the responsibilities itself.</p><h4>O — Open-Closed Principle</h4><p>The Open-Closed Principle (OCP) states that classes or modules should be open for extension, but closed for modification. This means that you should be able to add new functionality to a class or module without changing its existing code.</p><p>Here’s an example of a class that violates the OCP:</p><pre>class PaymentService {<br>    fun processPayment(paymentMethod: String, amount: Double) {<br>        when (paymentMethod) {<br>            &quot;creditCard&quot; -&gt; {<br>                // credit card processing logic<br>            }<br>            &quot;paypal&quot; -&gt; {<br>                // PayPal processing logic<br>            }<br>        }<br>    }<br>}</pre><p>This class violates the OCP because if a new payment method needs to be added, the processPayment method needs to be modified. Instead, we can use the Strategy pattern to make this class extensible:</p><pre>interface PaymentMethod {<br>    fun processPayment(amount: Double)<br>}<br><br>class CreditCardPayment : PaymentMethod {<br>    override fun processPayment(amount: Double) {<br>        // credit card processing logic<br>    }<br>}<br><br>class PaypalPayment : PaymentMethod {<br>    override fun processPayment(amount: Double) {<br>        // PayPal processing logic<br>}<br><br>class PaymentService(<br>  private val paymentMethod: PaymentMethod<br>) {<br><br>    fun processPayment(amount: Double) {<br>        paymentMethod.processPayment(amount)<br>     }<br><br>}</pre><p>In this refactored code, we have created an interface called PaymentMethod that defines a processPayment method. We have then created two classes, CreditCardPayment and PaypalPayment that implement the PaymentMethod interface. Finally, we’ve refactored the PaymentService class to take an instance of PaymentMethod in its constructor, so we can pass in any implementation of PaymentMethod at runtime.</p><h4>L — Liskov Substitution Principle</h4><p>The Liskov Substitution Principle (LSP) states that objects of a superclass should be able to be replaced with objects of a subclass without affecting the correctness of the program. This means that subclasses should be able to be used in place of their parent classes without breaking the code.</p><p>Here’s an example of a class hierarchy that violates the LSP:</p><pre>open class Shape {<br>    open fun area(): Double = 0.0<br>}<br><br>class Rectangle(<br>    private val width: Double,<br>    private val height: Double<br>) : Shape() {<br>    override fun area(): Double = width * height<br>}<br><br>class Square(<br>    private val side: Double<br>) : Shape() {<br>    override fun area(): Double = side * side<br>}</pre><p>This class hierarchy violates the LSP because a Square cannot be used in place of a Rectangle - if we try to create a Square with a width and height that are different, we&#39;ll get an incorrect area.</p><p>Here’s an example of how to refactor this class hierarchy to follow the LSP:</p><pre>interface Shape {<br>    fun area(): Double<br>}<br><br>class Rectangle(<br>    private val width: Double,<br>    private val height: Double<br>) : Shape {<br>    override fun area(): Double = width * height<br>}<br><br>class Square(<br>    private val side: Double<br>) : Shape {<br>    override fun area(): Double = side * side<br>}</pre><p>In this refactored code, we’ve created an interface called Shape that defines a area method. Both Rectangle and Square implement this interface, so they can be used interchangeably.</p><h4>I — Interface Segregation Principle</h4><p>The Interface Segregation Principle (ISP) states that clients should not be forced to depend on interfaces they don’t use. This means that interfaces should be designed with the specific needs of their clients in mind, and clients should not be required to implement methods they don’t need.</p><p>Here’s an example of a class hierarchy that violates the ISP:</p><pre>interface Car {<br>    fun startEngine()<br>    fun stopEngine()<br>    fun drive()<br>    fun reverse()<br>}<br><br>class Sedan : Car {<br>    override fun startEngine() { ... }<br>    override fun stopEngine() { ... }<br>    override fun drive() { ... }<br>    override fun reverse() { ... }<br>}<br><br>class SportsCar : Car {<br>    override fun startEngine() { ... }<br>    override fun stopEngine() { ... }<br>    override fun drive() { ... }<br>    override fun reverse() { ... }<br>}</pre><p>This class hierarchy violates the ISP because not all clients of the Car interface need to implement the reverse method. For example, a client that only needs to drive the car forward would be forced to implement the reverse method, even though it&#39;s not needed.</p><p>Here’s an example of how to refactor this class hierarchy to follow the ISP:</p><pre>interface Car {<br>    fun startEngine()<br>    fun stopEngine()<br>    fun drive()<br>}<br><br>interface ReverseableCar : Car {<br>    fun reverse()<br>}<br><br>class Sedan : Car {<br>    override fun startEngine() { ... }<br>    override fun stopEngine() { ... }<br>    override fun drive() { ... }<br>}<br><br>class SportsCar : ReverseableCar {<br>    override fun startEngine() { ... }<br>    override fun stopEngine() { ... }<br>    override fun drive() { ... }<br>    override fun reverse() { ... }<br>}</pre><p>In this refactored code, we’ve split the Car interface into two separate interfaces - Car and ReverseableCar. The Car interface only includes methods that all clients need to implement, while the ReverseableCar interface includes the additional reverse method. Clients that only need to drive the car forward can use the Car interface, while clients that need to drive the car in both directions can use the ReverseableCar interface.</p><h4>D — Dependency Inversion Principle</h4><p>The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions. This means that modules should be designed to depend on abstract interfaces or classes, rather than concrete implementations.</p><p>Here’s an example of a class hierarchy that violates the DIP:</p><pre>class UserService {<br>    private val userRepository = UserRepository()<br><br>    fun createUser(user: User) {<br>        userRepository.save(user)<br>    }<br>}<br><br>class UserRepository {<br>    fun save(user: User) {<br>        // save the user to the database<br>    }<br>}</pre><p>This class hierarchy violates the DIP because the UserService depends directly on the UserRepository class, which is a low-level module. If we later decide to change the way users are stored (e.g. switch to a different database or a web service), we&#39;ll need to change the UserService class as well.</p><p>Here’s an example of how to refactor this class hierarchy to follow the DIP:</p><pre>interface UserRepository {<br>    fun save(user: User)<br>}<br><br>class UserService(<br>    private val userRepository: UserRepository<br>) {<br>    fun createUser(user: User) {<br>        userRepository.save(user)<br>    }<br>}<br><br>class DatabaseUserRepository : UserRepository {<br>    override fun save(user: User) {<br>        // save the user to the database<br>    }<br>}</pre><p>In this refactored code, we’ve created an abstract UserRepository interface that defines the methods that the UserService needs. We&#39;ve also created a DatabaseUserRepository class that implements the UserRepository interface and provides the concrete implementation of the save method.</p><p>Now, the UserService class depends only on the abstract UserRepository interface, rather than the concrete UserRepository class. This makes the code more flexible and easier to maintain, because we can swap out the DatabaseUserRepository class with a different implementation (e.g. a web service implementation) without needing to change the UserService class.</p><h3>Advice to Fellow developers</h3><p>In summary, the SOLID principles are a set of guidelines for writing flexible, maintainable, and reusable code. By following these principles, we can write code that is easier to test, easier to maintain, and less prone to bugs.</p><p>While the examples in this article were written in Kotlin, the SOLID principles are applicable to any object-oriented programming language. If you’re new to SOLID, <strong>start by focusing on the Single Responsibility Principle</strong>, which is the most important of the five principles. Once you’ve mastered SRP, you can move on to the other principles and start applying them to your own code.</p><blockquote><strong>SOLID is not a set of hard-and-fast rules</strong> — it’s a set of guidelines that can help you write better code. As with any set of guidelines, there will be times when you need to break the rules in order to achieve your goals. But by following the SOLID principles whenever possible, you’ll be able to write code that is more robust, more maintainable, and more reusable.</blockquote><p>Thanks for reading this article. You can connect with me on <a href="https://www.linkedin.com/in/lvamsavarthan/">LinkedIn</a>, <a href="https://twitter.com/lvamsavarthan">Twitter</a>, and <a href="https://www.instagram.com/lvamsavarthan/">Instagram</a>.</p><blockquote><em>If you found this article helpful, please recommend it by hitting the clap icon as many times you wish 👏 Let’s enable each other with the power of knowledge.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=701382845dfe" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Organize your Android project like a pro]]></title>
            <link>https://medium.com/@amsavarthan/organize-your-android-project-like-a-pro-understanding-the-difference-between-api-and-70ae9cfb7b72?source=rss-f500f15cb4bf------2</link>
            <guid isPermaLink="false">https://medium.com/p/70ae9cfb7b72</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[android-app-development]]></category>
            <dc:creator><![CDATA[Amsavarthan Lv]]></dc:creator>
            <pubDate>Fri, 27 Jan 2023 06:14:30 GMT</pubDate>
            <atom:updated>2024-02-09T18:39:53.063Z</atom:updated>
            <content:encoded><![CDATA[<h3>Organize your Android project like a pro: Understanding the difference between ‘api’ and ‘implementation’ in Gradle</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*paItD3khoALZet9Z" /><figcaption>Photo by <a href="https://unsplash.com/@martinshreder?utm_source=medium&amp;utm_medium=referral">Martin Shreder</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>When working on an Android project, it’s important to understand the difference between the api and implementation configurations in Gradle. These configurations determine how dependencies are handled within the project and can affect the overall structure and performance of the app.</p><p>The api configuration is used to declare dependencies that are exposed to consumers of the module. This means that other modules in the project that depend on this one will have access to the classes and methods provided by the api dependencies. For example, if we have a module called library and we want other modules to be able to use the classes and methods provided by the Retrofit library, we would add the following line to the <strong>library’s</strong> <strong>build.gradle</strong> file:</p><pre>api(&quot;com.squareup.retrofit2:retrofit:2.9.0&quot;)</pre><p>On the other hand, the implementation configuration is used to declare dependencies that are only used within the module and not exposed to consumers. These dependencies are not available to other modules and are only used by the module itself. For example, if we have a module called app and we want to use the classes and methods provided by the OkHttp library, we would add the following line to the <strong>app’s build.gradle</strong> file:</p><pre>implementation(&quot;com.squareup.okhttp3:okhttp:4.9.0&quot;)</pre><p>It’s important to note that you can use both api and implementation configuration in the same module, it depends on the use case.</p><p>In summary, when declaring dependencies in an Android Gradle script, use the api configuration for dependencies that need to be exposed to other modules and use the implementation configuration for dependencies that are only used within the module. This will help maintain a clean and organized project structure and improve the performance of the app.</p><p>Thanks for reading this article. You can connect with me on <a href="https://www.linkedin.com/in/lvamsavarthan/">LinkedIn</a>, <a href="https://twitter.com/lvamsavarthan">Twitter</a>, and <a href="https://www.instagram.com/lvamsavarthan/">Instagram</a>.</p><blockquote><em>If you found this article helpful, please recommend it by hitting the clap icon as many times you wish 👏 Let’s enable each other with the power of knowledge.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=70ae9cfb7b72" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unlocking Reusability in Gradle]]></title>
            <link>https://medium.com/@amsavarthan/unlocking-reusability-in-gradle-how-to-use-kotlin-written-convention-plugins-11b95cb008ef?source=rss-f500f15cb4bf------2</link>
            <guid isPermaLink="false">https://medium.com/p/11b95cb008ef</guid>
            <category><![CDATA[android-development]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[technology]]></category>
            <dc:creator><![CDATA[Amsavarthan Lv]]></dc:creator>
            <pubDate>Thu, 26 Jan 2023 15:15:12 GMT</pubDate>
            <atom:updated>2024-02-09T18:41:01.517Z</atom:updated>
            <content:encoded><![CDATA[<h3>Unlocking Reusability in Gradle: How to Use Kotlin-written Convention Plugins</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*XUFlAhWOfuAkp6KI" /><figcaption>Photo by <a href="https://unsplash.com/@phonvanna?utm_source=medium&amp;utm_medium=referral">Vanna Phon</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Gradle is a powerful build tool that allows developers to easily manage dependencies and configure builds for their projects. However, as projects grow in size and complexity, it can become difficult to maintain and reuse build configurations. This is where Gradle convention plugins come in.</p><p><a href="https://docs.gradle.org/current/samples/sample_convention_plugins.html">Convention plugins</a> are a way to encapsulate and reuse common build configuration in Gradle. They allow developers to define a set of conventions for a project or module, and then apply those conventions to other projects or modules. This can greatly simplify build management and increase efficiency.</p><p>One of the most powerful features of convention plugins is that they can be written in Kotlin, a modern programming language that runs on the Java Virtual Machine. Kotlin provides a number of features that make it well-suited for writing Gradle plugins, such as strong type safety, a concise syntax, and improved support for functional programming.</p><h3>Creating a convention plugin</h3><pre>plugins { //PluginDependenciesSpecScope<br>    id(&quot;com.android.library&quot;)<br>    id(&quot;org.jetbrains.kotlin.android&quot;)<br>}<br><br>android { //BaseAppModuleExtension<br>    compileSdk = 33<br><br>    defaultConfig { //ApplicationDefaultConfig<br>        minSdk = 21<br>    }<br><br>    compileOptions { //CompileOptions<br>        sourceCompatibility = JavaVersion.VERSION_1_8<br>        targetCompatibility = JavaVersion.VERSION_1_8<br>        isCoreLibraryDesugaringEnabled = true<br>     }<br>}<br><br>dependencies { //DependencyHandlerScope<br>    implementation(&quot;com.android.tools:desugar_jdk_libs:1.2.2&quot;)<br>    //Some more common dependencies used by all libraries we create<br>}</pre><p>Consider the above configuration which we needs to be added to each and every library module we create. With the help of convention plugins we can eliminate the boilerplate code.</p><p>Let’s first create a folder called <strong>build-logic </strong>inside which resides the <strong>convention </strong>module, which can be used to keep a single source of truth for common module configurations.</p><p>Paste the following code in AndroidLibraryConventionPlugin.kt</p><pre>class AndroidLibraryConventionPlugin : Plugin&lt;Project&gt; {<br>    override fun apply(target: Project) {<br>        with(target) {<br>            with(pluginManager) {<br>                apply(&quot;com.android.library&quot;)<br>                apply(&quot;org.jetbrains.kotlin.android&quot;)<br>            }<br><br>            extensions.configure&lt;LibraryExtension&gt; {<br>                configureKotlinAndroid(this)<br>                defaultConfig.targetSdk = 33<br>            }<br>        }<br>    }<br>}<br><br>internal fun Project.configureKotlinAndroid(<br>    commonExtension: CommonExtension&lt;*, *, *, *&gt;,<br>) {<br><br>    commonExtension.apply {<br>        compileSdk = 33<br><br>        defaultConfig {<br>            minSdk = 21<br>        }<br><br>        compileOptions {<br>            sourceCompatibility = JavaVersion.VERSION_1_8<br>            targetCompatibility = JavaVersion.VERSION_1_8<br>            isCoreLibraryDesugaringEnabled = true<br>        }<br>    }<br><br>    //version catalog<br>    val libs = extensions.getByType&lt;VersionCatalogsExtension&gt;().named(&quot;libs&quot;)<br><br>    dependencies {<br>        add(&quot;coreLibraryDesugaring&quot;, libs.findLibrary(&quot;android.desugarJdkLibs&quot;).get())<br>    }<br><br>}</pre><p>To share dependency versions between project we can make use of Gradle <strong>version catalog</strong>. You can learn more about it from the official documentation <a href="https://docs.gradle.org/current/userguide/platforms.html">here</a>.</p><p>The above code has nothing different instead the same gradle script configuration written purely as a Kotlin class. Make sure to import classes from <strong>org.gradle</strong> package</p><p>Then, configure the <strong>settings.gradle.kts</strong> in the convention module</p><pre>dependencyResolutionManagement {<br>    repositories {<br>        google()<br>        mavenCentral()<br>    }<br>    versionCatalogs {<br>        create(&quot;libs&quot;) {<br>            from(files(&quot;../gradle/libs.versions.toml&quot;))<br>        }<br>    }<br>}<br><br>rootProject.name = &quot;build-logic&quot;<br>include(&quot;:convention&quot;)</pre><p>Then, we need to provide gradle the details of our plugins. Inside <strong>build.gradle.kts (:build-logic:convention)</strong></p><pre>plugins {<br>    `kotlin-dsl`<br>}<br><br>group = &quot;com.amsavarthan.buildlogic&quot;<br><br>java {<br>    sourceCompatibility = JavaVersion.VERSION_1_8<br>    targetCompatibility = JavaVersion.VERSION_1_8<br>}<br><br>dependencies {<br>    compileOnly(libs.android.gradlePlugin)<br>    compileOnly(libs.kotlin.gradlePlugin)<br>}<br><br>gradlePlugin{<br>    plugins{<br>        register(&quot;androidLibrary&quot;){<br>            id=&quot;amsavarthan.android.library&quot;<br>            implementationClass=&quot;AndroidLibraryConventionPlugin&quot;<br>        }<br>    }<br>}</pre><p>The <strong>libs.versions.toml </strong>must have the following</p><pre>[versions]<br>androidDesugarJdkLibs = &quot;1.2.2&quot;<br>androidGradlePlugin = &quot;7.3.1&quot;<br>kotlin = &quot;1.8.0&quot;<br><br>[libraries]<br>android-gradlePlugin = { group = &quot;com.android.tools.build&quot;, name = &quot;gradle&quot;, version.ref = &quot;androidGradlePlugin&quot; }<br>android-desugarJdkLibs = { group = &quot;com.android.tools&quot;, name = &quot;desugar_jdk_libs&quot;, version.ref = &quot;androidDesugarJdkLibs&quot; }<br>kotlin-gradlePlugin = { group = &quot;org.jetbrains.kotlin&quot;, name = &quot;kotlin-gradle-plugin&quot;, version.ref = &quot;kotlin&quot; }</pre><h4>Including in our project</h4><p>Once the above configuration is done, we can include the plugins into our project by following the below steps. First in our <strong>settings.gradle.kts (project level) </strong>we need to include our <strong>build-logic</strong>.</p><pre>pluginManagement {<br>    //Include this line<br>    includeBuild(&quot;build-logic&quot;)<br><br>    repositories {<br>        //...<br>    }<br>}<br>dependencyResolutionManagement {<br>    //...<br>}<br>rootProject.name = &quot;sample&quot;<br>include(&quot;:app&quot;)<br>//...</pre><p>In all your libraries we can just make use of the plugin we just created. This is how a <strong>module’s build.gradle.kts</strong> would usually look with the plugin.</p><pre>plugins {<br>    id(&quot;amsavarthan.android.library&quot;)<br>}<br><br>//Remove block if module doesn&#39;t have any extra dependencies<br>dependencies {<br>    //module-specific dependencies<br>}</pre><h3>FYR: Now in Android</h3><p>You can checkout the <a href="https://github.com/android/nowinandroid/tree/main/build-logic">Now in Android</a> (NiA) project which uses this approach.</p><blockquote>These plugins are additive and composable, and try to only accomplish a single responsibility. Modules can then pick and choose the configurations they need.</blockquote><p>Some really good examples from the codebase that show how this is all tied together are the following:</p><ol><li>The <a href="https://github.com/android/nowinandroid/blob/main/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt">AndroidLibraryConventionPlugin</a> applies the com.android.library and org.jetbrains.kotlin.android plugins, uses the configureKotlinAndroid() function from KotlinAndroid.kt to configure things like your compileSdk , compileOptions, kotlinOptions, and the configureFlavors() function from the Flavor.kt file to configure project flavors.</li><li>The <a href="https://github.com/android/nowinandroid/blob/main/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt">AndroidFeatureConventionPlugin</a> applies the same plugins from above plus org.jetbrains.kotlin.kapt because all features modules in NiA use libraries like Hilt which require KAPT for code generation. It also adds a lot of common dependencies like the :core modules and some libraries.</li><li>The <a href="https://github.com/android/nowinandroid/blob/main/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt">AndroidLibraryComposeConventionPlugin</a> uses the configureAndroidCompose() function to configure Compose to work in any module it’s applied to.</li></ol><p>The result is a feature module build scrips mostly end up looking like this, which is how they <em>should. </em>No duplication or unnecessary scripts that are hard to understand down the line. Just the things that a module needs.</p><pre>plugins {<br>    id(&quot;nowinandroid.android.feature&quot;)<br>    id(&quot;nowinandroid.android.library.jacoco&quot;)<br>    id(&quot;nowinandroid.android.library.compose&quot;)<br>}<br><br>dependencies {<br>    // module-specific dependencies<br>}</pre><p>You can immediately see what it means for these plugins to be <em>additive and composable. </em>Want a module that doesn’t use Compose? Just apply the first two plugins. Want to use Compose? Just add the third one.</p><p>Thanks for reading this article. You can connect with me on <a href="https://www.linkedin.com/in/lvamsavarthan/">LinkedIn</a>, <a href="https://twitter.com/lvamsavarthan">Twitter</a>, and <a href="https://www.instagram.com/lvamsavarthan/">Instagram</a>.</p><blockquote><strong><em>If you found this article helpful, please recommend it by hitting the clap icon as many times you wish 👏 Let’s enable each other with the power of knowledge.</em></strong></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=11b95cb008ef" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Parcelable and custom @TypeParceler in Kotlin Multiplatform Mobile (KMM)]]></title>
            <link>https://medium.com/@amsavarthan/using-parcelable-and-custom-typeparceler-in-kotlin-multiplatform-mobile-kmm-c99203bd2e?source=rss-f500f15cb4bf------2</link>
            <guid isPermaLink="false">https://medium.com/p/c99203bd2e</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[parcelable-android]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <dc:creator><![CDATA[Amsavarthan Lv]]></dc:creator>
            <pubDate>Mon, 05 Dec 2022 08:04:51 GMT</pubDate>
            <atom:updated>2022-12-05T08:04:51.104Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*t4R51H325XJckLQC" /><figcaption>Photo by <a href="https://unsplash.com/@jaken?utm_source=medium&amp;utm_medium=referral">Jake Nebov</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Cross platform annotations are rather confusing at first glance!</p><p>This short article shows you how to work with KMM (Kotlin Multiplatform Mobile) cross-platform annotations, implement a custom @TypeParceler for Android Parcelable interface in a KMM project for any Type or Class including primitives.</p><p>The biggest hurdle in implementing cross-platform entities in KMM is properly defining the annotations and setting up the implementation and the “stubs” for each platform. Code needs to be implemented on one platform and ignored on the other platform.</p><p>I will show how to implement the Parcelable interface for Android, and stub the Parcelable interface for iOS (so it is properly ignored), as iOS does not need or useParcelable.</p><p>In this example, I will use the non-natively parcelable class of LocalDateTime , from the shared Kotlin-native kotlinx-datetime library, as the example of non-primitive ‘Parcelable’ class. After seeing this example, its straightforward to use any non-primitive class, just change the implementation and add the appropriate @TypeParceler .</p><p>On Android, this code is very necessary to prevent crashes when the Android app is put into the background, as the Parceler is automatically run (on Android) to save state. Without this implementation, your app will crash when your domain models have custom class types. The iOS platform does not use a Parceler , so it needs to be stubbed out.</p><p>Using primitive data types is rather straightforward as you only stub/implement the Parcelable interface and the @Parcelize annotation.</p><p>In this article I go a step further and show you how to implement a custom@TypeParceler to use a non-primitive class Parceler. In this example we are making a @TypeParceler for LocalDateTime.</p><h3>Adding Dependencies</h3><p>Go to build.gradle.kts in the shared module and add the dependency for kotlix-datetime and kotlin parcelize plugin</p><pre>plugins {<br>    kotlin(&quot;multiplatform&quot;)<br>    id(&quot;com.android.library&quot;)<br>    id(&quot;kotlin-parcelize&quot;) // add this<br>    kotlin(&quot;plugin.serialization&quot;) version &quot;1.5.30&quot; // add this<br>    //...<br>}<br>kotlin {<br><br>    android()<br>    listOf(<br>        iosX64(),<br>        iosArm64(),<br>        iosSimulatorArm64()<br>    ).forEach {<br>        it.binaries.framework {<br>           baseName = &quot;shared&quot;<br>        }<br>    }<br><br>    sourceSets {<br>        val commonMain by getting {<br>        dependencies {<br>           implementation(&quot;org.jetbrains.kotlinx:kotlinx-datetime:0.4.0&quot;) // add this<br>           implementation(&quot;org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1&quot;)<br>        }<br>        //...<br>    }<br>        //...<br>  }<br>    //...<br>}</pre><p>Now lets modify the code in Platform.kt file of common module</p><pre>import kotlinx.datetime.LocalDateTime<br><br>@OptIn(ExperimentalMultiplatform::class)<br>@OptionalExpectation<br>@Target(AnnotationTarget.CLASS)<br>@Retention(AnnotationRetention.BINARY)<br>expect annotation class CommonParcelize()<br><br>expect interface CommonParcelable<br><br>@OptIn(ExperimentalMultiplatform::class)<br>@OptionalExpectation<br>@Retention(AnnotationRetention.SOURCE)<br>@Repeatable<br>@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)<br>expect annotation class CommonTypeParceler&lt;T, P : CommonParceler&lt;in T&gt;&gt;()<br><br>expect interface CommonParceler&lt;T&gt;<br><br>expect object LocalDateTimeParceler: CommonParceler&lt;LocalDateTime&gt;</pre><p>Now, open the Platform.kt file of android module and let’s add our actual implementation</p><pre>import android.os.Parcel<br>import android.os.Parcelable<br>import kotlinx.datetime.LocalDateTime <br>import kotlinx.datetime.toLocalDateTime<br>import kotlinx.parcelize.Parceler // NOTE: kotlinx.parcelize.*<br>import kotlinx.parcelize.Parcelize<br>import kotlinx.parcelize.TypeParceler<br><br>actual typealias CommonParcelize = Parcelize // defined on Android, skipped on iOS<br>actual typealias CommonParcelable = Parcelable // defined on Android, skipped on iOS<br><br>actual typealias CommonParceler&lt;T&gt; = Parceler&lt;T&gt; // defined on Android, skipped on iOS<br>actual typealias CommonTypeParceler&lt;T,P&gt; = TypeParceler&lt;T, P&gt; // defined on Android, skipped on iOS<br><br>// Performs the type conversion to/from a Parcelable supported type (primitives only)<br>actual object LocalDateTimeParceler : Parceler&lt;LocalDateTime&gt; {  // defined on Android, skipped on iOS<br>  override fun create(parcel: Parcel): LocalDateTime {<br>    val date = parcel.readString()<br>    return date?.toLocalDateTime()<br>      ?: LocalDateTime(0, 0, 0, 0, 0)<br>  }<br>  override fun LocalDateTime.write(parcel: Parcel, flags: Int) {<br>    parcel.writeString(this.toString())<br>  }<br>}</pre><p>Modify the Platform.kt file in the ios module as follows</p><pre>import kotlinx.datetime.LocalDateTime<br><br>// Note: no need to define CommonParcelize here because it is @OptionalExpectation<br>actual interface CommonParcelable // not used on iOS<br><br>// Note: no need to define CommonTypeParceler&lt;T,P : CommonParceler&lt;in T&gt;&gt; here because it is @OptionalExpectation<br>actual interface CommonParceler&lt;T&gt; // not used on iOS<br>actual object LocalDateTimeParceler : CommonParceler&lt;LocalDateTime&gt; // not used on iOS</pre><h3>Implementing into data classes</h3><p>I have created a sample data class which has the following members. Let’s annotate with our custom annotation that we have defined to tell android that this needs to be aliased by Parcelable interface</p><pre>import com.amsavarthan.note.domain.CommonParcelable<br>import com.amsavarthan.note.domain.CommonParcelize<br>import com.amsavarthan.note.domain.CommonTypeParceler<br>import com.amsavarthan.note.domain.LocalDateTimeParceler<br>import com.amsavarthan.note.domain.time.DateTimeUtil<br>import kotlinx.datetime.LocalDateTime<br>import kotlinx.datetime.serializers.LocalDateTimeIso8601Serializer<br>import kotlinx.serialization.Serializable<br><br>@Serializable<br>@CommonParcelize<br>data class Note(<br>    val id: Long? = null,<br>    val title: String = &quot;Untitled&quot;,<br>    val content: String = &quot;&quot;,<br>    @Serializable(with = LocalDateTimeIso8601Serializer::class)<br>    @CommonTypeParceler&lt;LocalDateTime, LocalDateTimeParceler&gt;()<br>    val created: LocalDateTime = DateTimeUtil.now()<br>) : CommonParcelable</pre><p>Given this code example you are able to readily extend it for any custom type that needs to use @Parcelize, as well as implement cross platform annotations for other parts of Android and iOS code.</p><p>You can checkout the sample project demonstration the usage of the above.</p><p><a href="https://github.com/amsavarthan/NotesKmm">GitHub - amsavarthan/NotesKmm: 🗒️ A minimal Notes app built using KMM and SQLDelight with Jetpack Compose for Android and SwiftUI for iOS following Clean Architecture</a></p><p>Thanks for reading this article. You can connect with me on <a href="https://www.linkedin.com/in/lvamsavarthan/">LinkedIn</a>, <a href="https://twitter.com/lvamsavarthan">Twitter</a>, and <a href="https://www.instagram.com/lvamsavarthan/">Instagram</a>.</p><blockquote><strong><em>If you found this article helpful, please recommend it by hitting the clap icon as many times you wish 👏 Let’s enable each other with the power of knowledge.</em></strong></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c99203bd2e" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>