<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Jetpack Compose - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Jetpack Compose - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 26 May 2026 22:47:43 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/jetpack-compose/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Use Tooltips in Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ When I wrote my last article about Jetpack Compose, I stated there that Jetpack Compose is missing some (in my opinion) basic components, and one of them is the tooltip. At the time, there was no built-in composable to display tooltips and there were... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-tooltips-in-jetpack-compose/</link>
                <guid isPermaLink="false">66fd516514798d90f2228542</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Jetpack Compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tooltip ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Wed, 02 Oct 2024 13:57:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813989960/b0a7ab29-d87c-4d87-9847-70b7e1c341b1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When I wrote my <a target="_blank" href="https://medium.com/better-programming/is-jetpack-compose-ready-for-you-eae6c93ad3f8">last article about Jetpack Compose</a>, I stated there that Jetpack Compose is missing some (in my opinion) basic components, and one of them is the tooltip.</p>
<p>At the time, there was no built-in composable to display tooltips and there were several alternative solutions circling online. The problem with those solutions was that once Jetpack Compose released newer versions, those solutions might break. So it wasn’t ideal and the community was left hoping that sometime in the future, support would be added for tooltips.</p>
<p>I’m glad to say that since <a target="_blank" href="https://developer.android.com/jetpack/androidx/releases/compose-material3#1.1.0">version 1.1.0 of Compose Material 3</a>, we now have built in tooltip support. 👏</p>
<p>While this in itself is great, more than a year has passed since that version was released. And with subsequent versions, the API related to tooltips changed drastically as well.</p>
<p>If you go over the changelog, you will see how the public and internal APIs have changed. So bear in mind, that when you read this article, things may have continued to change as everything related to Tooltips is still marked by the annotation <strong>ExperimentalMaterial3Api::class</strong>.</p>
<p>❗️ The version of material 3 used for this article is 1.2.1, which was released on March 6th, 2024</p>
<h2 id="heading-tooltip-types">Tooltip Types</h2>
<p>We now have support for two different types of tooltips:</p>
<ol>
<li><p>Plain tooltip</p>
</li>
<li><p>Rich media tooltip</p>
</li>
</ol>
<h3 id="heading-plain-tooltip">Plain Tooltip</h3>
<p>You can use the first kind to provide information about an icon button that wouldn’t be clear otherwise. For example, you can use a plain tooltip to indicate to a user what the icon button represents.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602449314/94cf84bf-dec0-462c-a8a0-6f878e0d5db3.gif" alt="Basic tooltip example" class="image--center mx-auto" width="213" height="450" loading="lazy"></p>
<p>To add a tooltip to your application, you use the <strong>TooltipBox</strong> composable. This composable takes several arguments:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TooltipBox</span><span class="hljs-params">(
    positionProvider: <span class="hljs-type">PopupPositionProvider</span>,
    tooltip: @<span class="hljs-type">Composable</span> <span class="hljs-type">TooltipScope</span>.() -&gt; <span class="hljs-type">Unit</span>,
    state: <span class="hljs-type">TooltipState</span>,
    modifier: <span class="hljs-type">Modifier</span> = Modifier,
    focusable: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">true</span>,
    enableUserInput: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">true</span>,
    content: @<span class="hljs-type">Composable</span> () -&gt; <span class="hljs-type">Unit</span>,
)</span></span>
</code></pre>
<p>Some of these should be familiar to you if you have used Composables before. I’ll highlight the ones that have a specific use case here:</p>
<ul>
<li><p>positionProvider - Of <strong>PopupPositionProvider</strong> type, and is used to calculate the position of the tooltip.</p>
</li>
<li><p>tooltip - This is where you can design the UI of how the tooltip will look like.</p>
</li>
<li><p>state - This holds the state that is associated with a specific Tooltip instance. It exposes methods like showing/dismissing the tooltip and when instantiating an instance of one, you can declare if the tooltip should be persistent or not (meaning if it should keep displaying on the screen until a user performs a click action outside the tooltip).</p>
</li>
<li><p>content - This is the UI that the tooltip will display above/below.</p>
</li>
</ul>
<p>Here is an example of instantiating a <strong>BasicTooltipBox</strong> with all the relevant arguments filled in:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">BasicTooltip</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> tooltipPosition = TooltipDefaults.rememberPlainTooltipPositionProvider()
    <span class="hljs-keyword">val</span> tooltipState = rememberBasicTooltipState(isPersistent = <span class="hljs-literal">false</span>)

    BasicTooltipBox(positionProvider = tooltipPosition,
        tooltip =  { Text(<span class="hljs-string">"Hello World"</span>) } ,
        state = tooltipState) {
        IconButton(onClick = { }) {
            Icon(imageVector = Icons.Filled.Favorite, 
                 contentDescription = <span class="hljs-string">"Your icon's description"</span>)
        }
    }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602558759/e00e0bed-6a95-489e-af5c-a7d9dcc33fe6.gif" alt="A basic tooltip" class="image--center mx-auto" width="213" height="450" loading="lazy"></p>
<p>Jetpack Compose has a built in class called TooltipDefaults. You can use this class to help you instantiate arguments that make up a TooltipBox. For instance, you could use <strong>TooltipDefaults.rememberPlainTooltipPositionProvider</strong> to correctly position the tooltip in relation to the anchor element.</p>
<h3 id="heading-rich-tooltip">Rich Tooltip</h3>
<p>A rich media tooltip takes more space than a plain tooltip and can be used to provide more context about the functionality of an icon button. When the tooltip is shown, you can add buttons and links to it to provide further explanation or definitions.</p>
<p>It is instantiated in a similar way as a plain tooltip, inside of a TooltipBox, but you use the RichTooltip composable.</p>
<pre><code class="lang-kotlin">TooltipBox(positionProvider = tooltipPosition,
        tooltip = {
                  RichTooltip(
                      title = { Text(<span class="hljs-string">"RichTooltip"</span>) },
                      caretSize = caretSize,
                      action = {
                          TextButton(onClick = {
                              scope.launch {
                                  tooltipState.dismiss()
                                  tooltipState.onDispose()
                              }
                          }) {
                              Text(<span class="hljs-string">"Dismiss"</span>)
                          }
                      }
                  ) {
                        Text(<span class="hljs-string">"This is where a description would go."</span>)
                  }
        },
        state = tooltipState) {
        IconButton(onClick = {
            <span class="hljs-comment">/* Icon button's click event */</span>
        }) {
            Icon(imageVector = tooltipIcon,
                contentDescription = <span class="hljs-string">"Your icon's description"</span>,
                tint = iconColor)
        }
    }
</code></pre>
<p>A few things to notice about a Rich tooltip:</p>
<ol>
<li><p>A Rich tooltip has support for a caret.</p>
</li>
<li><p>You can add an action (that is, a button) to the tooltip to give users an option to find out more information.</p>
</li>
<li><p>You can add logic to dismiss the tooltip.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602624042/40160d88-4e8a-4487-835d-1b74a9dd7c72.png" alt="Rich tooltip without a caret" class="image--center mx-auto" width="375" height="792" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602651265/f3e6f7fe-c4e1-4f98-972d-b20a273900b4.png" alt="Rich tooltip with a caret" class="image--center mx-auto" width="375" height="792" loading="lazy"></p>
<h3 id="heading-edge-cases">Edge Cases</h3>
<p>When you choose to mark your <strong>tooltip state as persistent</strong>, it means that once the user interacts with the UI that shows your tooltip, it will stay visible until the user presses anywhere else on the screen.</p>
<p>If you looked at the example of a Rich tooltip from above, you might have noticed that we have added a button to dismiss the tooltip once it’s clicked.</p>
<p>There is a problem that happens once a user presses that button. Since the dismiss action is performed on the tooltip, if a user wants to perform another long press on the UI item that invokes this tooltip, the tooltip won’t be shown again. This means that the state of the tooltip is persistent on it being dismissed. So, how do we go about and resolve this?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602690256/a31b56bb-77c4-4444-bab6-7ffcca3f5207.gif" alt="Second long press does not trigger the tooltip" class="image--center mx-auto" width="213" height="450" loading="lazy"></p>
<p>In order to “reset” the state of the tooltip, we have to call the <strong>onDispose</strong> method that is exposed through the tooltip state. Once we do that, the tooltip state is reset and the tooltip will be shown again when the user performs a long press on the UI item.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalMaterial3Api::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">RichTooltip</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> tooltipPosition = TooltipDefaults.rememberRichTooltipPositionProvider()
    <span class="hljs-keyword">val</span> tooltipState = rememberTooltipState(isPersistent = <span class="hljs-literal">true</span>)
    <span class="hljs-keyword">val</span> scope = rememberCoroutineScope()

    TooltipBox(positionProvider = tooltipPosition,
        tooltip = {
                  RichTooltip(
                      title = { Text(<span class="hljs-string">"RichTooltip"</span>) },
                      caretSize = TooltipDefaults.caretSize,
                      action = {
                          TextButton(onClick = {
                              scope.launch {
                                  tooltipState.dismiss()
                                  tooltipState.onDispose()  <span class="hljs-comment">/// &lt;---- HERE</span>
                              }
                          }) {
                              Text(<span class="hljs-string">"Dismiss"</span>)
                          }
                      }
                  ) {

                  }
        },
        state = tooltipState) {
        IconButton(onClick = {  }) {
            Icon(imageVector = Icons.Filled.Call, contentDescription = <span class="hljs-string">"Your icon's description"</span>)
        }
    }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602730404/60f31668-ea66-4127-b6fc-41f3aca952ae.gif" alt="onDispose solves the issue" class="image--center mx-auto" width="213" height="450" loading="lazy"></p>
<p>Another scenario where the tooltip state does not reset is if instead of calling ourselves for the dismiss method per a user’s action, the user clicks outside of the tooltip, causing it to be dismissed. This calls the dismiss method behind the scenes and the tooltip state is set to dismissed. Long pressing on the UI element to see our tooltip again will result in nothing.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602758707/60387e08-72e6-45d4-bd47-ffb2708e0efe.gif" alt="The tooltip does not show again" class="image--center mx-auto" width="213" height="450" loading="lazy"></p>
<p>Our logic that calls the tooltip’s onDispose method does not get triggered, so how can we reset the tooltip’s state?</p>
<p>Currently, I haven’t been able to figure this out. It might be related to the tooltip’s <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex">MutatorMutex</a>. Maybe with upcoming releases, there will be an API for this. I did notice that if other tooltips are present on the screen and they are pressed, this resets the previously clicked upon tooltip.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602790121/25a81994-a508-4c71-8424-c45370a7999d.gif" alt="25a81994-a508-4c71-8424-c45370a7999d" class="image--center mx-auto" width="213" height="450" loading="lazy"></p>
<p>If you would like to see the code featured here, you can go to <a target="_blank" href="https://github.com/TomerPacific/MediumArticles/tree/master/TooltipExample">this GitHub repository</a></p>
<p>If you would like to see tooltips in an application, you can check it out <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.laundry">here</a>.</p>
<h4 id="heading-references">References</h4>
<ul>
<li><p><a target="_blank" href="https://m3.material.io/components/tooltips/overview">Material3 Tooltip Overview</a></p>
</li>
<li><p><a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material3/TooltipDefaults">Tooltip Defaults</a></p>
</li>
<li><p><a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt">Tooltip Source Code</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Request Location Permissions in Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ Getting a user’s location can be a bit of hassle. Over the years, the permissions required to do this and the logic associated with it have changed quite drastically.  If your application depends on getting the user’s location, you want to ensure tha... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/requesting-location-permissions-in-jetpack-compose/</link>
                <guid isPermaLink="false">66ba5034158e6c6a8cb8c7a3</guid>
                
                    <category>
                        <![CDATA[ Jetpack Compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ user experience ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Thu, 05 Oct 2023 19:28:42 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/10/andrew-stutesman-l68Z6eF2peA-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Getting a user’s location can be a bit of hassle. Over the years, the permissions required to do this and the logic associated with it have changed quite drastically. </p>
<p>If your application depends on getting the user’s location, you want to ensure that the user has a good experience when the application requests this info. So it's crucial to handle all the edge cases and allow the user to select the option the they're most comfortable with.</p>
<p>With Jetpack Compose, the logic associated with getting the user’s location has changed a bit, and it’s important to know the in’s and out’s of how it can be done.</p>
<p>As with most things, there is a library you can use to handle getting permissions. It’s from Accompanist (read Google) and you can find it <a target="_blank" href="https://google.github.io/accompanist/permissions/#:~:text=A%20library%20which%20provides%20Android%20runtime%20permissions%20support%20for%20Jetpack%20Compose.&amp;text=The%20permission%20APIs%20are%20currently,marked%20with%20the%20%40ExperimentalPermissionsApi%20annotation.">here</a>. <strong>But you are here to learn how to do things yourself,</strong> right? So read on. 🕵️‍♀️</p>
<p>Before we get into the code and the logic, it is important to understand that requesting a permission from a user is a path that can have many decision points. As such, it is best described by having states that represent the current status of the permission (approved/rejected/denied) and the current status of the operating system. </p>
<p>The diagram below illustrates this flow:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/location.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Later on in this article, you will see how we will represent these states in variables in our code.</p>
<h2 id="heading-save-our-souls-sos">Save Our Souls (S.O.S)</h2>
<p>Before we get to the logic of asking for a permission, we’ll take care of the boilerplate around it. As always, add the necessary permissions to your AndroidManifest.xml file:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.ACCESS_COARSE_LOCATION"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.ACCESS_FINE_LOCATION"</span> /&gt;</span>
</code></pre>
<p>Then, we’ll create a composable screen where we ask for the necessary location permissions. The first step in this screen is to check whether the user has previously granted the required permissions. You can do this with something that is not new to Jetpack Compose – using the checkSelfPermission method:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> locationPermissionsAlreadyGranted = ContextCompat.checkSelfPermission(
            <span class="hljs-keyword">this</span>,
            Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
</code></pre>
<p>If the permission is not granted, we have to request it. We will use the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/activity/compose/package-summary#rememberlauncherforactivityresult">rememberLauncherForActivityResult</a> object to do this. This allows us in Jetpack Compose to get a result from an Activity.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> locationPermissions = arrayOf(
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.ACCESS_COARSE_LOCATION)

<span class="hljs-keyword">val</span> locationPermissionLauncher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
                onResult = { permissions -&gt;
                    <span class="hljs-keyword">val</span> permissionsGranted = permissions.values.reduce { acc, isPermissionGranted -&gt;
                        acc &amp;&amp; isPermissionGranted
                    }

                    <span class="hljs-keyword">if</span> (!permissionsGranted) {
                       <span class="hljs-comment">//Logic when the permissions were not granted by the user</span>
                    }
                })
</code></pre>
<p>The arguments that we need to pass to <code>rememberLauncherForActivityResult</code> are:</p>
<ol>
<li>An <a target="_blank" href="https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract">ActivityResultContract</a> – specifies the input of the activity and the output</li>
<li>onResult – a callback when the result is received</li>
</ol>
<p>In the code snippet above, we are using a multiple permissions contract, since we are requesting several location permissions. There is also a contract for requesting just one permission, <strong>ActivityResultContracts.RequestPermission().</strong></p>
<p>This piece of code doesn’t not run immediately as we need to request the permissions. To do that we use the launch method to start the activity:</p>
<pre><code class="lang-kotlin">locationPermissionLauncher.launch(locationPermissions)
</code></pre>
<p>In the case the user gave all or several of the required permissions, we can continue the logic of the application. </p>
<p>But, if the user did not approve any of the permissions, we need to find a way to make the user understand why these permissions are necessary.</p>
<h2 id="heading-explaining-the-rationale">Explaining the Rationale</h2>
<p>In case the user declined the permission, but did not select the “Deny And Don’t Ask Me Again” option, we have a way of giving the user a brief explanation on why they should grant the required permission(s). </p>
<p>To figure out if we should present this rationale, we use the <a target="_blank" href="https://developer.android.com/reference/androidx/core/app/ActivityCompat#shouldShowRequestPermissionRationale(android.app.Activity,java.lang.String)">shouldShowRequestPermissionRationale</a> from the Activity class:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> shouldShowPermissionRationale: <span class="hljs-built_in">Boolean</span> = shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)
</code></pre>
<p>Once we know that we can display this explanation, there are two ways to go about it:</p>
<ol>
<li>We can present it to the user with an AlertDialog</li>
<li>We can use the Snackbar</li>
</ol>
<p>Presenting an alert dialog is pretty straightforward. All we have to do is make sure we describe clearly to the user why it is required to approve this permission:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ShowLocationPermissionRationale</span><span class="hljs-params">()</span></span> {
        AlertDialog(
            onDismissRequest = {
               <span class="hljs-comment">//Logic when dismiss happens</span>
            },
        title = {
            Text(<span class="hljs-string">"Permission Required"</span>)
                },
        text = {
            Text(<span class="hljs-string">"You need to approve this permission in order to..."</span>)
        },
        confirmButton = {
            TextButton(onClick = {
              <span class="hljs-comment">//Logic when user confirms to accept permissions</span>
            }) {
                Text(<span class="hljs-string">"Confirm"</span>)
            }
        },
        dismissButton = {
            TextButton(onClick = {
              <span class="hljs-comment">//Logic when user denies to accept permissions</span>
            }) {
                Text(<span class="hljs-string">"Deny"</span>)
            }
        })
    }
</code></pre>
<p>If we want to present the Snackbar, we need to be aware that we have to use a Scaffold container since that is the only container that supports showing a Snackbar. If we don’t use one, a Snackbar won’t appear. </p>
<p>Below is a snippet that shows you how to do this:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> scope = rememberCoroutineScope()
<span class="hljs-keyword">val</span> snackbarHostState = remember { SnackbarHostState() }

Scaffold(snackbarHost = {
        SnackbarHost(hostState = snackbarHostState)
    }) { contentPadding -&gt;
        <span class="hljs-keyword">if</span> (shouldShowPermissionRationale) {
            LaunchedEffect(key1 = shouldShowPermissionRationale, block = {
                scope.launch {
                    <span class="hljs-keyword">val</span> userAction = snackbarHostState.showSnackbar(
                        message =<span class="hljs-string">"Please authorize location permissions"</span>,
                        actionLabel = <span class="hljs-string">"Approve"</span>,
                        duration = SnackbarDuration.Indefinite,
                        withDismissAction = <span class="hljs-literal">true</span>
                    )
                    <span class="hljs-keyword">when</span> (userAction) {
                        SnackbarResult.ActionPerformed -&gt; {
                            <span class="hljs-comment">//User approved to grant the permission</span>
                            <span class="hljs-comment">//Ask for permissions again</span>
                        }
                        SnackbarResult.Dismissed -&gt; {
                            <span class="hljs-comment">//User dismissed snackbar</span>
                        }
                    }
                }
            })
        }
}
</code></pre>
<p>We allowed the Snackbar itself to be dismissible using the withDismissAction attribute and listened in to the action performed by the user.</p>
<h2 id="heading-lifecycle-observer">Lifecycle Observer</h2>
<p>One thing we have glossed over is the fact that we need to make sure our permission request adheres to the composable lifecycle. This means that once a user chooses their preferences regarding the permission request, we need the UI to adapt accordingly. </p>
<p>If you try and put the code above inside the activity’s onCreate method, you will be surprised with the outcome, since the application will crash with the following exception:</p>
<blockquote>
<p><em>java.lang.IllegalStateException: Launcher has not been initialized</em></p>
</blockquote>
<p>This is because Composable functions are supposed to be side effect free. What is a side effect? According to <a target="_blank" href="https://developer.android.com/jetpack/compose/side-effects">Google’s documentation</a> it is:</p>
<blockquote>
<p>… a change to the state of the app that happens outside the scope of a composable function</p>
</blockquote>
<p>So, in our use case, launching an activity for the permissions is the side effect happening here. </p>
<p>To circumvent this scenario, we need to use one of the side effect options. Since we don’t want to ask the user for permissions without remembering what their choices were previously, we can’t use the general <strong>SideEffect</strong>. And <strong>LaunchedEffect</strong> is used for calling suspend methods inside of a Composable, which is not our use case here. </p>
<p>So we are left with <strong>DisposableEffect</strong>. Reading the <a target="_blank" href="https://developer.android.com/jetpack/compose/side-effects#disposableeffect">documentation</a>, we can see that <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#DisposableEffect(kotlin.Any,kotlin.Function1)">DisposableEffect</a> can be combined with Lifecycle events, which is what we are after.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> lifecycleOwner = LocalLifecycleOwner.current
            DisposableEffect(key1 = lifecycleOwner, effect = {
                <span class="hljs-keyword">val</span> observer = LifecycleEventObserver { _, event -&gt;
                    <span class="hljs-keyword">if</span> (event == Lifecycle.Event.ON_START &amp;&amp; !locationPermissionsAlreadyGranted) {
                        locationPermissionLauncher.launch(locationPermissions)
                       }
                    }
                    lifecycleOwner.lifecycle.addObserver(observer)
                    onDispose {
                        lifecycleOwner.lifecycle.removeObserver(observer)
                    }
                }
            )
</code></pre>
<p>In the code snippet above, we are adding a lifecycle observer that runs only in the case of the onStart lifecycle event. We also combine it with the boolean we have declared at the start of this section, locationPermissionsAlreadyGranted. This is so we won’t show the dialog for asking permissions if they are already granted. </p>
<p>As with all lifecycle observers, we need to remove our observer once the composition ends. We have that logic inside DisposableEffect’s onDispose clause.</p>
<h2 id="heading-location-not-found">Location Not Found</h2>
<p>The last case we need to deal with is when the user chooses the “Deny And Don’t Ask Me Again” option. When this happens, we cannot ask the user to grant the required permissions. </p>
<p>The only way the user can revert their choice is to go to the settings screen of our application and change the permissions there. So we need to direct the user to go there. </p>
<p>To open the settings screen of our application, we need to use an intent with the action of <strong>ACTION_APPLICATION_DETAILS_SETTINGS.</strong></p>
<pre><code class="lang-kotlin">Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts(<span class="hljs-string">"package"</span>, packageName, <span class="hljs-literal">null</span>)).also {
            startActivity(it)
        }
</code></pre>
<p>Taking the logic above, we can add it to our code when we know that the user has chosen to deny the permissions and not be asked again. This happens inside our request for permissions when the user has not granted the permissions and the option to show the rationale is false.</p>
<h2 id="heading-location-confirmed">Location Confirmed</h2>
<p>If we take everything we discussed in this article and put it inside one file, we will get the following code:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">ComponentActivity</span></span>() {

    <span class="hljs-meta">@OptIn(ExperimentalMaterial3Api::class)</span>
    <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)

        setContent {

            <span class="hljs-keyword">var</span> locationPermissionsGranted <span class="hljs-keyword">by</span> remember { mutableStateOf(areLocationPermissionsAlreadyGranted()) }
            <span class="hljs-keyword">var</span> shouldShowPermissionRationale <span class="hljs-keyword">by</span> remember {
                mutableStateOf(
                    shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)
                )
            }

            <span class="hljs-keyword">var</span> shouldDirectUserToApplicationSettings <span class="hljs-keyword">by</span> remember {
                mutableStateOf(<span class="hljs-literal">false</span>)
            }

            <span class="hljs-keyword">var</span> currentPermissionsStatus <span class="hljs-keyword">by</span> remember {
                mutableStateOf(decideCurrentPermissionStatus(locationPermissionsGranted, shouldShowPermissionRationale))
            }

            <span class="hljs-keyword">val</span> locationPermissions = arrayOf(
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION
            )

            <span class="hljs-keyword">val</span> locationPermissionLauncher = rememberLauncherForActivityResult(
                contract = ActivityResultContracts.RequestMultiplePermissions(),
                onResult = { permissions -&gt;
                    locationPermissionsGranted = permissions.values.reduce { acc, isPermissionGranted -&gt;
                        acc &amp;&amp; isPermissionGranted
                    }

                    <span class="hljs-keyword">if</span> (!locationPermissionsGranted) {
                        shouldShowPermissionRationale =
                            shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)
                    }
                    shouldDirectUserToApplicationSettings = !shouldShowPermissionRationale &amp;&amp; !locationPermissionsGranted
                    currentPermissionsStatus = decideCurrentPermissionStatus(locationPermissionsGranted, shouldShowPermissionRationale)
                })

            <span class="hljs-keyword">val</span> lifecycleOwner = LocalLifecycleOwner.current
            DisposableEffect(key1 = lifecycleOwner, effect = {
                <span class="hljs-keyword">val</span> observer = LifecycleEventObserver { _, event -&gt;
                    <span class="hljs-keyword">if</span> (event == Lifecycle.Event.ON_START &amp;&amp;
                        !locationPermissionsGranted &amp;&amp;
                        !shouldShowPermissionRationale) {
                        locationPermissionLauncher.launch(locationPermissions)
                    }
                }
                lifecycleOwner.lifecycle.addObserver(observer)
                onDispose {
                    lifecycleOwner.lifecycle.removeObserver(observer)
                    }
                }
            )

            <span class="hljs-keyword">val</span> scope = rememberCoroutineScope()
            <span class="hljs-keyword">val</span> snackbarHostState = remember { SnackbarHostState() }

            LocationPermissionsTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Scaffold(snackbarHost = {
                        SnackbarHost(hostState = snackbarHostState)
                    }) { contentPadding -&gt;
                        Column(modifier = Modifier.fillMaxSize(),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally){
                            Text(modifier = Modifier
                                .padding(contentPadding)
                                .fillMaxWidth(),
                                text = <span class="hljs-string">"Location Permissions"</span>,
                                textAlign = TextAlign.Center)
                            Spacer(modifier = Modifier.padding(<span class="hljs-number">20</span>.dp))
                            Text(modifier = Modifier
                                .padding(contentPadding)
                                .fillMaxWidth(),
                                text = <span class="hljs-string">"Current Permission Status: <span class="hljs-variable">$currentPermissionsStatus</span>"</span>,
                                textAlign = TextAlign.Center,
                                fontWeight = FontWeight.Bold
                            )
                        }
                        <span class="hljs-keyword">if</span> (shouldShowPermissionRationale) {
                            LaunchedEffect(<span class="hljs-built_in">Unit</span>) {
                                scope.launch {
                                    <span class="hljs-keyword">val</span> userAction = snackbarHostState.showSnackbar(
                                        message =<span class="hljs-string">"Please authorize location permissions"</span>,
                                        actionLabel = <span class="hljs-string">"Approve"</span>,
                                        duration = SnackbarDuration.Indefinite,
                                        withDismissAction = <span class="hljs-literal">true</span>
                                    )
                                    <span class="hljs-keyword">when</span> (userAction) {
                                        SnackbarResult.ActionPerformed -&gt; {
                                            shouldShowPermissionRationale = <span class="hljs-literal">false</span>
                                            locationPermissionLauncher.launch(locationPermissions)
                                        }
                                        SnackbarResult.Dismissed -&gt; {
                                            shouldShowPermissionRationale = <span class="hljs-literal">false</span>
                                        }
                                    }
                                }
                            }
                        }
                        <span class="hljs-keyword">if</span> (shouldDirectUserToApplicationSettings) {
                            openApplicationSettings()
                        }
                    }
                }
            }
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">areLocationPermissionsAlreadyGranted</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Boolean</span> {
        <span class="hljs-keyword">return</span> ContextCompat.checkSelfPermission(
            <span class="hljs-keyword">this</span>,
            Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">openApplicationSettings</span><span class="hljs-params">()</span></span> {
        Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts(<span class="hljs-string">"package"</span>, packageName, <span class="hljs-literal">null</span>)).also {
            startActivity(it)
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">decideCurrentPermissionStatus</span><span class="hljs-params">(locationPermissionsGranted: <span class="hljs-type">Boolean</span>,
                                              shouldShowPermissionRationale: <span class="hljs-type">Boolean</span>)</span></span>: String {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> (locationPermissionsGranted) <span class="hljs-string">"Granted"</span>
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (shouldShowPermissionRationale) <span class="hljs-string">"Rejected"</span>
        <span class="hljs-keyword">else</span> <span class="hljs-string">"Denied"</span>
    }
}
</code></pre>
<p>And this is how it looks like:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/location2.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>I have put all the logic in one file just for the purpose of this article. It is by no means the most esthetic and correct approach to handling the logic with requesting permissions. </p>
<p>You could easily refactor out the logic variables associated with holding the different state of the request for permissions to a view model class that is attached to this screen.</p>
<p>You can see all of the code described in this article by going to this project:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles/tree/master/LocationPermissions">https://github.com/TomerPacific/MediumArticles/tree/master/LocationPermissions</a></div>
<p>And if you would like to read more of my articles, you can go view them below:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles">https://github.com/TomerPacific/MediumArticles</a></div>
<p>I have also used this logic in an application that you can try out <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.scheduler">here</a>.</p>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create Tabs in Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ We’ve all seen it. We’ve all done it. Ain’t nothing like good ol’ tabs to organize content in a complex application. So how do we go about creating a tab layout in Jetpack Compose?  In this tutorial, we’ll go over all of the basics, but also show som... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/tabs-in-jetpack-compose/</link>
                <guid isPermaLink="false">66ba503cf8a814ef73b78bcc</guid>
                
                    <category>
                        <![CDATA[ Jetpack Compose ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Tue, 28 Feb 2023 19:09:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/02/chiara-f-MI8He1NWPWg-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>We’ve all seen it.</p>
<p>We’ve all done it.</p>
<p>Ain’t nothing like good ol’ tabs to organize content in a complex application. So how do we go about creating a tab layout in Jetpack Compose? </p>
<p>In this tutorial, we’ll go over all of the basics, but also show some things that are more advanced.</p>
<h2 id="heading-how-to-create-simple-tabs">How to Create Simple Tabs</h2>
<p>To create a tab layout, you need to start with a <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#TabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function1,kotlin.Function0,kotlin.Function0)"><strong>TabRow</strong></a>. This will be a container element that will hold your tabs.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-meta">@UiComposable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TabRow</span><span class="hljs-params">(
    selectedTabIndex: <span class="hljs-type">Int</span>,
    modifier: <span class="hljs-type">Modifier</span> = Modifier,
    backgroundColor: <span class="hljs-type">Color</span> = MaterialTheme.colors.primarySurface,
    contentColor: <span class="hljs-type">Color</span> = contentColorFor(backgroundColor)</span></span>,
    indicator: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> (tabPositions: List&lt;TabPosition&gt;) -&gt; <span class="hljs-built_in">Unit</span> = <span class="hljs-meta">@Composable</span> { tabPositions -&gt;
            TabRowDefaults.Indicator(
                Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
            )
        },
    divider: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> () -&gt; <span class="hljs-built_in">Unit</span> = <span class="hljs-meta">@Composable</span> {
            TabRowDefaults.Divider()
        },
    tabs: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> () -&gt; <span class="hljs-built_in">Unit</span>
): <span class="hljs-built_in">Unit</span>
</code></pre>
<ul>
<li><strong>selectedTabIndex</strong> indicates the index of the tab that is currently selected</li>
<li><strong>indicator</strong> represents the UI that indicates which tab is currently selected</li>
<li><strong>divider</strong> is a composable that is drawn at the bottom of the TabRow under the indicator</li>
<li>If you don’t have a need to custom style your tabs, you can use <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/TabRowDefaults"><strong>TabRowDefaults</strong></a> as it contains the default values and implementation used for TabRow (you can see it being used inside divider)</li>
</ul>
<p>Let’s see the usage of TabRow with an example. We will create a simple layout that will have three tabs:</p>
<ol>
<li>Home</li>
<li>About</li>
<li>Settings</li>
</ol>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TabScreen</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> tabIndex <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-number">0</span>) }

    <span class="hljs-keyword">val</span> tabs = listOf(<span class="hljs-string">"Home"</span>, <span class="hljs-string">"About"</span>, <span class="hljs-string">"Settings"</span>)

    Column(modifier = Modifier.fillMaxWidth()) {
        TabRow(selectedTabIndex = tabIndex) {
            tabs.forEachIndexed { index, title -&gt;
                Tab(text = { Text(title) },
                    selected = tabIndex == index,
                    onClick = { tabIndex = index }
                )
            }
        }
        <span class="hljs-keyword">when</span> (tabIndex) {
            <span class="hljs-number">0</span> -&gt; HomeScreen()
            <span class="hljs-number">1</span> -&gt; AboutScreen()
            <span class="hljs-number">2</span> -&gt; SettingsScreen()
        }
    }
}
</code></pre>
<p>A couple things to pay attention to:</p>
<ul>
<li>The TabRow composable holds inside of itself a <strong>Tab</strong> composable</li>
<li>After the TabRow composable, we have a when clause to handle what happens when each tab is clicked (in our specific case we are opening different screens)</li>
<li>We are using a variable called tabIndex to keep track of which Tab is selected</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/1.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Pretty bland, right?</p>
<p>Let’s spice things up with icons by using the icon attribute of the Tab composable.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TabScreen</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> tabIndex <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-number">0</span>) }

    <span class="hljs-keyword">val</span> tabs = listOf(<span class="hljs-string">"Home"</span>, <span class="hljs-string">"About"</span>, <span class="hljs-string">"Settings"</span>)

    Column(modifier = Modifier.fillMaxWidth()) {
        TabRow(selectedTabIndex = tabIndex) {
            tabs.forEachIndexed { index, title -&gt;
                Tab(text = { Text(title) },
                    selected = tabIndex == index,
                    onClick = { tabIndex = index },
                    icon = {
                        <span class="hljs-keyword">when</span> (index) {
                            <span class="hljs-number">0</span> -&gt; Icon(imageVector = Icons.Default.Home, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">1</span> -&gt; Icon(imageVector = Icons.Default.Info, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">2</span> -&gt; Icon(imageVector = Icons.Default.Settings, contentDescription = <span class="hljs-literal">null</span>)
                        }
                    }
                )
            }
        }
        <span class="hljs-keyword">when</span> (tabIndex) {
            <span class="hljs-number">0</span> -&gt; HomeScreen()
            <span class="hljs-number">1</span> -&gt; AboutScreen()
            <span class="hljs-number">2</span> -&gt; SettingsScreen()
        }
    }
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/1-1.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Looking better, but a question does arise: What if we have more tabs than the screen can show?</p>
<p>Luckily, the answer is simple.</p>
<p>There is an option to make our TabRow scrollable. Instead of using the TabRow element, you can use the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#ScrollableTabRow(kotlin.Int,androidx.compose.ui.Modifier,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp,kotlin.Function1,kotlin.Function0,kotlin.Function0)"><strong>ScrollableTabRow</strong></a> composable.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-meta">@UiComposable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ScrollableTabRow</span><span class="hljs-params">(
    selectedTabIndex: <span class="hljs-type">Int</span>,
    modifier: <span class="hljs-type">Modifier</span> = Modifier,
    backgroundColor: <span class="hljs-type">Color</span> = MaterialTheme.colors.primarySurface,
    contentColor: <span class="hljs-type">Color</span> = contentColorFor(backgroundColor)</span></span>,
    edgePadding: Dp = TabRowDefaults.ScrollableTabRowPadding,
    indicator: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> (tabPositions: List&lt;TabPosition&gt;) -&gt; <span class="hljs-built_in">Unit</span> = <span class="hljs-meta">@Composable</span> { tabPositions -&gt;
            TabRowDefaults.Indicator(
                Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
            )
        },
    divider: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> () -&gt; <span class="hljs-built_in">Unit</span> = <span class="hljs-meta">@Composable</span> {
            TabRowDefaults.Divider()
        },
    tabs: <span class="hljs-meta">@Composable</span> <span class="hljs-meta">@UiComposable</span> () -&gt; <span class="hljs-built_in">Unit</span>
): <span class="hljs-built_in">Unit</span>
</code></pre>
<p>So if we convert our example from above, we will get this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TabScreen</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> tabIndex <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-number">0</span>) }

    <span class="hljs-keyword">val</span> tabs = listOf(<span class="hljs-string">"Home"</span>, <span class="hljs-string">"About"</span>, <span class="hljs-string">"Settings"</span>, <span class="hljs-string">"More"</span>, <span class="hljs-string">"Something"</span>, <span class="hljs-string">"Everything"</span>)

    Column(modifier = Modifier.fillMaxWidth()) {
        ScrollableTabRow(selectedTabIndex = tabIndex) {
            tabs.forEachIndexed { index, title -&gt;
                Tab(text = { Text(title) },
                    selected = tabIndex == index,
                    onClick = { tabIndex = index },
                    icon = {
                        <span class="hljs-keyword">when</span> (index) {
                            <span class="hljs-number">0</span> -&gt; Icon(imageVector = Icons.Default.Home, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">1</span> -&gt; Icon(imageVector = Icons.Default.Info, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">2</span> -&gt; Icon(imageVector = Icons.Default.Settings, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">3</span> -&gt; Icon(imageVector = Icons.Default.Lock, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">4</span> -&gt; Icon(imageVector = Icons.Default.HeartBroken, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">5</span> -&gt; Icon(imageVector = Icons.Default.Star, contentDescription = <span class="hljs-literal">null</span>)
                        }
                    }
                )
            }
        }
        <span class="hljs-keyword">when</span> (tabIndex) {
            <span class="hljs-number">0</span> -&gt; HomeScreen()
            <span class="hljs-number">1</span> -&gt; AboutScreen()
            <span class="hljs-number">2</span> -&gt; SettingsScreen()
            <span class="hljs-number">3</span> -&gt; MoreScreen()
            <span class="hljs-number">4</span> -&gt; SomethingScreen()
            <span class="hljs-number">5</span> -&gt; EverythingScreen()
        }
    }
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/2.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-create-tabs-with-swiping-enabled">How to Create Tabs with Swiping Enabled</h2>
<p>Scrollable tabs are nice, but swiping between tabs is even better. Most users will feel that it's more intuitive to swipe between the tabs rather than clicking on each one. If you look at the documentation, you will notice that there are a few options to go with:</p>
<ol>
<li>The <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#(androidx.compose.ui.Modifier).swipeable(androidx.compose.material.SwipeableState,kotlin.collections.Map,androidx.compose.foundation.gestures.Orientation,kotlin.Boolean,kotlin.Boolean,androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Function2,androidx.compose.material.ResistanceConfig,androidx.compose.ui.unit.Dp)">swipeable</a> modifier</li>
<li>The <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/gestures/package-summary#(androidx.compose.ui.input.pointer.PointerInputScope).detectDragGestures(kotlin.Function1,kotlin.Function0,kotlin.Function0,kotlin.Function2)">detectDragGestures</a> modifier</li>
<li>The <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/gestures/package-summary#(androidx.compose.ui.Modifier).draggable(androidx.compose.foundation.gestures.DraggableState,androidx.compose.foundation.gestures.Orientation,kotlin.Boolean,androidx.compose.foundation.interaction.MutableInteractionSource,kotlin.Boolean,kotlin.coroutines.SuspendFunction2,kotlin.coroutines.SuspendFunction2,kotlin.Boolean)">draggable</a> modifier</li>
</ol>
<p>Not all of these will help us in achieving our goal, each one for its own reasons. If you don’t want to go through the “hassle” of doing things yourself, there is a library from Accompanist called <a target="_blank" href="https://google.github.io/accompanist/pager/#usage">pager</a> that you can use. It allows you to add the ability to either horizontally or vertically create a row/column that reacts to swipes.</p>
<p>Steps to implement it have been covered already and you can use the resources below to learn how to do it:</p>
<ul>
<li><a target="_blank" href="https://johncodeos.com/how-to-create-tabs-with-jetpack-compose/">https://johncodeos.com/how-to-create-tabs-with-jetpack-compose/</a></li>
<li><a target="_blank" href="https://www.rockandnull.com/jetpack-compose-swipe-pager/">https://www.rockandnull.com/jetpack-compose-swipe-pager/</a></li>
</ul>
<p>If you are like me and you like to do things for yourself and are up for getting your hands dirty, read on.</p>
<h2 id="heading-option-1-the-swipeable-modifier">Option 1: the <code>Swipeable</code> Modifier</h2>
<p>The first thing to know about the swipeable modifier is that it is annotated with the @<a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/ExperimentalMaterialApi"><strong>ExperimentalMaterialApi</strong></a>. This means that this API can change between versions of Jetpack Compose and that it isn’t stable. </p>
<p>Apart from that, we need to go over the mechanism that the swipeable modifier uses. It has 3 building blocks:</p>
<ol>
<li>A swipeable state – Denoting the current state and holding data about any on going swipe or swipe related animation.</li>
<li>Anchors – A map of values (Float based) restricting the swipe action from the minimum value to the maximum value. It maps anchor points to swipeable states.</li>
<li>Thresholds – A value denoting the difference between two known anchors.</li>
</ol>
<pre><code class="lang-kotlin"><span class="hljs-meta">@ExperimentalMaterialApi</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T : Any?&gt;</span> Modifier.<span class="hljs-title">swipeable</span><span class="hljs-params">(
    state: <span class="hljs-type">SwipeableState</span>&lt;<span class="hljs-type">T</span>&gt;,
    anchors: <span class="hljs-type">Map</span>&lt;<span class="hljs-type">Float</span>, T&gt;,
    orientation: <span class="hljs-type">Orientation</span>,
    enabled: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">true</span>,
    reverseDirection: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">false</span>,
    interactionSource: <span class="hljs-type">MutableInteractionSource</span>? = <span class="hljs-literal">null</span>,
    thresholds: (<span class="hljs-type">from</span>, <span class="hljs-type">to</span>) -&gt; <span class="hljs-type">ThresholdConfig</span> = { _, _ -&gt; FixedThreshold(<span class="hljs-number">56.</span>dp)</span></span> },
    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
    velocityThreshold: Dp = VelocityThreshold
): Modifier
</code></pre>
<p>Regardless of this API being experimental, it just isn’t meant to be used for the swiping gesture we are seeking. </p>
<p>You can. use this modifier for a switch button that the user can drag between on/off positions (as an example). But what would be our anchors in our example? How do we define the thresholds? The swipe a user performs cannot be constrained between two points. Therefore, we’ll let this one go and move on to detectDragGestures.</p>
<h2 id="heading-option-2-the-detectdraggestures-modifier">Option 2: the <code>detectDragGestures</code> Modifier</h2>
<p>As the name implies, this modifier detects the drag gesture, which can be quite similar to swiping.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">suspend</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> PointerInputScope.<span class="hljs-title">detectDragGestures</span><span class="hljs-params">(
    onDragStart: (<span class="hljs-type">Offset</span>) -&gt; <span class="hljs-type">Unit</span> = { },
    onDragEnd: () -&gt; <span class="hljs-type">Unit</span> = { },
    onDragCancel: () -&gt; <span class="hljs-type">Unit</span> = { },
    onDrag: (<span class="hljs-type">change</span>: <span class="hljs-type">PointerInputChange</span>, <span class="hljs-type">dragAmount</span>: <span class="hljs-type">Offset</span>) -&gt; <span class="hljs-type">Unit</span>
)</span></span>: <span class="hljs-built_in">Unit</span>
</code></pre>
<p>As you can see, the <strong>onDrag</strong> callback has two arguments:</p>
<ol>
<li><code>change</code> – of <code>PointerInputChange</code> type, denoting the change in pointer when dragging</li>
<li><code>dragAmount</code> – of <code>Offset</code> type, denoting the amount dragged in x,y values</li>
</ol>
<p>This callback is called when:</p>
<blockquote>
<p><em>“… waits for pointer down and touch stop in any direction and then calls <code>onDrag</code> for each drag event.”</em></p>
</blockquote>
<p>The upside to use this modifier instead of the draggable one is that it provides you with information about the change in both x and y coordinates.</p>
<p>The downside of it is that it isn’t going to offer a smooth and elegant solution for swiping. This is because of the amount of times the onDrag callback is triggered. </p>
<p>When a user performs a swipe gesture, the onDrag callback is triggered multiple times. This makes it harder to discern when the “drag” gesture has ended completely. </p>
<p>When experimenting with this, I saw the onDrag callback being triggered three times for each swipe gesture. This won’t be a good fit for our use case so let’s check out the draggable modifier.</p>
<h2 id="heading-option-3-the-draggable-modifier">Option 3: the <code>Draggable</code> Modifier</h2>
<p>Think of this modifier as the stripped down version of the one before. This one measures changes in the UI when the user performs a drag gesture in only one orientation (vertical/horizontal). Since we only care about horizontal swipes, this can be a good option.</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> Modifier.<span class="hljs-title">draggable</span><span class="hljs-params">(
    state: <span class="hljs-type">DraggableState</span>,
    orientation: <span class="hljs-type">Orientation</span>,
    enabled: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">true</span>,
    interactionSource: <span class="hljs-type">MutableInteractionSource</span>? = <span class="hljs-literal">null</span>,
    startDragImmediately: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">false</span>,
    onDragStarted: <span class="hljs-type">suspend</span> <span class="hljs-type">CoroutineScope</span>.(<span class="hljs-type">startedPosition</span>: <span class="hljs-type">Offset</span>) -&gt; <span class="hljs-type">Unit</span> = {},
    onDragStopped: <span class="hljs-type">suspend</span> <span class="hljs-type">CoroutineScope</span>.(<span class="hljs-type">velocity</span>: <span class="hljs-type">Float</span>) -&gt; <span class="hljs-type">Unit</span> = {},
    reverseDirection: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">false</span>
)</span></span>: Modifier
</code></pre>
<p>Here as well there is no similarity to the two other modifiers and we will point out the things to pay attention to:</p>
<ul>
<li><code>state</code> – Similar to the state in the swipeable modifier, only here we are talking about a drag motion.</li>
<li><code>onDragStarted</code> – A callback triggered when the drag motion has begun.</li>
<li><code>onDragStopped</code> – A callback triggered when the drag motion has ended.</li>
</ul>
<p>Unlike <strong><code>detectDragGestures</code></strong>, here <code>onDragStopped</code> is called once for every swipe gesture, making this modifier the best candidate for the job.</p>
<p>It’s implementation as a swipe gesture detector in our example is quite robust, so let’s start with some prerequisites:</p>
<ol>
<li>We will be saving the index of the tab currently being viewed in a view model class</li>
<li>This index will be of <code>MutableLiveData</code> so that our composables will be able to recompose when the value is changed</li>
<li>Each of our screens will add the <code>draggable</code> modifier to its layout</li>
<li>We will need to add the runtime-livedata library as we are going to use the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/runtime/livedata/package-summary#(androidx.lifecycle.LiveData).observeAsState(kotlin.Any)"><code>observeAsState</code></a> method.</li>
</ol>
<p>We will start with #4.</p>
<p>Go to your application’s build.gradle file and add the following dependency:</p>
<pre><code>implementation <span class="hljs-string">"androidx.compose.runtime:runtime-livedata:$compose_version"</span>
</code></pre><p>where <strong><code>$compose_version</code></strong> is the version of Jetpack Compose you are using.</p>
<p>We have also minimized our previous example to hold three screens instead of six, as the solution works for either case and there is no need to create extra boiler plate.</p>
<p>Below is the view model:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainViewModel</span></span>(application: Application) : AndroidViewModel(application) {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> _tabIndex: MutableLiveData&lt;<span class="hljs-built_in">Int</span>&gt; = MutableLiveData(<span class="hljs-number">0</span>)
    <span class="hljs-keyword">val</span> tabIndex: LiveData&lt;<span class="hljs-built_in">Int</span>&gt; = _tabIndex
    <span class="hljs-keyword">val</span> tabs = listOf(<span class="hljs-string">"Home"</span>, <span class="hljs-string">"About"</span>, <span class="hljs-string">"Settings"</span>)

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">updateTabIndexBasedOnSwipe</span><span class="hljs-params">(isSwipeToTheLeft: <span class="hljs-type">Boolean</span>)</span></span> {
        _tabIndex.value = <span class="hljs-keyword">when</span> (isSwipeToTheLeft) {
            <span class="hljs-literal">true</span> -&gt; Math.floorMod(_tabIndex.value!!.plus(<span class="hljs-number">1</span>), tabs.size)
            <span class="hljs-literal">false</span> -&gt; Math.floorMod(_tabIndex.value!!.minus(<span class="hljs-number">1</span>), tabs.size)
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">updateTabIndex</span><span class="hljs-params">(i: <span class="hljs-type">Int</span>)</span></span> {
        _tabIndex.value = i
    }

}
</code></pre>
<ul>
<li><strong><code>tabIndex</code></strong> is in charge of holding the currently selected index.</li>
<li><strong><code>index</code></strong> is the exposed tabIndex.</li>
<li><strong><code>tabs</code></strong> is the list of tab names.</li>
<li>The method <strong><code>updateTabIndexBasedOnSwipe</code></strong> is triggered when a swipe happens and performs the calculation of where to move the tabIndex to.</li>
</ul>
<p>Each screen is made up of the same layout:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">AboutScreen</span><span class="hljs-params">(viewModel: <span class="hljs-type">MainViewModel</span>)</span></span> {

    <span class="hljs-keyword">var</span> isSwipeToTheLeft <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-literal">false</span>) }
    <span class="hljs-keyword">val</span> dragState = rememberDraggableState(onDelta = { delta -&gt;
        isSwipeToTheLeft = delta &gt; <span class="hljs-number">0</span>
    })

    Column(modifier = Modifier.fillMaxSize().draggable(
        state = dragState,
        orientation = Orientation.Horizontal,
        onDragStarted = {  },
        onDragStopped = {
            viewModel.updateTabIndexBasedOnSwipe(isSwipeToTheLeft = isSwipeToTheLeft)
        }),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center) {
        Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
            Text(
                text = <span class="hljs-string">"About"</span>,
                textAlign = TextAlign.Center,
                fontSize = <span class="hljs-number">20</span>.sp,
                fontWeight = FontWeight.Bold
            )
        }
    }
}
</code></pre>
<ul>
<li><strong><code>isSwipeToTheLeft</code></strong> is a Boolean indicating the direction of the swipe.</li>
<li><strong><code>dragState</code></strong> holds the state of the drag being performed and updates isSwipeToTheLeft according to the delta.</li>
<li>When the callback <strong><code>onDragStopped</code></strong> is called, we are calling the exposed viewModel method updateTabIndexBasedOnSwipe.</li>
</ul>
<p>And finally, our <code>TabLayout</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TabLayout</span><span class="hljs-params">(viewModel: <span class="hljs-type">MainViewModel</span>)</span></span> {
    <span class="hljs-keyword">val</span> tabIndex = viewModel.tabIndex.observeAsState()
    Column(modifier = Modifier.fillMaxWidth()) {
        TabRow(selectedTabIndex = tabIndex.value!!) {
            viewModel.tabs.forEachIndexed { index, title -&gt;
                Tab(text = { Text(title) },
                    selected = tabIndex.value!! == index,
                    onClick = { viewModel.updateTabIndex(index) },
                    icon = {
                        <span class="hljs-keyword">when</span> (index) {
                            <span class="hljs-number">0</span> -&gt; Icon(imageVector = Icons.Default.Home, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">1</span> -&gt; Icon(imageVector = Icons.Default.Info, contentDescription = <span class="hljs-literal">null</span>)
                            <span class="hljs-number">2</span> -&gt; Icon(imageVector = Icons.Default.Settings, contentDescription = <span class="hljs-literal">null</span>)
                        }
                    }
                )
            }
        }

        <span class="hljs-keyword">when</span> (tabIndex.value) {
            <span class="hljs-number">0</span> -&gt; HomeScreen(viewModel = viewModel)
            <span class="hljs-number">1</span> -&gt; AboutScreen(viewModel = viewModel)
            <span class="hljs-number">2</span> -&gt; SettingsScreen(viewModel = viewModel)
        }
    }
}
</code></pre>
<ul>
<li>Notice that when a tab is selected, we are updating the currently selected tab in the <code>viewModel</code> with <strong><code>updateTabIndex</code></strong>.</li>
</ul>
<p>Putting it all together yields:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/2-1.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>A few words regarding what we have accomplished. You might have noticed that there is some boilerplate we are adding for each of our screens that results in repetition. Each screen is saving the state of the drag. </p>
<p>To improve on that, we can move the <code>draggableState</code> to the view model, like so:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainViewModel</span></span>(application: Application) : AndroidViewModel(application) {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> _tabIndex: MutableLiveData&lt;<span class="hljs-built_in">Int</span>&gt; = MutableLiveData(<span class="hljs-number">0</span>)
    <span class="hljs-keyword">val</span> tabIndex: LiveData&lt;<span class="hljs-built_in">Int</span>&gt; = _tabIndex
    <span class="hljs-keyword">val</span> tabs = listOf(<span class="hljs-string">"Home"</span>, <span class="hljs-string">"About"</span>, <span class="hljs-string">"Settings"</span>)

    <span class="hljs-keyword">var</span> isSwipeToTheLeft: <span class="hljs-built_in">Boolean</span> = <span class="hljs-literal">false</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> draggableState = DraggableState { delta -&gt;
        isSwipeToTheLeft= delta &gt; <span class="hljs-number">0</span>
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> _dragState = MutableLiveData&lt;DraggableState&gt;(draggableState)
    <span class="hljs-keyword">val</span> dragState: LiveData&lt;DraggableState&gt; = _dragState

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">updateTabIndexBasedOnSwipe</span><span class="hljs-params">()</span></span> {
        _tabIndex.value = <span class="hljs-keyword">when</span> (isSwipeToTheLeft) {
            <span class="hljs-literal">true</span> -&gt; Math.floorMod(_tabIndex.value!!.plus(<span class="hljs-number">1</span>), tabs.size)
            <span class="hljs-literal">false</span> -&gt; Math.floorMod(_tabIndex.value!!.minus(<span class="hljs-number">1</span>), tabs.size)
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">updateTabIndex</span><span class="hljs-params">(i: <span class="hljs-type">Int</span>)</span></span> {
        _tabIndex.value = i
    }

}
</code></pre>
<p>And that reduces the boilerplate a bit, since each screen now looks like:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">AboutScreen</span><span class="hljs-params">(viewModel: <span class="hljs-type">MainViewModel</span>)</span></span> {

    Column(modifier = Modifier.fillMaxSize().draggable(
        state = viewModel.dragState.value!!,
        orientation = Orientation.Horizontal,
        onDragStarted = {  },
        onDragStopped = {
            viewModel.updateTabIndexBasedOnSwipe()
        }),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center) {
        Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
            Text(
                text = <span class="hljs-string">"About"</span>,
                textAlign = TextAlign.Center,
                fontSize = <span class="hljs-number">20</span>.sp,
                fontWeight = FontWeight.Bold
            )
        }
    }
}
</code></pre>
<p>I hope this article gave you the necessary tools to create your own tabs UI in Jetpack Compose. </p>
<p>The example shown above can be found <a target="_blank" href="https://github.com/TomerPacific/MediumArticles/tree/master/JetpackComposeTabs">here</a>.</p>
<p>And if you would like to read other articles I have written, you can check them out <a target="_blank" href="https://github.com/TomerPacific/MediumArticles">here</a>.</p>
<p>References:</p>
<ul>
<li><a target="_blank" href="https://m3.material.io/components/tabs/overview">Material Design page about Tabs</a></li>
<li><a target="_blank" href="https://developer.android.com/jetpack/compose/touch-input/gestures">Gestures In Jetpack Compose</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Serialize Your Data in Kotlin and Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ Serialization is the process of transforming data that's in one format into another format that can be stored.  If you have ever worked with a database or fetching data from a server, this should all be familiar to you. If not, you have come to the r... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/serializing-your-data-in-kotlin/</link>
                <guid isPermaLink="false">66ba503a158e6c6a8cb8c7a5</guid>
                
                    <category>
                        <![CDATA[ Jetpack Compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kotlin ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Wed, 01 Feb 2023 21:19:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/01/fineas-anton-cnoMG2034k8-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Serialization is the process of transforming data that's in one format into another format that can be stored. </p>
<p>If you have ever worked with a database or fetching data from a server, this should all be familiar to you. If not, you have come to the right place. </p>
<p>In this tutorial, we will go over:</p>
<ul>
<li>How to setup serialization in a Jetpack Compose project</li>
<li>How to serialize a data class</li>
<li>How to de-serialize a data class</li>
</ul>
<p>You might be asking yourself, what’s so special about serialization in Jetpack Compose? In essence, there isn’t a lot of difference than with a regular Kotlin Android project. The only difference is in the setup.</p>
<h2 id="heading-how-to-set-everything-up">How to Set Everything Up</h2>
<p>Each version of Jetpack Compose corresponds with a version of Kotiln that it is compatible with. Each version of the kotlin-serialization library is also compatible with a specific version of Kotlin. So you need to make sure that each of the three parts in this tripod are compatible with one another.</p>
<p>How can you that?</p>
<p>Your first resource you'll want to consult is the <a target="_blank" href="https://developer.android.com/jetpack/androidx/releases/compose-kotlin">Compose to Kotlin Compatibility Map</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/1_5brVwILW54aNaFFimDF87Q.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here you can see which version of Jetpack Compose corresponds to which Kotlin version.</p>
<p>The second resource you will need is the <a target="_blank" href="https://github.com/Kotlin/kotlinx.serialization/releases">releases page</a> for the kotlin-serialization library. There you will find which library version is compatible with which Kotlin version.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/1_y6Ba1fROOcSSXXm-Nll4Ew.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Confused? 😕</p>
<p>Let’s illustrate this with an example:</p>
<ul>
<li>Your Jetpack Compose version is <strong>1.1.0</strong>.</li>
<li>Looking over the compatibility map, you see it is compatible with Kotlin version <strong>1.6.10</strong>.</li>
<li>Heading to the releases page of kotlin-serialization library, you see that the version of the kotlin-serialization library that you need to use is <strong>1.3.2</strong>.</li>
</ul>
<p>Head into your project level build.gradle file, and inside the buildscript object, in the dependencies section, put in classpath for the kotlin-serialization library with the version you need.</p>
<pre><code class="lang-kotlin">dependencies {
        ...
        classpath <span class="hljs-string">"org.jetbrains.kotlin:kotlin-serialization:X.Y.Z"</span>
 }
</code></pre>
<p>Then, head over to your application build.gradle file and do these two things:</p>
<ol>
<li>Add the <strong>id ‘org.jetbrains.kotlin.plugin.serialization’</strong> inside of the plugins at the top of the file:</li>
</ol>
<pre><code class="lang-kotlin">plugins {
   ...
   id <span class="hljs-string">'org.jetbrains.kotlin.plugin.serialization'</span>
}
</code></pre>
<ol start="2">
<li>At the bottom of the file, inside the dependencies section add <strong>implementation ‘org.jetbrains.kotlinx:kotlinx-serialization-json:X.Y.Z’</strong>:</li>
</ol>
<pre><code class="lang-kotlin">dependencies {
   ...
   implementation <span class="hljs-string">'org.jetbrains.kotlinx:kotlinx-serialization-json:X.Y.Z'</span>
}
</code></pre>
<p>Sync your project and you should be good to go.</p>
<p>Note that we are using the <strong>json</strong> format of the library, but there are other formats that are supported as well:</p>
<ul>
<li>Protocol Buffers</li>
<li>CBOR (Concise Binary Object Representation)</li>
<li>Properties</li>
<li>HOCON (Human Optimized Config Object Notation)</li>
</ul>
<blockquote>
<p>⚠️ If you encounter any errors, make sure the versions you put are correct</p>
</blockquote>
<h2 id="heading-how-to-build-your-data-class">How to Build Your Data Class</h2>
<p>In order to have something we can serialize and later de-serialize, we need to work with data classes. </p>
<p>Creating a data class is simple. If you are using Android Studio, just right click inside your project’s module and choose New Kotlin file. Enter your class name and then append the data keyword before it.</p>
<p>For the sake of this article, let's say we are working with an API that returns a list of users. Each user object has a range of attributes it can have (just to name a few):</p>
<ul>
<li>First name</li>
<li>Last name</li>
<li>Age</li>
<li>Birthdate</li>
<li>Id</li>
</ul>
<p>To make our data class serializable, all you need to do is add the <strong>@Serializable</strong> annotation above the class declaration.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Serializable</span>
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span></span>(
   <span class="hljs-keyword">val</span> firstName: String,
   <span class="hljs-keyword">val</span> lastName: String,
   <span class="hljs-keyword">val</span> age: <span class="hljs-built_in">Int</span>,
   <span class="hljs-keyword">val</span> birthdate: Date,
   <span class="hljs-keyword">val</span> id: <span class="hljs-built_in">Long</span>
)
</code></pre>
<p>Pretty nifty, right?</p>
<p>Well, there’s more.</p>
<p>The variable that will hold the user’s first name is written as firstName. That means that in the response from our server, it needs to return in a field with the same name. </p>
<p>Sometimes, in API responses, the keys are not written in camelCase, but rather in kebab_case. That would mean that the key for first name, might be first_name. In that case, we would have to write it out like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Serializable</span>
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span></span>(
   <span class="hljs-keyword">val</span> first_name: String,
   <span class="hljs-keyword">val</span> lastName: String,
   <span class="hljs-keyword">val</span> age: <span class="hljs-built_in">Int</span>,
   <span class="hljs-keyword">val</span> birthdate: Date,
   <span class="hljs-keyword">val</span> id: <span class="hljs-built_in">Long</span>
)
</code></pre>
<p>But that is not the <a target="_blank" href="https://kotlinlang.org/docs/coding-conventions.html">convention</a> for property names in Kotlin.</p>
<p>So what can we do?</p>
<p>We can use the <strong>@SerialName</strong> annotation. This allows us to mark what the name of the field will be from the response and then write anything as the property for it.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Serializable</span>
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span></span>(
   <span class="hljs-meta">@SerialName(<span class="hljs-meta-string">"first_name"</span>)</span>
   <span class="hljs-keyword">val</span> firstName: String,
   <span class="hljs-keyword">val</span> lastName: String,
   <span class="hljs-keyword">val</span> age: <span class="hljs-built_in">Int</span>,
   <span class="hljs-keyword">val</span> birthdate: Date,
   <span class="hljs-keyword">val</span> id: <span class="hljs-built_in">Long</span>
)
</code></pre>
<h2 id="heading-how-to-serialize-and-de-serialize">How to Serialize and De-Serialize</h2>
<p>Now that our data class is set up, let’s enjoy the fruits of our labor. Whenever we need to serialize our data class, we will use the <a target="_blank" href="https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/encode-to-string.html">Json.encodeToString</a> method:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> dataAsString: String = Json.encodeToString(user)
</code></pre>
<p>When we run the above line of code, we will get our data class in string form.</p>
<p>De-serializing our data is as simple as serializing it. We will use the <a target="_blank" href="https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/decode-from-string.html">Json.decodeFromString</a> method:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> user: UserModel = Json.decodeFromString&lt;UserModel&gt;(dataAsString)
</code></pre>
<blockquote>
<p>✋ Notice that we specified which type of data we want to de-serialize to with the type parameter ().</p>
</blockquote>
<p><img src="https://images.unsplash.com/photo-1600176842064-635fe81d2441?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDMwfHxyZW1vdGUlMjBjb250cm9sfGVufDB8fHx8MTY3NTAxODM0NQ&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Image" width="2000" height="1325" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@macroman?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"&gt;Immo Wegmann / &lt;a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm<em>campaign=api-credit)</em></p>
<p>Time for some extra credit.</p>
<p>Let’s say that in your data class you have a field that you don’t want to serialize. If we take our UserModel class, imagine that we want to have a user’s actual picture (bitmap).</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Serializable</span>
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span></span>(
   <span class="hljs-meta">@SerialName(<span class="hljs-meta-string">"first-name"</span>)</span>
   <span class="hljs-keyword">val</span> firstName: String,
   <span class="hljs-keyword">val</span> lastName: String,
   <span class="hljs-keyword">val</span> age: <span class="hljs-built_in">Int</span>,
   <span class="hljs-keyword">val</span> birthdate: Date,
   <span class="hljs-keyword">val</span> id: <span class="hljs-built_in">Long</span>,
   <span class="hljs-keyword">var</span> photo: Bitmap?
)
</code></pre>
<p>This is not something we will get from our API call, so how can we exclude it? Because if we don’t, our serialization will fail.</p>
<p>Here to our rescue is the <a target="_blank" href="https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/"><strong>@Transient</strong> annotation</a>.</p>
<pre><code>@Serializable
data <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span>(
   @<span class="hljs-title">SerialName</span>("<span class="hljs-title">first</span>-<span class="hljs-title">name</span>")
   <span class="hljs-title">val</span> <span class="hljs-title">firstName</span>: <span class="hljs-title">String</span>,
   <span class="hljs-title">val</span> <span class="hljs-title">lastName</span>: <span class="hljs-title">String</span>,
   <span class="hljs-title">val</span> <span class="hljs-title">age</span>: <span class="hljs-title">Int</span>,
   <span class="hljs-title">val</span> <span class="hljs-title">birthdate</span>: <span class="hljs-title">Date</span>,
   <span class="hljs-title">val</span> <span class="hljs-title">id</span>: <span class="hljs-title">Long</span>,
   @<span class="hljs-title">Transient</span>
   <span class="hljs-title">var</span> <span class="hljs-title">photo</span>: <span class="hljs-title">Bitmap</span>?
)</span>
</code></pre><p>This will exclude the marked field from being serialized and de-serialized.</p>
<ul>
<li>If you want to see a real life example of using serialization inside a project, you can check out a project I made <a target="_blank" href="https://medium.com/r?url=https%3A%2F%2Fgithub.com%2FTomerPacific%2Fmovies-presenter">here</a></li>
<li>And if you would like to read other articles I have written, you can go <a target="_blank" href="https://medium.com/r?url=https%3A%2F%2Fgithub.com%2FTomerPacific%2FMediumArticles">here</a></li>
<li>For more information about the kotlin-serialization library, you can go <a target="_blank" href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/basic-serialization.md#json-encoding">here</a></li>
</ul>
<p>Thank you for reading! Happy serializing.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Handle UI Events in Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ In this short and practical article, we will talk about how to handle UI events in Jetpack Compose. In the old system, we used OnClickListeners and other interfaces. In Compose, we can take full advantage of Kotlin’s Sealed Classes, Function Types an... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-handle-ui-events-in-jetpack-compose/</link>
                <guid isPermaLink="false">66d460c99f2bec37e2da066a</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Android Studio ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Jetpack Compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kotlin ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ryan Michael Kay ]]>
                </dc:creator>
                <pubDate>Tue, 16 Mar 2021 18:22:24 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/03/cat-4793068_1280-5.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this short and practical article, we will talk about how to handle UI events in Jetpack Compose.</p>
<p>In the old system, we used OnClickListeners and other interfaces. In Compose, we can take full advantage of Kotlin’s <strong>Sealed Classes</strong>, <strong>Function Types</strong> and <strong>Lambda Expressions</strong>.</p>
<p>If you do not know what a composable is, consider reading <a target="_blank" href="https://www.freecodecamp.org/news/jetpack-compose-beginner-tutorial-composables-recomposition/">this article which explains the fundamentals</a>.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/LrNPw1LQHEw" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-how-to-model-ui-events-with-a-sealed-class">How to Model UI Events with a Sealed Class</h2>
<p>First, we must learn what is meant by UI Events and how to model them with Sealed Classes.</p>
<p>I have described this same process for <a target="_blank" href="https://medium.com/swlh/simplify-your-ui-interactions-with-events-java-kotlin-any-language-5062c1b1e0e4">Java and Kotlin</a> (with the old view system) before, so I will keep this brief.</p>
<h3 id="heading-the-process">The Process</h3>
<p>For each screen or sub-screen of your UI, ask yourself this question: What are all the different ways which the user can interact with it?</p>
<p>Let's take an example from my first app built fully in compose, <a target="_blank" href="https://play.google.com/store/apps/details?id=com.bracketcove.graphsudoku">Graph Sudoku</a>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/graph_sudoku_small_screen.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Screenshot of a Sudoku Android App</em></p>
<p>The sealed class I use to represent the UI interactions of this screen looks like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">sealed</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ActiveGameEvent</span> </span>{
    <span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OnInput</span></span>(<span class="hljs-keyword">val</span> input: <span class="hljs-built_in">Int</span>) : ActiveGameEvent()
    <span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OnTileFocused</span></span>(<span class="hljs-keyword">val</span> x: <span class="hljs-built_in">Int</span>, 
    <span class="hljs-keyword">val</span> y: <span class="hljs-built_in">Int</span>) : ActiveGameEvent()
    <span class="hljs-keyword">object</span> OnNewGameClicked : ActiveGameEvent()
    <span class="hljs-keyword">object</span> OnStart : ActiveGameEvent()
    <span class="hljs-keyword">object</span> OnStop : ActiveGameEvent()
}
</code></pre>
<p>To explain briefly:</p>
<ul>
<li><p>OnInput represents a user touching an input button (like 0, 1, 2, 3, 4)</p>
</li>
<li><p>OnTileFocused represents a user selecting a tile (like the amber highlighted one)</p>
</li>
<li><p>OnNewGameClicked is self-explanatory</p>
</li>
<li><p>OnStart and OnStop are lifecycle events which my composables do not care about, but they are used in the Activity which acts as a Container for the composables</p>
</li>
</ul>
<p>Once you have your sealed class set up, you can now handle a wide variety of events using a single event handler function. Sometimes it might make more sense to have multiple event handler functions, so keep in mind that <strong>this approach must be adapted to your project's specific requirements</strong>.</p>
<h2 id="heading-how-to-connect-your-software-architecture">How to Connect Your Software Architecture</h2>
<p>What you have handling these events is totally up to you. Some people think that MVVM is the golden standard of software architectures, but it seems like more and more people are realizing that <strong>there is no single architecture which works best for every situation</strong>.</p>
<p>For Android with Compose, my current approach is to use a very 3rd party minimalist approach which typically has these things in each feature (screen):</p>
<ul>
<li><p>A (Presentation) Logic class <strong>as an event handler</strong></p>
</li>
<li><p>A ViewModel to store the data necessary to render the View (as the name implies)</p>
</li>
<li><p>An Activity which acts as a Container (not a god object)</p>
</li>
<li><p>Composables to form the View</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/model_view_whatever-3.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Model-View-Whatever</em></p>
<p>I do not care what you use as long as you are applying <a target="_blank" href="https://youtu.be/B_C41SF0KbI">separation of concerns</a>. This is how I arrived at this architecture, by simply asking what should and should not be put together in the same class.</p>
<p>Whether you want your ViewModel, a Fragment, or an Activity to be your event handler, all of them can be set up the same way: <strong>Function Types!</strong></p>
<p>Within your class of choice, set up an event handler function which accepts your sealed class as its argument:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ActiveGameLogic</span></span>(
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> container: ActiveGameContainer?,
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> viewModel: ActiveGameViewModel,
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> gameRepo: IGameRepository,
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> statsRepo: IStatisticsRepository,
    dispatcher: DispatcherProvider
) : BaseLogic&lt;ActiveGameEvent&gt;(dispatcher),
    CoroutineScope {
    <span class="hljs-comment">//...</span>
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onEvent</span><span class="hljs-params">(event: <span class="hljs-type">ActiveGameEvent</span>)</span></span> {
        <span class="hljs-keyword">when</span> (event) {
            <span class="hljs-keyword">is</span> ActiveGameEvent.OnInput -&gt; onInput(
                event.input,
                viewModel.timerState
            )
            ActiveGameEvent.OnNewGameClicked -&gt; onNewGameClicked()
            ActiveGameEvent.OnStart -&gt; onStart()
            ActiveGameEvent.OnStop -&gt; onStop()
            <span class="hljs-keyword">is</span> ActiveGameEvent.OnTileFocused -&gt; onTileFocused(event.x, event.y)
        }
    }
    <span class="hljs-comment">//...</span>
}
</code></pre>
<p>This approach is very organized and makes it easy to test every Unit in this 3rd party library free class through a single entry point.</p>
<p>However, we are not done yet. Naturally, we need a way to get a reference to this event handler function, <code>onEvent</code>, to our Composables. We can do this using a <strong>function reference</strong>:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ActiveGameActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>(), ActiveGameContainer {
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> logic: ActiveGameLogic

    <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)

        <span class="hljs-keyword">val</span> viewModel = ActiveGameViewModel()

        setContent {
            ActiveGameScreen(
                onEventHandler = logic::onEvent,
                viewModel
            )
        }

        logic = buildActiveGameLogic(<span class="hljs-keyword">this</span>, viewModel, applicationContext)
    }

      <span class="hljs-comment">//...</span>
}
</code></pre>
<p>I am sure some of you are wondering why I am using an Activity. You can ask me during a <a target="_blank" href="https://youtu.be/-xV8k-4UW50">livestream Q&amp;A sometime for a detailed answer</a>.</p>
<p>In short, Fragments appear to be a bit pointless with Compose with my approach to architecture (I do not use Jetpack Navigation), and there is nothing wrong with using Activities as a feature specific container. <strong>Just avoid writing god activities, basically.</strong></p>
<p>To be specific, the way you make a reference to a function in Kotlin, is by providing the <strong>class/interface name</strong> (or <strong>skip that if it is a Top-Level function</strong>), followed by <strong>two colons</strong>, and the <strong>name of the function without any arguments or brackets</strong>:</p>
<pre><code class="lang-pgsql">onEventHandler = logic::onEvent
</code></pre>
<h2 id="heading-how-to-replace-onclicklistener-with-jetpack-compose-onclick-modifier">How to Replace onClickListener With Jetpack Compose onClick Modifier</h2>
<p>With that stuff ready, we can look at how this works within the composable. Naturally, your root composable will need the event handler function as a parameter:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ActiveGameScreen</span><span class="hljs-params">(
    onEventHandler: (<span class="hljs-type">ActiveGameEvent</span>) -&gt; <span class="hljs-type">Unit</span>,
    viewModel: <span class="hljs-type">ActiveGameViewModel</span>
)</span></span> {
<span class="hljs-comment">//...</span>
}
</code></pre>
<p>It can be a bit tricky to get function type syntax correctly, but understand that this <strong>really is a reference to a function,</strong> which is not so different from a reference to a class.</p>
<p>Just as you should not build god objects, you should not build giant composables:</p>
<ol>
<li><p>Break your UI down into the <strong>smallest reasonable parts</strong></p>
</li>
<li><p>Wrap them in a composable function</p>
</li>
<li><p>For each composable which has a UI interaction associated with it, <strong>it must be given a reference to your event handler function</strong></p>
</li>
</ol>
<p>Here is a composable which represents the input buttons of the Sudoku app, which is given the event handler by reference:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">SudokuInputButton</span><span class="hljs-params">(
    onEventHandler: (<span class="hljs-type">ActiveGameEvent</span>) -&gt; <span class="hljs-type">Unit</span>,
    number: <span class="hljs-type">Int</span>
)</span></span> {
    Button(
        onClick = { onEventHandler.invoke(ActiveGameEvent.OnInput(number)) },
        modifier = Modifier
            .requiredSize(<span class="hljs-number">56</span>.dp)
            .padding(<span class="hljs-number">2</span>.dp)
    ) {
        Text(
            text = number.toString(),
            style = inputButton.copy(color = MaterialTheme.colors.onPrimary),
            modifier = Modifier.fillMaxSize()
        )
    }
}
</code></pre>
<p>To actually pass the event to the logic class, we must use the <code>invoke</code> function, which will accept arguments as per the function type definition (which accepts an <code>ActiveGameEvent</code> in this case).</p>
<p>At this point, you are ready to handle UI interaction events in Kotlin (compose or not) by taking full advantage of this beautiful and modern programming language.</p>
<p>If you liked this article, share it on social media and consider checking out the resources below to support an independent programmer and content creator.</p>
<h3 id="heading-social">Social</h3>
<p>You can find me on <a target="_blank" href="https://www.instagram.com/rkay301/">Instagram here</a> and on <a target="_blank" href="https://twitter.com/wiseAss301">Twitter here</a>.</p>
<h3 id="heading-here-are-some-of-my-tutorials-amp-courses">Here are some of my tutorials &amp; courses</h3>
<p><a target="_blank" href="https://www.youtube.com/channel/UCSwuCetC3YlO1Y7bqVW5GHg">https://youtube.com/wiseass</a> <a target="_blank" href="https://www.freecodecamp.org/news/author/ryan-michael-kay/">https://www.freecodecamp.org/news/author/ryan-michael-kay/</a> <a target="_blank" href="https://skl.sh/35IdKsj">https://skl.sh/35IdKsj</a> (introduction to Android with Android Studio)</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
