<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sanskar's Stuff]]></title><description><![CDATA[Sanskar's Stuff]]></description><link>https://blog.sanskar10100.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 16 Sep 2026 05:46:53 GMT</lastBuildDate><atom:link href="https://blog.sanskar10100.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[A note on Context Windows]]></title><description><![CDATA[In long agent conversations, Developers often lose track of how much context has been used. Tools like Claude Code or Codex have auto-compaction built in, but they compact too late. Opus 4.8 has a mil]]></description><link>https://blog.sanskar10100.dev/a-note-on-context-windows</link><guid isPermaLink="true">https://blog.sanskar10100.dev/a-note-on-context-windows</guid><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Fri, 03 Jul 2026 12:39:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/617d6f4ed55bde5cb66815ee/dd61f3c9-a1df-4827-8e06-b4af7f3baafc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In long agent conversations, Developers often lose track of how much context has been used. Tools like Claude Code or Codex have auto-compaction built in, but they compact too late. Opus 4.8 has a million token context window, but retrieval quality falls off after about 200k. Opus 4.7 was actually a huge regression in this, 4.8 has a lower drop off. The bigger effect though is the cost. This comes directly from the horse's mouth, longer context == more cost.</p>
<img src="https://cdn.hashnode.com/uploads/covers/617d6f4ed55bde5cb66815ee/484ab6e6-80bd-4b65-b183-d6a049903514.png" alt="" style="display:block;margin:0 auto" />

<p>A few things that have worked for me:</p>
<ul>
<li><p>Use <code>/context</code> or <code>/status</code> to see where you currently are.</p>
</li>
<li><p>Use <code>/statusline</code> to add context and more information to the bottom of your terminal</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/617d6f4ed55bde5cb66815ee/574eac92-888d-4b9c-b63f-b12ca1796ac3.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><code>/compact</code> often, in both Codex and Claude Code</p>
</li>
<li><p>Start new conversations frequently (like one per bugfix). Use the <code>/new</code> or <code>/clear</code> command</p>
</li>
<li><p>Keep a small <code>AGENTS.md</code>/<code>CLAUDE.md</code> file. Avoid repeating instructions.</p>
</li>
<li><p>Use fewer and smaller MCPs, skills and subagents. Every skill and subagent you have are injected into the initial prompt, inflating context. MCPs are loaded on-demand, but descriptions are still sent for the model to decide. By 3-4 turns you're already exceeding the optimal window. I have seen people use 20+ MCPs! Don't do that. Think about what needs to be scoped to the user vs the project.</p>
</li>
<li><p>Spawn subagents. They give a small, concise results to your main agent, preventing context inflation. Also better retrieval! Most agents spawn them without your intervention.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/617d6f4ed55bde5cb66815ee/624dea1a-6691-4d43-a816-8d1d4f6838e5.png" alt="" style="display:block;margin:0 auto" />]]></content:encoded></item><item><title><![CDATA[Drawing Custom Alerts on Top of Bottom Sheets in Jetpack Compose]]></title><description><![CDATA[Bottom Sheets have become ubiquitous in Mobile design across Android and iOS. If you’re using Jetpack Compose, you’ll likely implement them using either the ModalBottomSheet or the BottomSheetScaffold. The former is more used in my experience, so fur...]]></description><link>https://blog.sanskar10100.dev/drawing-custom-alerts-on-top-of-bottom-sheets-in-jetpack-compose</link><guid isPermaLink="true">https://blog.sanskar10100.dev/drawing-custom-alerts-on-top-of-bottom-sheets-in-jetpack-compose</guid><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Tue, 08 Oct 2024 06:26:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1728373547942/43d2595f-425a-4c42-b4a7-3ba12d1e2767.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://jetc.dev/issues/236.html"><img src="https://img.shields.io/badge/As_Seen_In-jetc.dev_Newsletter_Issue_%23236-blue?logo=Jetpack+Compose&amp;logoColor=white" alt="As Seen In - jetc.dev Newsletter Issue #236" /></a></p>
<p>Bottom Sheets have become ubiquitous in Mobile design across Android and iOS. If you’re using Jetpack Compose, you’ll likely implement them using either the <code>ModalBottomSheet</code> or the <code>BottomSheetScaffold</code>. The former is more used in my experience, so further references to the bottom sheets in this article will mean <code>ModalBottomSheet</code>. We’ll be using the Material 3 implementation for the component.</p>
<p>Assume a scenario where we want to display a bottom sheet, and upon some action, show a custom alert message at the top of the screen. This alert should appear on top of the sheet, but it’s not as trivial as it may seem. Let’s start with the custom alert.</p>
<h2 id="heading-custom-alert">Custom Alert</h2>
<p>I find the default Snackbar available in Material 3 to be quite restrictive. This is why we’ll design our own, using a <code>Popup</code> composable. It’s part of the Compose UI package and is available with or without Material implementation. From the docs,</p>
<blockquote>
<p>A popup is a floating container that appears on top of the current activity It is especially useful for non-modal UI surfaces that remain hidden until they are needed, for example floating menus like Cut/Copy/Paste.</p>
</blockquote>
<p>This seems suitable for our use case. Let’s make a custom alert that appears on the top, displays some text, and disappears after 2 seconds. We can use the following code for this purpose.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalComposeUiApi::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">Content</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> showAlert <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-string">""</span>) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(<span class="hljs-number">0x55242424</span>))
    ) {
        Button(
            onClick = { showAlert = <span class="hljs-string">"Hello, world!"</span> },
            modifier = Modifier.align(Alignment.Center)
        ) {
            Text(<span class="hljs-string">"Click me!"</span>)
        }
    }

    Popup(
        alignment = Alignment.TopCenter,
        properties = PopupProperties(
            dismissOnClickOutside = <span class="hljs-literal">false</span>,
            dismissOnBackPress = <span class="hljs-literal">false</span>,
            usePlatformDefaultWidth = <span class="hljs-literal">false</span>
        )
    ) {
        AnimatedVisibility(
            modifier = Modifier.fillMaxWidth(),
            visible = showAlert.isNotBlank(),
            enter = expandVertically(),
            exit = shrinkVertically()
        ) {
            LaunchedEffect(<span class="hljs-built_in">Unit</span>) {
                delay(<span class="hljs-number">2000</span>)
                showAlert = <span class="hljs-string">""</span>
            }

            Alert(message = showAlert)
        }
    }
}

<span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">Alert</span><span class="hljs-params">(message: <span class="hljs-type">String</span>)</span></span> {
    Text(
        text = message,
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = <span class="hljs-number">32</span>.dp)
            .shadow(<span class="hljs-number">5</span>.dp)
            .background(Color.White)
            .padding(vertical = <span class="hljs-number">16</span>.dp),
        textAlign = TextAlign.Center
    )
}
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Please note that the above code is very specific and unoptimized for the sake of brevity.</div>
</div>

<p>This results in the following UI being drawn:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728213827591/1f9e704b-dbe9-4791-9616-6c6a0e01e955.gif" alt class="image--center mx-auto" /></p>
<p>Good enough. Now, we can move on to the Bottom Sheet.</p>
<h2 id="heading-the-bottom-sheet">The Bottom Sheet</h2>
<p>Let’s create a bottom sheet that asks a user for their name and shows them a welcome alert.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalComposeUiApi::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">Content</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> showSheet <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-literal">false</span>) }

    <span class="hljs-keyword">if</span> (showSheet) {
        NameSheet(
            onDismiss = { showSheet = <span class="hljs-literal">false</span> },
            onNameInput = { name -&gt;
                <span class="hljs-comment">// TODO show alert</span>
            }
        )
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(<span class="hljs-number">0x55242424</span>))
    ) {
        Button(
            onClick = { showSheet = <span class="hljs-literal">true</span> },
            modifier = Modifier.align(Alignment.Center)
        ) {
            Text(<span class="hljs-string">"Show Sheet"</span>)
        }
    }
}

<span class="hljs-meta">@OptIn(ExperimentalMaterial3Api::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">NameSheet</span><span class="hljs-params">(
    onDismiss: () -&gt; <span class="hljs-type">Unit</span>,
    onNameInput: (<span class="hljs-type">String</span>) -&gt; <span class="hljs-type">Unit</span>
)</span></span> {
    ModalBottomSheet(
        onDismissRequest = onDismiss
    ) {
        <span class="hljs-keyword">var</span> name <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-string">""</span>) }
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text(<span class="hljs-string">"Hi!, What's your name?"</span>) },
            placeholder = { Text(<span class="hljs-string">"John Doe"</span>) },
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = <span class="hljs-number">16</span>.dp)
                .padding(bottom = <span class="hljs-number">8</span>.dp),
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Text,
                imeAction = ImeAction.Done
            ),
            keyboardActions = KeyboardActions(
                onDone = { onNameInput(name) }
            ),
            singleLine = <span class="hljs-literal">true</span>
        )
    }
}
</code></pre>
<p>Upon rendering, we find that the UI can be a bit better. The scrims don’t draw correctly on the system bar, and the navigation bar insets are not being consumed by the sheet. This is not ideal since apps on Android 15 draw content edge-to-edge.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728316106880/55e03095-ef5a-45a4-a687-a73b17b79227.jpeg" alt class="image--center mx-auto" /></p>
<p>This is a bug in the earlier versions of Material3. A fix was published in May with the release of Version <a target="_blank" href="https://developer.android.com/jetpack/androidx/releases/compose-material3#1.3.0-alpha06">1.3.0-alpha06</a> of Material 3 for Compose. Since 1.3.0 stable is available now, we will use that:</p>
<pre><code class="lang-kotlin">implementation(<span class="hljs-string">"androidx.compose.material3:material3:1.3.0"</span>)
</code></pre>
<p>Just switching the library version fixes the issue, and we get this now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728316132046/d7d96729-7283-473b-bf67-4fc9ff366945.jpeg" alt class="image--center mx-auto" /></p>
<p>Now, we just need to show the alert when the user inputs their name and hits the done IME action.</p>
<h2 id="heading-displaying-the-alert-with-bottom-sheet-open">Displaying the Alert with Bottom Sheet open</h2>
<p>Let’s now use the following code to display an alert when a user inputs their name and hits the done button on the keyboard.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">Content</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> showAlert <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-string">""</span>) }
    <span class="hljs-keyword">var</span> showSheet <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-literal">false</span>) }

    <span class="hljs-keyword">if</span> (showSheet) {
        NameSheet(
            onDismiss = { showSheet = <span class="hljs-literal">false</span> },
            onNameInput = { name -&gt;
                showAlert = <span class="hljs-string">"Hello, <span class="hljs-variable">$name</span>!"</span>
            }
        )
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(<span class="hljs-number">0x55242424</span>))
    ) {
        Button(
            onClick = { showSheet = <span class="hljs-literal">true</span> },
            modifier = Modifier.align(Alignment.Center)
        ) {
            Text(<span class="hljs-string">"Show Sheet"</span>)
        }
    }

    Popup(
        alignment = Alignment.TopCenter,
        properties = PopupProperties(
            dismissOnClickOutside = <span class="hljs-literal">false</span>,
            dismissOnBackPress = <span class="hljs-literal">false</span>,
            usePlatformDefaultWidth = <span class="hljs-literal">false</span>
        )
    ) {
        AnimatedVisibility(
            modifier = Modifier.fillMaxWidth(),
            visible = showAlert.isNotBlank(),
            enter = expandVertically(),
            exit = shrinkVertically()
        ) {
            LaunchedEffect(<span class="hljs-built_in">Unit</span>) {
                delay(<span class="hljs-number">2000</span>)
                showAlert = <span class="hljs-string">""</span>
            }

            Alert(message = showAlert)
        }
    }
}

<span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">Alert</span><span class="hljs-params">(message: <span class="hljs-type">String</span>)</span></span> {
    Text(
        text = message,
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = <span class="hljs-number">32</span>.dp)
            .shadow(<span class="hljs-number">5</span>.dp)
            .background(Color.White)
            .padding(vertical = <span class="hljs-number">16</span>.dp),
        textAlign = TextAlign.Center
    )
}

<span class="hljs-meta">@OptIn(ExperimentalMaterial3Api::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">NameSheet</span><span class="hljs-params">(
    onDismiss: () -&gt; <span class="hljs-type">Unit</span>,
    onNameInput: (<span class="hljs-type">String</span>) -&gt; <span class="hljs-type">Unit</span>
)</span></span> {
    ModalBottomSheet(
        onDismissRequest = onDismiss
    ) {
        <span class="hljs-keyword">var</span> name <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-string">""</span>) }
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text(<span class="hljs-string">"Hi!, What's your name?"</span>) },
            placeholder = { Text(<span class="hljs-string">"John Doe"</span>) },
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = <span class="hljs-number">16</span>.dp)
                .padding(bottom = <span class="hljs-number">8</span>.dp),
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Text,
                imeAction = ImeAction.Done
            ),
            keyboardActions = KeyboardActions(
                onDone = { onNameInput(name) }
            ),
            singleLine = <span class="hljs-literal">true</span>
        )
    }
}
</code></pre>
<p>Let’s see the output:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728312913279/e138b430-97f7-4d66-8848-074a79e1a833.gif" alt class="image--center mx-auto" /></p>
<p>We notice that the popup is rendered <strong>behind</strong> the Bottom Sheet’s scrim. This happens because the current implementation of the Bottom Sheet uses <code>Dialog</code> as the base component, which has a higher z-index. We can see the commit here:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728313070965/3b6b4bde-24e2-468f-9aa4-5326984b6176.png" alt class="image--center mx-auto" /></p>
<p>This is not the best UX, and we ideally want to draw the alert <strong>on top of</strong> the Bottom Sheet. Two solutions come to mind:</p>
<h2 id="heading-potential-solution-popup-inside-bottomsheet-content">Potential Solution: Popup inside BottomSheet content</h2>
<p>If we draw the <code>Popup</code> inside the Bottom Sheet’s content, it’ll be inside the <code>Dialog</code> in which the Bottom Sheet is displayed and will render on top of it. Let’s modify our code:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">Content</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> showSheet <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-literal">false</span>) }

    <span class="hljs-keyword">if</span> (showSheet) {
        NameSheet(onDismiss = { showSheet = <span class="hljs-literal">false</span> },)
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(<span class="hljs-number">0x55242424</span>))
    ) {
        Button(
            onClick = { showSheet = <span class="hljs-literal">true</span> },
            modifier = Modifier.align(Alignment.Center)
        ) {
            Text(<span class="hljs-string">"Show Sheet"</span>)
        }
    }
}

<span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">Alert</span><span class="hljs-params">(message: <span class="hljs-type">String</span>)</span></span> {
    Text(
        text = message,
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = <span class="hljs-number">32</span>.dp)
            .shadow(<span class="hljs-number">5</span>.dp)
            .background(Color.White)
            .padding(vertical = <span class="hljs-number">16</span>.dp),
        textAlign = TextAlign.Center
    )
}

<span class="hljs-meta">@OptIn(ExperimentalMaterial3Api::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">NameSheet</span><span class="hljs-params">(
    onDismiss: () -&gt; <span class="hljs-type">Unit</span>,
)</span></span> {
    <span class="hljs-keyword">var</span> showAlert <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-string">""</span>) }

    ModalBottomSheet(
        onDismissRequest = onDismiss
    ) {
        <span class="hljs-keyword">var</span> name <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-string">""</span>) }
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text(<span class="hljs-string">"Hi!, What's your name?"</span>) },
            placeholder = { Text(<span class="hljs-string">"John Doe"</span>) },
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = <span class="hljs-number">16</span>.dp)
                .padding(bottom = <span class="hljs-number">8</span>.dp),
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Text,
                imeAction = ImeAction.Done
            ),
            keyboardActions = KeyboardActions(
                onDone = { showAlert = <span class="hljs-string">"Hello, <span class="hljs-variable">$name</span>!"</span> }
            ),
            singleLine = <span class="hljs-literal">true</span>
        )

        Popup(
            alignment = Alignment.TopCenter,
            properties = PopupProperties(
                dismissOnClickOutside = <span class="hljs-literal">false</span>,
                dismissOnBackPress = <span class="hljs-literal">false</span>,
                usePlatformDefaultWidth = <span class="hljs-literal">false</span>
            )
        ) {
            AnimatedVisibility(
                modifier = Modifier.fillMaxWidth(),
                visible = showAlert.isNotBlank(),
                enter = expandVertically(),
                exit = shrinkVertically()
            ) {
                LaunchedEffect(<span class="hljs-built_in">Unit</span>) {
                    delay(<span class="hljs-number">2000</span>)
                    showAlert = <span class="hljs-string">""</span>
                }

                Alert(message = showAlert)
            }
        }
    }
}
</code></pre>
<p>This doesn’t quite work as intended, because now the Popup has the same container size as the Bottom Sheet, and does not render at the top of the screen (unless the Bottom Sheet is at full height):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728313411338/bcab56c8-a74e-4a8f-b68d-c8b76ff0fb97.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-actual-solution-using-dialog-to-display-the-alert">Actual Solution: Using dialog to display the alert</h2>
<p>This is a workaround I came up with recently: Instead of using a <code>Popup</code> to display the alert, we’ll switch to a <code>Dialog</code> to render it on top of the sheet. This is a bit more complicated since</p>
<ul>
<li><p>Dialogs intercept any clicks on views beneath them, so as long as the alert is visible, the user will not be able to interact with the UI. To solve this issue, we set some <code>WindowManager</code> flags to make the dialog not focusable, resulting in it allowing click passthrough.</p>
</li>
<li><p>The dialog cannot be a persistent part of the UI like the <code>Popup</code>. It must be added to the tree <strong>after</strong> the sheet so that it obtains a higher z-index.</p>
</li>
</ul>
<p>Let’s look at code that implements both of these scenarios:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">Content</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> showAlert <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-string">""</span>) }
    <span class="hljs-keyword">var</span> showSheet <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-literal">false</span>) }

    <span class="hljs-keyword">if</span> (showSheet) {
        NameSheet(
            onDismiss = { showSheet = <span class="hljs-literal">false</span> },
            onNameInput = { name -&gt;
                showAlert = <span class="hljs-string">"Hello, <span class="hljs-variable">$name</span>!"</span>
            }
        )
    }

    <span class="hljs-comment">// Screen content</span>
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(<span class="hljs-number">0x55242424</span>))
    ) {
        Button(
            onClick = { showSheet = <span class="hljs-literal">true</span> },
            modifier = Modifier.align(Alignment.Center)
        ) {
            Text(<span class="hljs-string">"Show Sheet"</span>)
        }
    }

    <span class="hljs-keyword">if</span> (showAlert.isNotBlank()) {
        Alert(showAlert) { showAlert = <span class="hljs-string">""</span> }
    }
}

<span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">Alert</span><span class="hljs-params">(
    message: <span class="hljs-type">String</span>,
    onDismiss: () -&gt; <span class="hljs-type">Unit</span>
)</span></span> {
    Dialog(
        onDismissRequest = onDismiss,
        properties = DialogProperties(
            dismissOnClickOutside = <span class="hljs-literal">false</span>,
            dismissOnBackPress = <span class="hljs-literal">false</span>,
            usePlatformDefaultWidth = <span class="hljs-literal">false</span>
        )
    ) {
        (LocalView.current.parent <span class="hljs-keyword">as</span> DialogWindowProvider).window.apply {
            setDimAmount(<span class="hljs-number">0f</span>)
            addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
            addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
        }

        AnimatedVisibility(
            modifier = Modifier
                .fillMaxSize()
                .padding(top = <span class="hljs-number">8</span>.dp)
                .padding(horizontal = <span class="hljs-number">16</span>.dp)
                .wrapContentHeight(Alignment.Top),
            visible = message.isNotBlank(),
            enter = expandVertically(),
            exit = shrinkVertically()
        ) {
            LaunchedEffect(<span class="hljs-built_in">Unit</span>) {
                delay(<span class="hljs-number">2000</span>)
                onDismiss()
            }

            Text(
                text = message,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = <span class="hljs-number">32</span>.dp)
                    .shadow(<span class="hljs-number">5</span>.dp)
                    .background(Color.White)
                    .padding(vertical = <span class="hljs-number">16</span>.dp),
                textAlign = TextAlign.Center
            )
        }
    }
}

<span class="hljs-meta">@OptIn(ExperimentalMaterial3Api::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">NameSheet</span><span class="hljs-params">(
    onDismiss: () -&gt; <span class="hljs-type">Unit</span>,
    onNameInput: (<span class="hljs-type">String</span>) -&gt; <span class="hljs-type">Unit</span>
)</span></span> {
    ModalBottomSheet(
        onDismissRequest = onDismiss
    ) {
        <span class="hljs-keyword">var</span> name <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-string">""</span>) }
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text(<span class="hljs-string">"Hi!, What's your name?"</span>) },
            placeholder = { Text(<span class="hljs-string">"John Doe"</span>) },
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = <span class="hljs-number">16</span>.dp)
                .padding(bottom = <span class="hljs-number">8</span>.dp),
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Text,
                imeAction = ImeAction.Done
            ),
            keyboardActions = KeyboardActions(
                onDone = { onNameInput(name) }
            ),
            singleLine = <span class="hljs-literal">true</span>
        )
    }
}
</code></pre>
<p>Since this is relatively complex, let’s walk over some bits that require further elucidation:</p>
<ul>
<li><pre><code class="lang-kotlin">    <span class="hljs-keyword">if</span> (showAlert.isNotBlank()) {
        Alert(showAlert) { showAlert = <span class="hljs-string">""</span> }
    }
</code></pre>
<p>  Earlier, our <code>Popup</code> was a consistent part of the UI tree, but as explained above, the dialog needs to be added <strong>after</strong> the sheet for it to appear higher.</p>
</li>
<li><pre><code class="lang-kotlin">    (LocalView.current.parent <span class="hljs-keyword">as</span> DialogWindowProvider).window.apply {
        setDimAmount(<span class="hljs-number">0f</span>)
        addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
        addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
    }
</code></pre>
<p>  Since we need to change the window properties, we obtain an instance through the <code>DialogWindowProvider</code> interface. Then, we set the dim amount to 0, so that the Dialog doesn’t draw any shadow behind it. Additionally, we set two layout flags that make the dialog not touchable or focusable, causing it to pass clicks to views beneath it, allowing interaction with the UI as long as the alert is visible.</p>
</li>
<li><pre><code class="lang-kotlin">    AnimatedVisibility(
        modifier = Modifier
            .fillMaxSize()
            .padding(top = <span class="hljs-number">8</span>.dp)
            .padding(horizontal = <span class="hljs-number">16</span>.dp)
            .wrapContentHeight(Alignment.Top),
</code></pre>
<p>  Since there isn’t a direct and easy method to provide the position of a dialog, we instead measure the content to take up the entire screen, and then only draw in the area that is required. Essentially, we’re actually rendering the dialog over all of the screen space, but only the alert is visible since we wrap it to the height of the content, which is the Text. This can be extended to have Images, Icons, etc.</p>
</li>
</ul>
<p>This implementation outputs the intended behavior:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728315072892/8eaadd44-41c7-4d74-a3f4-5cb90dfbb921.gif" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">There’s still a minor issue here with how the Animation is happening, but that can be adjusted by adding a slight delay, allowing the <code>Dialog</code> to be added to the tree first, and then for the animation to happen.</div>
</div>

<p>This largely satisfies the requirement we stated at the start of the article, and can be used as the global snackbar at the root of your app in conjunction with a <code>CompositionLocal</code> to provide access to the Snackbar stream. To read how you can do that, consult this post:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.linkedin.com/embed/feed/update/urn:li:share:7212518142042853377">https://www.linkedin.com/embed/feed/update/urn:li:share:7212518142042853377</a></div>
<p> </p>
<p>Thanks for reading, any feedback is welcome!</p>
]]></content:encoded></item><item><title><![CDATA[Automating Bluetooth Profile switching thanks to Copilot Chat]]></title><description><![CDATA[A task I completed that I wouldn't have (or at least, not without putting several hours into it) without Generative AI:
I use an Arch-based Linux distro called Endeavour OS. It's a great OS, but lately, I've been having this issue where my computer w...]]></description><link>https://blog.sanskar10100.dev/automating-bluetooth-profile-switching-thanks-to-copilot-chat</link><guid isPermaLink="true">https://blog.sanskar10100.dev/automating-bluetooth-profile-switching-thanks-to-copilot-chat</guid><category><![CDATA[Linux]]></category><category><![CDATA[github copilot]]></category><category><![CDATA[bluetooth]]></category><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Mon, 09 Oct 2023 20:20:18 GMT</pubDate><content:encoded><![CDATA[<p>A task I completed that I wouldn't have (or at least, not without putting several hours into it) without Generative AI:</p>
<p>I use an Arch-based Linux distro called Endeavour OS. It's a great OS, but lately, I've been having this issue where my computer wasn't recognizing my Bluetooth headset's mic. The only way to have calls with a mic was to plug in a wired headset.</p>
<p>After a bit of research, I found out about Bluetooth profiles. Briefly, a typical Bluetooth headset with a mic can operate in two profiles:</p>
<ol>
<li><p>A2DP: High audio quality, but no mic</p>
</li>
<li><p>HFP/HSP: Low audio quality, but can use mic for calls.</p>
</li>
</ol>
<p>Generally, an OS switches between these profiles depending on the task. When on a call, HFP/HSP is used. During music playback, A2DP is used. My computer wasn't switching between them, and that's why I wasn't able to use the mic.</p>
<p>I could change the profile either through the command line or through the GUI console. However, this was rather cumbersome to do before and after every call. So, I resorted to writing a script that can be run to switch the profiles. This though, would require extracting a lot of information from the console, and quite a bit of grep commands.</p>
<p>Since I'm not that big of an expert in shell programming, I used Copilot Chat from GitHub. I supplied it output of <code>pactl list cards</code>, and from there with a little bit of back and forth, we arrived at a script that can toggle profiles for any of my headsets.</p>
<p>Since I use KDE, I easily created a desktop icon with a dedicated keyboard shortcut and put it in my panel for easy access. Now until I find a permanent solution, this works wonderfully.</p>
<p>Without tools like ChatGPT or GitHub Copilot Chat, I would've never written the script and kept using the console to switch between profiles. Admittedly, I'd have gotten rather fast at it, but this wasn't the best solution.</p>
<p>This supplants my belief that Generative AI tools can augment developer productivity, by allowing us to venture into areas where we otherwise wouldn't. It's not like Copilot gave me the right commands in the first go. It took about 30 minutes of back and forth and someone with the knowledge of operating the terminal to arrive at the correct script, but it's a nice start!</p>
<p>I see this partnership becoming only productive in the future. Excited to see what it holds.</p>
<p>PS: The script in question:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="fa2eb78bcf431cec28dde509dd797288"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/sanskar10100/fa2eb78bcf431cec28dde509dd797288" class="embed-card">https://gist.github.com/sanskar10100/fa2eb78bcf431cec28dde509dd797288</a></div>]]></content:encoded></item><item><title><![CDATA[Context Receivers in Kotlin: An Example]]></title><description><![CDATA[Context Receivers are a new feature introduced in Kotlin 1.6.20. They're useful for receiving the context from the call-site when a function is called. To understand it and its utility, let's first take a look at a similar feature available in the la...]]></description><link>https://blog.sanskar10100.dev/context-receivers-in-kotlin-an-example</link><guid isPermaLink="true">https://blog.sanskar10100.dev/context-receivers-in-kotlin-an-example</guid><category><![CDATA[Android]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[kotlin-flow]]></category><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Sun, 26 Feb 2023 16:21:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677429541651/18536e99-44a4-42a6-a31a-7f7c6fdc4815.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Context Receivers are a new feature introduced in Kotlin 1.6.20. They're useful for receiving the <code>context</code> from the call-site when a function is called. To understand it and its utility, let's first take a look at a similar feature available in the language.</p>
<h3 id="heading-extension-functions">Extension Functions</h3>
<p>In Kotlin, Extension functions have proven to be an extremely useful feature. They can be used to extend the functionality of a class without inheriting it, and prove extremely useful when you have to define utility methods.</p>
<p>Say you wanted to write a utility method that reverses an integer. You could write something like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">reverse</span><span class="hljs-params">(number: <span class="hljs-type">Int</span>)</span></span>: <span class="hljs-built_in">Int</span> {
    <span class="hljs-keyword">var</span> sum = <span class="hljs-number">0</span>
    <span class="hljs-keyword">var</span> num = number
    <span class="hljs-keyword">while</span> (num != <span class="hljs-number">0</span>) {
        sum = (sum * <span class="hljs-number">10</span>) + (num % <span class="hljs-number">10</span>)
        num /= <span class="hljs-number">10</span>
    }
    <span class="hljs-keyword">return</span> sum
}

println(reverse(<span class="hljs-number">12345</span>))
</code></pre>
<p>With extension functions, you can define an additional method on the <code>Int</code> the class itself, and call that on any integer you'd like:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-built_in">Int</span>.<span class="hljs-title">reverse</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Int</span> {
    <span class="hljs-keyword">var</span> sum = <span class="hljs-number">0</span>
    <span class="hljs-keyword">var</span> num = <span class="hljs-keyword">this</span>
    <span class="hljs-keyword">while</span> (num != <span class="hljs-number">0</span>) {
        sum = (sum * <span class="hljs-number">10</span>) + (num % <span class="hljs-number">10</span>)
        num /= <span class="hljs-number">10</span>
    }
    <span class="hljs-keyword">return</span> sum
}

println(<span class="hljs-number">12345</span>.reverse())
</code></pre>
<p>When we're using Extension Functions, the object that it is being called upon is called the <strong>receiver.</strong></p>
<h3 id="heading-limitation-single-receiver">Limitation: Single Receiver</h3>
<p>With extension functions, we can only use a single receiver at a time. There are some use cases that warrant the usage of 2 or more receivers.</p>
<p>As an example, consider the case where we're using <a target="_blank" href="https://developer.android.com/kotlin/flow/stateflow-and-sharedflow"><code>StateFlow</code></a> instead of <code>LiveData</code> to make the UI reactive to state change. The correct method to collect a <code>StateFlow</code> in a <code>Fragment</code> looks like this:</p>
<pre><code class="lang-kotlin">viewLifecycleOwner.lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.state.collect {
            binding.toolbar.text = it.titleText
            <span class="hljs-comment">// ...</span>
        }
    }
}
</code></pre>
<p>This is rather verbose, and if the pattern is being used in several places, you'd obviously like to convert this to a utility function that'd make things simpler. A very simple definition is presented below:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> Fragment.<span class="hljs-title">collectStateFlow</span><span class="hljs-params">(body: <span class="hljs-type">suspend</span> <span class="hljs-type">CoroutineScope</span>.() -&gt; <span class="hljs-type">Unit</span>)</span></span> {
    viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            body()
        }
    }
}

<span class="hljs-comment">// In Fragment</span>
collectStateFlow {
    viewModel.state.collect {
        binding.toolbar.text = it.titleText
        <span class="hljs-comment">// ...</span>
    }
}
</code></pre>
<p>What if we wanted to take this a step further, and eliminate the additional line to collect the <code>StateFlow</code> too? We can define an extension function on <code>Flow</code> or <code>StateFlow</code> that can collect emissions while handling the lifecycle under the hood. <strong>Here's where we run into a problem.</strong> If we define an extension function on <code>StateFlow</code>, it can <em>only</em> <em>receive</em> the <code>StateFlow</code>. In other words, only methods that can be called on a StateFlow will be inside the function, since it'll have the context of the <code>StateFlow</code> it has been called on. Hence, we'll have to pass the Fragment as an argument to the function:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> StateFlow<span class="hljs-type">&lt;T&gt;</span>.<span class="hljs-title">collectWithLifecycle</span><span class="hljs-params">(fragment: <span class="hljs-type">Fragment</span>, block: (<span class="hljs-type">T</span>) -&gt; <span class="hljs-type">Unit</span>)</span></span> {
    fragment.viewLifecycleOwner.lifecycleScope.launch {
        fragment.repeatOnLifecycle(Lifecycle.State.CREATED) {
            <span class="hljs-keyword">this</span><span class="hljs-symbol">@collectWithLifecycle</span>.collect {
                block(it)
            }
        }
    }
}

<span class="hljs-comment">// In Fragment</span>
viewModel.state.collectWithLifecycle(<span class="hljs-keyword">this</span>) {
    binding.toolbar.text = it.titleText
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Now that's neat. Looks a lot like the <code>observe</code> method of a <code>LiveData</code> right?</p>
<h3 id="heading-context-receivers">Context Receivers</h3>
<p>Being programmers, we have a bad habit of trying to write clever code, so your mind naturally wonders: What if we could have the <code>Fragment</code> context just be implicitly available in the function body? That'd be great. And that's exactly where Context Receivers help us. For a good explanation of how they work, see <a target="_blank" href="https://kt.academy/article/fk-context-receivers">this</a>. In short, using context receiver syntax, we can have as many receivers for our function as we'd like, meaning we can get any scope we want. Consider this example:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">A</span> </span>{
    <span class="hljs-keyword">val</span> x = <span class="hljs-number">10</span>
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">B</span> </span>{
    <span class="hljs-keyword">val</span> y = <span class="hljs-number">20</span>
}

context(A, B)
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">printValues</span><span class="hljs-params">()</span></span> { 
    println(x)
    println(y)
}

<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    A().apply {
        with(B()) {
            printValues()
        }
    }
}
</code></pre>
<p>No need to pass arguments. We can access <code>x</code> and <code>y</code> inside <code>printValues</code> since when we call it, contexts of both classes <code>A,</code> and <code>B</code> are available, and that's seemingly transferred to our method.</p>
<h3 id="heading-best-version-of-our-code-using-context-receivers">Best version of our code using Context Receivers</h3>
<p>To use Context Receivers on Android, add this to the <code>kotlinOptions</code> block in your app-level <code>build.gradle</code> file:</p>
<pre><code class="lang-kotlin">freeCompilerArgs = [<span class="hljs-string">"-Xcontext-receivers"</span>]

<span class="hljs-comment">// Overall</span>
kotlinOptions {
    jvmTarget = <span class="hljs-string">'1.8'</span>
    freeCompilerArgs = [<span class="hljs-string">"-Xcontext-receivers"</span>]
}
</code></pre>
<p>Additionally, you need to be using <strong>Kotlin 1.6.20 or higher.</strong></p>
<p>Once we've enabled the feature, we can write a utility method like this:</p>
<pre><code class="lang-kotlin">context(Fragment)
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> StateFlow<span class="hljs-type">&lt;T&gt;</span>.<span class="hljs-title">collectWithLifecycle</span><span class="hljs-params">(block: (<span class="hljs-type">T</span>) -&gt; <span class="hljs-type">Unit</span>)</span></span> {
    viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.CREATED) {
            <span class="hljs-keyword">this</span><span class="hljs-symbol">@collectWithLifecycle</span>.collect {
                block(it)
            }
        }
    }
}

<span class="hljs-comment">// In Fragment</span>
viewModel.state.collectWithLifecycle {
    binding.toolbar.text = it.titleText
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>This eliminates all boilerplate we need to write while collecting a <code>StateFlow</code>.</p>
<blockquote>
<p><strong>Tip</strong>: Instead of <code>StateFlow&lt;T&gt;</code>, use <code>Flow&lt;T&gt;</code> to use with all <code>Flow</code> derivates such as <code>SharedFlow</code> and <code>StateFlow</code>.</p>
<hr />
<p><strong>Tip:</strong> To use with <code>Activity</code>, replace <code>Fragment</code> with <code>Activity</code> as the receiver in the code, and remove <code>viewLifecycleOwner</code> since it's not necessary with an <code>Activity</code>.</p>
<hr />
<p><strong>Tip:</strong> Compose already has an API for this. <a target="_blank" href="https://medium.com/androiddevelopers/consuming-flows-safely-in-jetpack-compose-cde014d0d5a3">See here</a>.</p>
</blockquote>
<p>Arrivederci!</p>
]]></content:encoded></item><item><title><![CDATA[Integrating Google Maps, Places API, and Reverse Geocoding with Jetpack Compose]]></title><description><![CDATA[Integrating Google maps into Android apps has been somewhat of a tough nut to crack for developers. With the advent of Jetpack Compose for building native UI on Android, the Google Maps team announced a Map SDK for Compose, available here. However, i...]]></description><link>https://blog.sanskar10100.dev/integrating-google-maps-places-api-and-reverse-geocoding-with-jetpack-compose</link><guid isPermaLink="true">https://blog.sanskar10100.dev/integrating-google-maps-places-api-and-reverse-geocoding-with-jetpack-compose</guid><category><![CDATA[places api]]></category><category><![CDATA[google maps]]></category><category><![CDATA[Android]]></category><category><![CDATA[geocoding]]></category><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Sun, 08 Jan 2023 16:51:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673197620660/70473860-a0ca-436d-800c-bd2c69b43dba.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<iframe src="https://androidweekly.net/issues/issue-553/badge" height="25px"></iframe>

<p>Integrating Google maps into Android apps has been somewhat of a tough nut to crack for developers. With the advent of Jetpack Compose for building native UI on Android, the Google Maps team announced a Map SDK for Compose, available <a target="_blank" href="https://github.com/googlemaps/android-maps-compose">here</a>. However, it is only a piece of the puzzle.</p>
<p>The SDK helps with drawing the map on the screen and gives you a very featureful <code>GoogleMap</code> composable with tons of customization, but if you are looking to build a full-fledged address selection screen akin to Ola, Uber, or Swiggy, you may need to integrate several APIs in addition to this.</p>
<p>The two most important are:</p>
<ol>
<li><p><a target="_blank" href="https://developers.google.com/maps/documentation/places/android-sdk/overview">Places API</a>, used to facilitate <strong>autocomplete</strong> and provide details for a place on the map.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673193812807/db423776-b40a-4ee3-bed8-5955161e4160.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><a target="_blank" href="https://developer.android.com/reference/android/location/Geocoder">Geocoding API</a>, used to convert between lat-long coordinates and the textual address for a point.</p>
</li>
</ol>
<p>Let's walk through their implementation.</p>
<h1 id="heading-our-goal">Our Goal</h1>
<p>We will build a Map screen where users can select their current location. They can do so either by moving the marker to their desired location or by searching for a place in the <code>TextField</code>. The finished screen will look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673195316045/128a1b07-3192-4083-aa30-8063291f8ba5.jpeg" alt class="image--center mx-auto" /></p>
<p>Let's get started.</p>
<h2 id="heading-1-google-maps-platform">1. Google Maps Platform</h2>
<p>To use the Google Maps platform, you must create a Google Cloud project, link it to a billing account, and enable the Google Maps SDK for Android.</p>
<blockquote>
<p>At the time of writing, Google Maps provides all projects with $200 credit that recurs monthly. That means that you likely won't be paying anything until you have a lot of users. Your billing account will only be charged if you spend more than $200 in a month.</p>
</blockquote>
<p>To do so, follow these steps:</p>
<ol>
<li><p>Create a Google Cloud Project and/or enable <strong>Maps SDK for Android, Places API,</strong> and <strong>Geocoding</strong> API. Extensive instructions and direct links are available <a target="_blank" href="https://developers.google.com/maps/documentation/android-sdk/cloud-setup">here</a></p>
</li>
<li><p>Obtain an API key by following the instructions <a target="_blank" href="https://developers.google.com/maps/documentation/android-sdk/get-api-key">here</a>. This API key will be used to authenticate your app with the Google Maps service.</p>
<blockquote>
<p><strong>It's recommended to restrict the API key to your app by following the instructions in the</strong> <a target="_blank" href="https://developers.google.com/maps/documentation/android-sdk/get-api-key#restrict_key"><strong>doc</strong></a><strong>. This prevents misuse of your API key if it's leaked.</strong></p>
</blockquote>
</li>
<li><p>Set up your app to use the Google Maps platform, and specify the API key in the manifest. Instructions <a target="_blank" href="https://developers.google.com/maps/documentation/android-sdk/config#step_2_set_up_the_sdk">here</a>.</p>
</li>
<li><p>By now, you should've added your API key to your <code>local.properties</code> name <code>MAPS_API_KEY</code>. We'll need the key later for interacting with the Places API, so create a buildConfigField for it. In your app-level <code>build.gradle</code>, add the following code inside the <code>defaultConfig</code> block:</p>
<pre><code class="lang-java"> Properties properties = <span class="hljs-keyword">new</span> Properties()
 <span class="hljs-keyword">if</span> (rootProject.file(<span class="hljs-string">"local.properties"</span>).exists()) {
     properties.load(
             rootProject
             .file(<span class="hljs-string">"local.properties"</span>)
             .newDataInputStream()
     )
 }

 buildConfigField(
     <span class="hljs-string">"String"</span>,
     <span class="hljs-string">"MAPS_API_KEY"</span>,
     properties.getProperty(<span class="hljs-string">"MAPS_API_KEY"</span>)
 )
</code></pre>
<p> This will create a new BuildConfig field from the key that you have defined in your <code>local.properties</code> file, which can be used anywhere in the app by referencing <code>BuildConfig.MAPS_API_KEY</code>.</p>
</li>
</ol>
<h2 id="heading-2-googlemap-composable">2. GoogleMap Composable</h2>
<p>To display a Google Map in your Compose-based Android app, you can use the <a target="_blank" href="https://github.com/googlemaps/android-maps-compose">SDK for compose</a>. It wraps an AndroidView under the hood and gives you several good abstractions that'll help you write idiomatic Compose code. Instructions on integrating the SDK are provided via the <a target="_blank" href="https://github.com/googlemaps/android-maps-compose/blob/main/README.md">README</a> of the project.</p>
<p>We want our map to initially be focused on the user's current location. To obtain the current location, we'll need</p>
<ol>
<li><p><code>ACCESS_FINE_LOCATION</code> permission</p>
</li>
<li><p>Location enabled in the settings</p>
</li>
</ol>
<p>To represent all these states, we can construct a sealed class:</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">LocationState</span> </span>{
    <span class="hljs-keyword">object</span> NoPermission: LocationState()
    <span class="hljs-keyword">object</span> LocationDisabled: LocationState()
    <span class="hljs-keyword">object</span> LocationLoading: LocationState()
    <span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocationAvailable</span></span>(<span class="hljs-keyword">val</span> location: LatLng): LocationState()
    <span class="hljs-keyword">object</span> Error: LocationState()
}
</code></pre>
<p>This is an extremely simplified version. A production app will have more complex state interactions.</p>
<p>Now, we'll build our <code>ViewModel</code> for the screen, which will act as the state holder, containing an instance of the above-defined sealed class. It'll also be responsible for fetching and updating the current location. To do so, we'll use the <code>FusedLocationProviderClient</code>. You can read more about it <a target="_blank" href="https://developer.android.com/training/location/retrieve-current">here</a>. Currently, our <code>ViewModel</code> looks like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocationViewModel</span> : <span class="hljs-type">ViewModel</span></span>() {
    <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> fusedLocationClient: FusedLocationProviderClient

    <span class="hljs-keyword">var</span> locationState <span class="hljs-keyword">by</span> mutableStateOf&lt;LocationState&gt;(LocationState.NoPermission)

    <span class="hljs-meta">@SuppressLint(<span class="hljs-meta-string">"MissingPermission"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getCurrentLocation</span><span class="hljs-params">()</span></span> {
        locationState = LocationState.LocationLoading
        fusedLocationClient
            .getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, <span class="hljs-literal">null</span>)
            .addOnSuccessListener { location -&gt;
                locationState = <span class="hljs-keyword">if</span> (location == <span class="hljs-literal">null</span> &amp;&amp; locationState !<span class="hljs-keyword">is</span> LocationState.LocationAvailable) {
                    LocationState.Error
                } <span class="hljs-keyword">else</span> {
                    LocationState.LocationAvailable(LatLng(location.latitude, location.longitude))
                }
            }
    }
}
</code></pre>
<p>Note that the <code>fusedLocationClient</code> is a <code>lateinit</code> object, so we'll need to instantiate it in the fragment's <code>onCreate</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
    <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)

    viewModel.fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity())
}
</code></pre>
<p>Now, let's define a couple of self-explanatory utility functions in the fragment (they can also be made extension functions or stored separately in a util file with the activity being an argument):</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">requestLocationEnable</span><span class="hljs-params">()</span></span> {
    activity?.let {
        <span class="hljs-keyword">val</span> locationRequest = LocationRequest.create()
        <span class="hljs-keyword">val</span> builder = LocationSettingsRequest
            .Builder()
            .addLocationRequest(locationRequest)
        <span class="hljs-keyword">val</span> task = LocationServices
            .getSettingsClient(it)
            .checkLocationSettings(builder.build())
            .addOnSuccessListener {
                <span class="hljs-keyword">if</span> (it.locationSettingsStates?.isLocationPresent == <span class="hljs-literal">true</span>) {
                    viewModel.getCurrentLocation()
                }
            }
            .addOnFailureListener {
                <span class="hljs-keyword">if</span> (it <span class="hljs-keyword">is</span> ResolvableApiException) {
                    <span class="hljs-keyword">try</span> {
                        it.startResolutionForResult(requireActivity(), <span class="hljs-number">999</span>)
                    } <span class="hljs-keyword">catch</span> (e : IntentSender.SendIntentException) {
                        e.printStackTrace()
                    }
                }
            }

    }
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">locationEnabled</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Boolean</span> {
    <span class="hljs-keyword">val</span> locationManager = requireActivity().getSystemService(Context.LOCATION_SERVICE) <span class="hljs-keyword">as</span> LocationManager
    <span class="hljs-keyword">return</span> LocationManagerCompat.isLocationEnabled(locationManager)
}
</code></pre>
<p>With our states and utility methods defined, we finally move to our composable. We can use the <a target="_blank" href="https://google.github.io/accompanist/permissions/">Permissions</a> Library from the Accompanist collection to request location permission.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> locationPermissionState = rememberMultiplePermissionsState(
    listOf(
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_FINE_LOCATION
    )
)
</code></pre>
<p>Then we can add a <code>LaunchedEffect</code> to update the state when the permission state changes. If they are granted, we check for the location is enabled, and if that's also enabled, we ask the <code>ViewModel</code> to get the current location.</p>
<pre><code class="lang-kotlin">LaunchedEffect(locationPermissionState.allPermissionsGranted) {
    <span class="hljs-keyword">if</span> (locationPermissionState.allPermissionsGranted) {
        <span class="hljs-keyword">if</span> (locationEnabled()) {
            viewModel.getCurrentLocation()
        } <span class="hljs-keyword">else</span> {
            viewModel.locationState = LocationState.LocationDisabled
        }
    }
}
</code></pre>
<p>Lastly, we define what UI will be rendered on basis of the location state:</p>
<pre><code class="lang-kotlin">AnimatedContent(
    viewModel.locationState
) { state -&gt;
    <span class="hljs-keyword">when</span> (state) {
        <span class="hljs-keyword">is</span> LocationState.NoPermission -&gt; {
            Column {
                Text(<span class="hljs-string">"We need location permission to continue"</span>)
                Button(onClick = { locationPermissionState.launchMultiplePermissionRequest() }) {
                    Text(<span class="hljs-string">"Request permission"</span>)
                }
            }
        }
        <span class="hljs-keyword">is</span> LocationState.LocationDisabled -&gt; {
            Column {
                Text(<span class="hljs-string">"We need location to continue"</span>)
                Button(onClick = { requestLocationEnable() }) {
                    Text(<span class="hljs-string">"Enable location"</span>)
                }
            }
        }
        <span class="hljs-keyword">is</span> LocationState.LocationLoading -&gt; {
            Text(<span class="hljs-string">"Loading Map"</span>)
        }
        <span class="hljs-keyword">is</span> LocationState.Error -&gt; {
            Column {
                Text(<span class="hljs-string">"Error fetching your location"</span>)
                Button(onClick = { viewModel.getCurrentLocation() }) {
                    Text(<span class="hljs-string">"Retry"</span>)
                }
            }
        }
        <span class="hljs-keyword">is</span> LocationState.LocationAvailable -&gt; {
            <span class="hljs-keyword">val</span> cameraPositionState = rememberCameraPositionState {
                position = CameraPosition.fromLatLngZoom(state.location, <span class="hljs-number">15f</span>)
            }
            <span class="hljs-keyword">val</span> mapUiSettings <span class="hljs-keyword">by</span> remember { mutableStateOf(MapUiSettings()) }
            <span class="hljs-keyword">val</span> mapProperties <span class="hljs-keyword">by</span> remember { mutableStateOf(MapProperties(isMyLocationEnabled = <span class="hljs-literal">true</span>)) }

            GoogleMap(
                modifier = Modifier.fillMaxSize(),
                cameraPositionState = cameraPositionState,
                uiSettings = mapUiSettings,
                properties = mapProperties
            )
        }
    }
}
</code></pre>
<p>In the <code>GoogleMap</code> composable, we can see that it fills the entire screen and takes a Camera Position State, which is responsible for what the user sees on the screen. In our code, the position is set to the user's location that's been obtained through the <code>fusedLocationProvider</code>. It also takes a <code>MapUiSettings</code> object, where you can tweak the visibility of elements like zoom buttons and my location button. Additionally, in <code>MapProperties</code> you can define attributes like the map type, style, and whether the user location is enabled.</p>
<p>Our complete fragment looks like this at the moment:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocationFragment</span> : <span class="hljs-type">Fragment</span></span>() {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> viewModel <span class="hljs-keyword">by</span> viewModels&lt;LocationViewModel&gt;()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreateView</span><span class="hljs-params">(
        inflater: <span class="hljs-type">LayoutInflater</span>,
        container: <span class="hljs-type">ViewGroup</span>?,
        savedInstanceState: <span class="hljs-type">Bundle</span>?
    )</span></span>: View? {
        <span class="hljs-keyword">return</span> ComposeView(requireContext()).apply {
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                LocationScreen()
            }
        }
    }

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

        viewModel.fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity())
    }

    <span class="hljs-meta">@OptIn(ExperimentalPermissionsApi::class, ExperimentalAnimationApi::class)</span>
    <span class="hljs-meta">@Composable</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">LocationScreen</span><span class="hljs-params">(modifier: <span class="hljs-type">Modifier</span> = Modifier)</span></span> {
        <span class="hljs-keyword">val</span> locationPermissionState = rememberMultiplePermissionsState(
            listOf(
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION
            )
        )

        LaunchedEffect(locationPermissionState.allPermissionsGranted) {
            <span class="hljs-keyword">if</span> (locationPermissionState.allPermissionsGranted) {
                <span class="hljs-keyword">if</span> (locationEnabled()) {
                    viewModel.getCurrentLocation()
                } <span class="hljs-keyword">else</span> {
                    viewModel.locationState = LocationState.LocationDisabled
                }
            }
        }

        AnimatedContent(
            viewModel.locationState
        ) { state -&gt;
            <span class="hljs-keyword">when</span> (state) {
                <span class="hljs-keyword">is</span> LocationState.NoPermission -&gt; {
                    Column {
                        Text(<span class="hljs-string">"We need location permission to continue"</span>)
                        Button(onClick = { locationPermissionState.launchMultiplePermissionRequest() }) {
                            Text(<span class="hljs-string">"Request permission"</span>)
                        }
                    }
                }
                <span class="hljs-keyword">is</span> LocationState.LocationDisabled -&gt; {
                    Column {
                        Text(<span class="hljs-string">"We need location to continue"</span>)
                        Button(onClick = { requestLocationEnable() }) {
                            Text(<span class="hljs-string">"Enable location"</span>)
                        }
                    }
                }
                <span class="hljs-keyword">is</span> LocationState.LocationLoading -&gt; {
                    Text(<span class="hljs-string">"Loading Map"</span>)
                }
                <span class="hljs-keyword">is</span> LocationState.Error -&gt; {
                    Column {
                        Text(<span class="hljs-string">"Error fetching your location"</span>)
                        Button(onClick = { viewModel.getCurrentLocation() }) {
                            Text(<span class="hljs-string">"Retry"</span>)
                        }
                    }
                }
                <span class="hljs-keyword">is</span> LocationState.LocationAvailable -&gt; {
                    <span class="hljs-keyword">val</span> cameraPositionState = rememberCameraPositionState {
                        position = CameraPosition.fromLatLngZoom(state.location, <span class="hljs-number">15f</span>)
                    }
                    <span class="hljs-keyword">val</span> mapUiSettings <span class="hljs-keyword">by</span> remember { mutableStateOf(MapUiSettings()) }
                    <span class="hljs-keyword">val</span> mapProperties <span class="hljs-keyword">by</span> remember { mutableStateOf(MapProperties(isMyLocationEnabled = <span class="hljs-literal">true</span>)) }

                    GoogleMap(
                        modifier = Modifier.fillMaxSize(),
                        cameraPositionState = cameraPositionState,
                        uiSettings = mapUiSettings,
                        properties = mapProperties
                    )
                }
            }
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">locationEnabled</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Boolean</span> {
        <span class="hljs-keyword">val</span> locationManager = requireActivity().getSystemService(Context.LOCATION_SERVICE) <span class="hljs-keyword">as</span> LocationManager
        <span class="hljs-keyword">return</span> LocationManagerCompat.isLocationEnabled(locationManager)
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">requestLocationEnable</span><span class="hljs-params">()</span></span> {
        activity?.let {
            <span class="hljs-keyword">val</span> locationRequest = LocationRequest.create()
            <span class="hljs-keyword">val</span> builder = LocationSettingsRequest
                .Builder()
                .addLocationRequest(locationRequest)
            <span class="hljs-keyword">val</span> task = LocationServices
                .getSettingsClient(it)
                .checkLocationSettings(builder.build())
                .addOnSuccessListener {
                    <span class="hljs-keyword">if</span> (it.locationSettingsStates?.isLocationPresent == <span class="hljs-literal">true</span>) {
                        viewModel.getCurrentLocation()
                    }
                }
                .addOnFailureListener {
                    <span class="hljs-keyword">if</span> (it <span class="hljs-keyword">is</span> ResolvableApiException) {
                        <span class="hljs-keyword">try</span> {
                            it.startResolutionForResult(requireActivity(), <span class="hljs-number">999</span>)
                        } <span class="hljs-keyword">catch</span> (e : IntentSender.SendIntentException) {
                            e.printStackTrace()
                        }
                    }
                }

        }
    }
}
</code></pre>
<p>And our app looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673182553752/82626295-7bb2-4c98-ad36-ecdf98bfaf4a.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-lets-add-a-marker">Let's add a marker</h3>
<p>The <code>GoogleMap</code> composable has a content parameter, so we can pass it more composables that it'll render on the surface. One such Composable is <code>Marker</code>. Here's how you can use it:</p>
<pre><code class="lang-kotlin">GoogleMap(
    modifier = Modifier.fillMaxSize(),
    cameraPositionState = cameraPositionState,
    uiSettings = mapUiSettings,
    properties = mapProperties
) {
    Marker(
        state = rememberMarkerState(position = state.location)
    )
}
</code></pre>
<p>This code emits a marker that's always centered on the map since it uses the map's camera position to derive its own position. The user can move the map to center the marker anywhere they'd like. You can also add attributes like title, snippets and even configure onClick behavior.</p>
<h2 id="heading-3-places-api">3. Places API</h2>
<p>We use the Places API to provide autocomplete in textfields. Our intention is to achieve UI like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673192571631/4dd87c85-e5af-4010-b547-8448d5dc53b2.gif" alt class="image--center mx-auto" /></p>
<p>Here, the user can search for any place and select a result from the autocomplete. Upon selection, the map moves to the place as well.</p>
<p><strong>Prerequisites:</strong></p>
<p>To use the places API, first ensure it's enabled in the <a target="_blank" href="https://console.cloud.google.com/apis/library/places-backend.googleapis.com">Google Cloud Console</a>.</p>
<p>Afterward, add this library to your app-level <code>build.gradle</code>:</p>
<pre><code class="lang-kotlin">dependencies {
    implementation <span class="hljs-string">'com.google.android.libraries.places:places:3.0.0'</span>
}
</code></pre>
<p>That's it, we're now ready to use the Places API.</p>
<p>It's time to add a TextField to our composable so that the user can search for places. We can add one to the bottom of the map using a <code>Box</code>. See the code below:</p>
<pre><code class="lang-kotlin">Box(
    modifier = Modifier.fillMaxSize()
) {
    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState,
        uiSettings = mapUiSettings,
        properties = mapProperties,
        onMapClick = {
            scope.launch {
                cameraPositionState.animate(CameraUpdateFactory.newLatLng(it))
            }
        }
    )

    Surface(
        modifier = Modifier
            .align(Alignment.BottomCenter)
            .padding(<span class="hljs-number">8</span>.dp)
            .fillMaxWidth(),
        color = Color.White,
        shape = RoundedCornerShape(<span class="hljs-number">8</span>.dp)
    ) {
        Column(
            modifier = Modifier
                .padding(<span class="hljs-number">16</span>.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            <span class="hljs-keyword">var</span> text <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-string">""</span>) }

            AnimatedVisibility(
                viewModel.locationAutofill.isNotEmpty(),
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(<span class="hljs-number">8</span>.dp)
            ) {
                LazyColumn(
                    verticalArrangement = Arrangement.spacedBy(<span class="hljs-number">8</span>.dp)
                ) {
                    items(viewModel.locationAutofill) {
                        Row(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(<span class="hljs-number">16</span>.dp)
                                .clickWithRipple {
                                    text = it.address
                                    viewModel.locationAutofill.clear()
                                    viewModel.getCoordinates(it)
                                }
                        ) {
                            Text(it.address)
                        }
                    }
                }
                Spacer(Modifier.height(<span class="hljs-number">16</span>.dp))
            }
            OutlinedTextField(
                value = text,
                onValueChange = {
                    text = it
                    viewModel.searchPlaces(it)
                },
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(<span class="hljs-number">8</span>.dp)
            )
        }
    }
}
</code></pre>
<p>We add a surface towards the bottom of the map, and use a column to position a textfield. There's a LazyColumn above that which appears when there are autofill results.</p>
<p>To handle places autocomplete results, we construct a data class. It contains a full address and a place ID, which we can use to obtain the lat-long for the place.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AutocompleteResult</span></span>(
    <span class="hljs-keyword">val</span> address: String,
    <span class="hljs-keyword">val</span> placeId: String
)
</code></pre>
<p>Then, we use a <code>SnapshotStateList</code>, which is a state-aware list that'll store any autofill results that are made available via the search.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> locationAutofill = mutableStateListOf&lt;AutocompleteResult&gt;()
</code></pre>
<p>Now, whenever the user inputs a query, the <code>searchPlaces</code> method in the ViewModel is called:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> job: Job? = <span class="hljs-literal">null</span>

<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">searchPlaces</span><span class="hljs-params">(query: <span class="hljs-type">String</span>)</span></span> {
    job?.cancel()
    locationAutofill.clear()
    job = viewModelScope.launch {
        <span class="hljs-keyword">val</span> request = FindAutocompletePredictionsRequest
            .builder()
            .setQuery(query)
            .build()
        placesClient
            .findAutocompletePredictions(request)
            .addOnSuccessListener { response -&gt;
                locationAutofill += response.autocompletePredictions.map {
                    AutocompleteResult(
                        it.getFullText(<span class="hljs-literal">null</span>).toString(),
                        it.placeId
                    )
                }
            }
            .addOnFailureListener {
                it.printStackTrace()
                println(it.cause)
                println(it.message)
            }
    }
}
</code></pre>
<p>Notice the use of a <code>CoroutineJob</code> to handle debouncing. Since this method is called every time the user inputs a new character in the field, it can be called quite often, resulting in numerous expensive searches. To reduce the amount, we perform the whole operation inside a Coroutine, which is canceled whenever the method is called. This ensures that the method is called only for the last input character.</p>
<p>The API has several filters like country, location, and radius available which you can use to optimize your results.</p>
<p>When autocomplete results are available, the <code>SnapshotStateList</code> is updated with them, and the changes are immediately reflected in the UI. Now, to handle movement to the place on which the user has clicked, we define a new state object called <code>currentLatLang</code>.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">var</span> currentLatLong <span class="hljs-keyword">by</span> mutableStateOf(LatLng(<span class="hljs-number">0.0</span>, <span class="hljs-number">0.0</span>))
</code></pre>
<p>In our composable, we have a LaunchedEffect that's triggered everytime this state updates. It executes a camera movement:</p>
<pre><code class="lang-kotlin">LaunchedEffect(viewModel.currentLatLong) {
    cameraPositionState.animate(CameraUpdateFactory.newLatLng(viewModel.currentLatLong))
}
</code></pre>
<p>Now, to update this state object, we call the <code>getCoordinates</code> method. Remember, in our autocomplete results, we have a place ID, not coordinates. To get the lat-long for a place ID, we can use this code:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getCoordinates</span><span class="hljs-params">(result: <span class="hljs-type">AutocompleteResult</span>)</span></span> {
        <span class="hljs-keyword">val</span> placeFields = listOf(Place.Field.LAT_LNG)
        <span class="hljs-keyword">val</span> request = FetchPlaceRequest.newInstance(result.placeId, placeFields)
        placesClient.fetchPlace(request)
            .addOnSuccessListener {
                <span class="hljs-keyword">if</span> (it != <span class="hljs-literal">null</span>) {
                    currentLatLong = it.place.latLng!!
                }
            }
            .addOnFailureListener {
                it.printStackTrace()
            }
    }
</code></pre>
<p>So, as soon as the user selects an autocomplete result in the UI, this method is called and executes an update in the map's camera position. We have successfully implemented the Places Autocomplete API and integrated it with our <code>GoogleMap</code>. yay!</p>
<h2 id="heading-4-reverse-geocoding-api">4. Reverse Geocoding API</h2>
<p>Now we need to handle the last use-case where the address in the textfield should update whenever the marker is moved. To do this, we use the process of Reverse Geocoding, where we convert lat-long coordinates to an address.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673194945393/23a7760b-90b2-4afe-90f8-c7a8c39611b3.gif" alt class="image--center mx-auto" /></p>
<p>Handling this case is sufficiently trivial. First, we move the <code>text</code> state variable that's used by the <code>TextField</code> to the ViewModel. Once that's done, let's add a <code>LaunchedEffect</code> that'll tell us when the camera position has changed:</p>
<pre><code class="lang-kotlin">LaunchedEffect(cameraPositionState.isMoving) {
    <span class="hljs-keyword">if</span> (!cameraPositionState.isMoving) {
        viewModel.getAddress(cameraPositionState.position.target)
    }
}
</code></pre>
<p>This effect is triggered everytime the camera settles down after moving. In our ViewModel, we simply use the Geocoding API to produce an address:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getAddress</span><span class="hljs-params">(latLng: <span class="hljs-type">LatLng</span>)</span></span> {
        viewModelScope.launch {
            <span class="hljs-keyword">val</span> address = geoCoder.getFromLocation(latLng.latitude, latLng.longitude, <span class="hljs-number">1</span>)
            text = address?.<span class="hljs-keyword">get</span>(<span class="hljs-number">0</span>)?.getAddressLine(<span class="hljs-number">0</span>).toString()
        }
    }
</code></pre>
<p>Since <code>text</code> is the state variable for the <code>TextField</code>, changing it results in the UI being updated as well. And with that, we've achieved all our goals.</p>
<h1 id="heading-end-result">End Result</h1>
<iframe src="https://player.vimeo.com/video/787340093?h=46b1d4028a&amp;badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" width="406" height="864"></iframe>

<p>Otherwise, see <a target="_blank" href="https://user-images.githubusercontent.com/22092047/211208551-d61c4eb2-0890-48c2-9524-42e09c444c0c.mp4">here</a></p>
<h1 id="heading-final-words-and-code">Final Words and Code</h1>
<p>We have successfully built the screen as per the stated goal. There may be <strong>rough edges and bugs</strong> throughout the code, it's meant to be used as a sample of how to implement these features and integrate them with one another.</p>
<p>After a little bit of cleanup, the codes look like below:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="eeff4187c12dec8510ad895ad72a2a6a"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/sanskar10100/eeff4187c12dec8510ad895ad72a2a6a" class="embed-card">https://gist.github.com/sanskar10100/eeff4187c12dec8510ad895ad72a2a6a</a></div><p> </p>
<hr />
<p><strong>I hope this article was helpful to you. Consider leaving a thumbs up if you liked it.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Developing for Android with a local REST API server]]></title><description><![CDATA[When you're building an Android app, chances are that it'll be interacting with the internet in some way. More often than not, this is through the use of REST API.
First, a refresher
Ideally, you'd have a couple of base URLs, one for production usage...]]></description><link>https://blog.sanskar10100.dev/developing-for-android-with-a-local-rest-api-server</link><guid isPermaLink="true">https://blog.sanskar10100.dev/developing-for-android-with-a-local-rest-api-server</guid><category><![CDATA[Android]]></category><category><![CDATA[REST API]]></category><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Mon, 26 Dec 2022 09:17:38 GMT</pubDate><content:encoded><![CDATA[<iframe src="https://androidweekly.net/issues/issue-552/badge" height="25px&quot;"></iframe>

<p>When you're building an Android app, chances are that it'll be interacting with the internet in some way. More often than not, this is through the use of REST API.</p>
<h2 id="heading-first-a-refresher">First, a refresher</h2>
<p>Ideally, you'd have a couple of base URLs, one for production usage, and one for testing purposes. You also have several endpoints which expose your resources, and everything is hosted somewhere on the web, which could be on-premises or on the cloud. When you're building your Android app, you use a library like Retrofit and a JSON converter like gson or Moshi to interact with the API.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672046630943/495eb77b-f915-4b6a-887c-88ef2ed0d6a4.png" alt class="image--center mx-auto" /></p>
<p>Here, the segment in bold is your base URL and the text after the <code>/</code>, typically called the last path segment, that's your endpoint.</p>
<h2 id="heading-what-if-your-api-isnt-hosted-anywhere-yet">What if your API isn't hosted anywhere (yet)?</h2>
<p>Say you're working for a smaller company that wants to save on hosting costs or you're working on a personal project and saving on hosting costs, so you don't deploy the 'dev' or 'test' version of your API to a proper server. You can of course run everything locally and work from there, but how do you connect it to your Android app? What's your BASE URL? How does your app access it?</p>
<h2 id="heading-its-pretty-easy-actually-barely-an-inconvenience">It's pretty easy actually, barely an inconvenience</h2>
<p>These steps will allow your app on an Emulator/Physical device to interface with the server running on your development machine:</p>
<ol>
<li><p>Set up the endpoints on Retrofit (or any other networking library that you may use). An endpoint may look like this: <code>/user</code>, <code>/add</code>, <code>/movie/popular</code></p>
</li>
<li><p>Run the server on your development machine (PC?), and keep the port number in mind. It could be 3000, 8080, or something similar.</p>
</li>
<li><p>Once your server is up and running, it'll be available on this URL :</p>
<blockquote>
<p><code>http://localhost:&lt;port number here&gt;</code></p>
</blockquote>
<p> For example,</p>
<blockquote>
<p><code>http://localhost:3000/</code></p>
</blockquote>
<p> This will serve as your <code>BASE_URL</code>. The actual address of localhost is <code>127.0.0.1</code>. You can use this instead of <code>localhost</code> as well. Just <strong>don't forget to add the port number at the end</strong>!</p>
</li>
<li><p>Notice that in your base URL, the protocol is <strong>HTTP</strong> and not <strong>**HTTPS**.</strong> Android does support both, but HTTP support isn't enabled out of the box anymore. If you want to use it, you can add a line in your <code>AndroidManifest.xml</code> file, under the application tag that'll let you use HTTP.</p>
<pre><code class="lang-xml"> <span class="hljs-tag">&lt;<span class="hljs-name">application</span>
     &lt;!<span class="hljs-attr">--</span> <span class="hljs-attr">.....</span> <span class="hljs-attr">--</span>&gt;</span>
     android:usesCleartextTraffic="true"
     <span class="hljs-comment">&lt;!-- ..... --&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">application</span>&gt;</span>
</code></pre>
<p> You probably don't want to have this flag enabled in production as it's not exactly the best security practice. A solution is available <a target="_blank" href="https://stackoverflow.com/a/53732801">here</a>.</p>
</li>
<li><p>Once your code is set up and your server is running, all you need to do is forward your server port from your development machine to the device that's running your app. This allows your test device to communicate with the server and can be simply achieved using <code>adb</code>, the Android Debug Bridge. If you don't have adb, you can find instructions to install it <a target="_blank" href="https://www.xda-developers.com/install-adb-windows-macos-linux/">here</a>. Once you have adb, execute this to forward the port:</p>
<blockquote>
<p>adb reverse tcp:&lt;port number here&gt; tcp:&lt;port number here&gt;</p>
</blockquote>
<p> Assuming your server is running on port 3000, your command would be:</p>
<blockquote>
<p>adb reverse tcp:3000 tcp:3000</p>
</blockquote>
<p> This forwards your development machine's port 3000 to your phone's port 3000. You can forward to any port on your device if you want of course, but we're keeping it the same for simplicity's sake.</p>
<hr />
</li>
</ol>
<p><mark>Psst, If you're an Android Developer or hobbyist, adb can do a lot more than forwarding ports for you. Some cool examples </mark> <a target="_blank" href="https://gist.github.com/Pulimet/5013acf2cd5b28e55036c82c91bd56d8"><mark>on this github gist</mark></a><mark>.</mark></p>
<h2 id="heading-thats-it-thats-all-that-you-need-to-do">That's it. That's all that you need to do.</h2>
<p>Some steps need to be done only once, like setting up your endpoints, your manifest, and your BASE URL. Second usage onwards, you just need to spin up your server and forward the right port! You get fast API access while keeping your costs at zero!</p>
<h2 id="heading-bonus-different-base-urls-for-different-builds">Bonus: Different base URLs for different builds</h2>
<p>When it comes time to deploy (or maybe your prod server is already deployed?), you might want to keep different versions of your base URL, one for debug, and one for prod at the very least. You can do so by adding a build config field in your app-level <code>build.gradle</code>, and it's extremely easy. All Android projects have two different build types, <code>debug</code> and <code>release</code> by default. You can add more if you like of course, but for these two, you can easily configure different base URLs and even API keys. This is how you do that:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672045669509/93791dc0-74a7-4584-bf47-0c84792e0de1.png" alt class="image--center mx-auto" /></p>
<p>So you see, both debug and release variants have a <code>buildConfigField</code>, called <code>BASE_URL</code>. Once that's done, you can use them anywhere in the code by referring <code>BuildConfig.BASE_URL</code> as in here:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672045859372/d946d9f9-055c-4459-8ff6-8dfa063ff687.png" alt class="image--center mx-auto" /></p>
<p>The right <code>BASE_URL</code> will be picked for you automatically depending on your selected build variant.</p>
<hr />
<h1 id="heading-hope-this-was-helpful-and-until-next-time">Hope this was helpful, and until next time 👋</h1>
]]></content:encoded></item><item><title><![CDATA[Implementing Periodic Notifications with WorkManager]]></title><description><![CDATA[I had some free time on my hands this morning so I decided to work on an issue for my app Transactions. The issue was to add a recurring notification feature that would remind the users of the app to log their transactions for the day.
So, I did some...]]></description><link>https://blog.sanskar10100.dev/implementing-periodic-notifications-with-workmanager</link><guid isPermaLink="true">https://blog.sanskar10100.dev/implementing-periodic-notifications-with-workmanager</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Android Studio]]></category><category><![CDATA[android development]]></category><category><![CDATA[android apps]]></category><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Sat, 09 Apr 2022 14:49:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1649509118809/B3xH3wu1a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<iframe src="https://androidweekly.net/issues/issue-514/badge" height="25px"></iframe>

<p>I had some free time on my hands this morning so I decided to work on an <a target="_blank" href="https://github.com/sanskar10100/Transactions/issues/48">issue</a> for my app <a target="_blank" href="https://play.google.com/store/apps/details?id=dev.sanskar.transactions">Transactions</a>. The issue was to add a recurring notification feature that would remind the users of the app to log their transactions for the day.</p>
<p>So, I did some research, and decided to use one of two ways:</p>
<h3 id="heading-1-firebase-cloud-messaging-the-easy-way">1. Firebase Cloud Messaging (the easy way)</h3>
<p>Firebase Cloud Messaging is a service that allows you to send notifications or data payload to your Android, iOS, or Web app. Sending background notifications (which only work when your app is in the background) is fairly easy. You can either use the firebase <code>admin-sdk</code> or the notification composer in the Firebase Console to send a notification with an optional payload (image, or key-value pairs) to a target token, a topic that the client device subscribed to, or to all the users of the app altogether. When the app is in the background, the notification appears in the system tray, and tapping it launches the app. It's extremely simple to set up a recurring notification as well, you just have to change the frequency.</p>
<p>For <a target="_blank" href="https://play.google.com/store/apps/details?id=dev.sanskar.transactions">Transactions</a>, I simply added the SDK dependencies to my <code>build.gradle</code> file, ran the app, exited the app (so that it's in the background, foreground notifications work very differently), and used the notification composer to send a test notification. Once that worked, I set up a recurring notification for 10 PM daily.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649509821393/QRW5_q3xy.png" alt="image.png" /></p>
<p>FCM also allows you to modify your notifications on the go, so that's an added advantage. But, I wanted some more features out of this:</p>
<ul>
<li><p>Users should be able to change the default reminder time of 10 PM to a time that they'd prefer</p>
</li>
<li><p>Users should be able to opt out of this feature if they want to.</p>
</li>
</ul>
<p>While the second feature could've been implemented by having the app register or deregister from a <code>daily-reminder</code> topic in the FCM, the first one would require either cloud functions or a suitable backend, and I wanted to keep this implementation on-device. So, I moved to the second option.</p>
<h3 id="heading-2-workmanager-with-periodic-work-requests">2. WorkManager with Periodic work requests</h3>
<p>I'd taken a <a target="_blank" href="https://developer.android.com/codelabs/android-workmanager">codelab</a> earlier that taught how to set up notifications with <a target="_blank" href="https://developer.android.com/topic/libraries/architecture/workmanager">WorkManager</a>. Albeit it was focused on immediate one-time requests, I was aware that <code>WorkManager</code> had the facility of Deferred, Periodic work requests that could execute repeatedly at a fixed interval. I could add some initial delay to this to get the behavior that I wanted.</p>
<p>I started out by setting up a <code>NotificationHelper</code> Kotlin singleton object, whose purpose was to create the notification channel (required on Android 8+), the <code>PendingIntent</code> that'd launch the app when the notification was tapped, and the notification itself.</p>
<h4 id="heading-notificationhandler">NotificationHandler</h4>
<pre><code class="lang-kotlin"><span class="hljs-keyword">object</span> NotificationHandler {
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> CHANNEL_ID = <span class="hljs-string">"transactions_reminder_channel"</span>

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">createReminderNotification</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>)</span></span> {
        <span class="hljs-comment">//  No back-stack when launched</span>
        <span class="hljs-keyword">val</span> intent = Intent(context, MainActivity::<span class="hljs-keyword">class</span>.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        }
        <span class="hljs-keyword">val</span> pendingIntent = PendingIntent.getActivity(context, <span class="hljs-number">0</span>, intent,
            <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE
            <span class="hljs-keyword">else</span> PendingIntent.FLAG_UPDATE_CURRENT)

        createNotificationChannel(context) <span class="hljs-comment">// This won't create a new channel everytime, safe to call</span>

        <span class="hljs-keyword">val</span> builder = NotificationCompat.Builder(context, CHANNEL_ID)
            .setSmallIcon(R.mipmap.ic_launcher_round)
            .setContentTitle(<span class="hljs-string">"Remember to add your transactions!"</span>)
            .setContentText(<span class="hljs-string">"Logging your transactions daily can help you manage your finances better."</span>)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setContentIntent(pendingIntent) <span class="hljs-comment">// For launching the MainActivity</span>
            .setAutoCancel(<span class="hljs-literal">true</span>) <span class="hljs-comment">// Remove notification when tapped</span>
            .setVisibility(VISIBILITY_PUBLIC) <span class="hljs-comment">// Show on lock screen</span>

        with(NotificationManagerCompat.from(context)) {
            notify(<span class="hljs-number">1</span>, builder.build())
        }
    }

    <span class="hljs-comment">/**
     * Required on Android O+
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">createNotificationChannel</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>)</span></span> {
        <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.O) {
            <span class="hljs-keyword">val</span> name = <span class="hljs-string">"Daily Reminders"</span>
            <span class="hljs-keyword">val</span> descriptionText = <span class="hljs-string">"This channel sends daily reminders to add your transactions"</span>
            <span class="hljs-keyword">val</span> importance = NotificationManager.IMPORTANCE_HIGH
            <span class="hljs-keyword">val</span> channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
                description = descriptionText
            }
            <span class="hljs-comment">// Register the channel with the system</span>
            <span class="hljs-keyword">val</span> notificationManager: NotificationManager =
                context.getSystemService(Context.NOTIFICATION_SERVICE) <span class="hljs-keyword">as</span> NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
}
</code></pre>
<p>I'd planned for the <code>createReminderNotification</code> method to be called from the <code>Worker</code> when it was invoked. So, I started writing the implementation for the <code>WorkManager</code>. Before we look at the code, this is what <code>WorkManager</code> is, according to the AndroidX team:</p>
<blockquote>
<p>WorkManager is the recommended solution for persistent work. Work is persistent when it remains scheduled through app restarts and system reboots. Because most background processing is best accomplished through persistent work, WorkManager is the primary recommended API for background processing.</p>
</blockquote>
<p>WorkManager abstracts away the myriads of background processing APIs that Android has, including FirebaseJobDispatcher, GcmNetworkManager, and Job Scheduler. It provides one single surface, which then delegates work to these APIs depending on the context and the API level. These are the type of <code>Work</code> requests that <code>WorkManager</code> supports:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649511185251/e4JtvhWyv.png" alt="image.png" /></p>
<p>Its features cannot be described in a single article, so I hope the code and the comments will be able to convey the meaning of what's happening. To start with <code>WorkManager</code>, we add this dependency to our <code>build.gradle</code> (for Kotlin):</p>
<pre><code class="lang-kotlin">implementation <span class="hljs-string">"androidx.work:work-runtime-ktx:2.7.1"</span>
</code></pre>
<h4 id="heading-remindernotificationworker">ReminderNotificationWorker</h4>
<p>These are requests that run at a fixed interval of <code>TimeUnit</code>, such as Days, Hours, Minutes, etc. One important thing to note here is that <code>WorkManager</code> doesn't guarantee that a <code>Work</code> will be executed at the set time. It may be delayed due to battery optimizations and such, but it does guarantee that the <code>Work</code> will be executed, sooner or later. I started out by extending the <code>Worker</code> class to create a new <code>Worker</code> whose <code>doWork()</code> method is executed by the <code>WorkManager</code> when it's fired.</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ReminderNotificationWorker</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> appContext: Context, workerParameters: WorkerParameters) : Worker(appContext, workerParameters) {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">doWork</span><span class="hljs-params">()</span></span>: Result {
        NotificationHandler.createReminderNotification(appContext)
        <span class="hljs-keyword">return</span> Result.success()
    }
}
</code></pre>
<p>Whenever the <code>ReminderNotificationWorker</code> is executed, it creates a new reminder notification. I didn't include any error handling here, which I plan on adding some time later.</p>
<p>After that, I defined a static, or as Kotlin likes to call it, <code>companion object</code> method, called <code>schedule</code>, which takes in a <code>Context</code>, and the time at which the notification should be generated daily. This method also handles some cases like when the user selects a time that's before the current time, it delays the first firing until the next day. That may sound easy, but it was surprisingly complex to implement manually, and I resorted to the <code>Calendar</code> class in the end.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">companion</span> <span class="hljs-keyword">object</span> {

        <span class="hljs-comment">/**
         * <span class="hljs-doctag">@param</span> hourOfDay the hour at which daily reminder notification should appear [0-23]
         * <span class="hljs-doctag">@param</span> minute the minute at which daily reminder notification should appear [0-59]
         */</span>
        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">schedule</span><span class="hljs-params">(appContext: <span class="hljs-type">Context</span>, hourOfDay: <span class="hljs-type">Int</span>, minute: <span class="hljs-type">Int</span>)</span></span> {
            log(<span class="hljs-string">"Reminder scheduling request received for <span class="hljs-variable">$hourOfDay</span>:<span class="hljs-variable">$minute</span>"</span>)
            <span class="hljs-keyword">val</span> now = Calendar.getInstance()
            <span class="hljs-keyword">val</span> target = Calendar.getInstance().apply {
                <span class="hljs-keyword">set</span>(Calendar.HOUR_OF_DAY, hourOfDay)
                <span class="hljs-keyword">set</span>(Calendar.MINUTE, minute)
            }

            <span class="hljs-keyword">if</span> (target.before(now)) {
                target.add(Calendar.DAY_OF_YEAR, <span class="hljs-number">1</span>)
            }

            log(<span class="hljs-string">"Scheduling reminder notification for <span class="hljs-subst">${target.timeInMillis - System.currentTimeMillis()}</span> ms from now"</span>)

            <span class="hljs-keyword">val</span> notificationRequest = PeriodicWorkRequestBuilder&lt;ReminderNotificationWorker&gt;(<span class="hljs-number">24</span>, TimeUnit.HOURS)
                .addTag(TAG_REMINDER_WORKER)
                .setInitialDelay(target.timeInMillis - System.currentTimeMillis(), TimeUnit.MILLISECONDS).build()
            WorkManager.getInstance(appContext)
                .enqueueUniquePeriodicWork(
                <span class="hljs-string">"reminder_notification_work"</span>,
                ExistingPeriodicWorkPolicy.REPLACE,
                notificationRequest
            )
        }
    }
</code></pre>
<p>We use <code>UniquePeriodicWork</code> to ensure that there's ever only one instance of our <code>ReminderNotificationWorker</code> in existence since I didn't want to annoy the user with improper notifications. The tag is used later to identify and cancel all the <code>Work</code> requests should the user choose to opt out. I realized I'd need to handle the user preferences at this point, so I wrote a <code>SharedPreferences</code> helper class that'd help us interact with the <code>SharedPreferences</code> key-value store that Android provides.</p>
<h4 id="heading-preferencestore">PreferenceStore</h4>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PreferenceStore</span></span>(context: Context) {
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> sharedPref: SharedPreferences = context.getSharedPreferences(<span class="hljs-string">"transactions_shared_pref"</span>, Context.MODE_PRIVATE)

    <span class="hljs-comment">/**
     * <span class="hljs-doctag">@return</span> the hour of the day in which the reminder should be shown, default is 22, if canceled -1
     * <span class="hljs-doctag">@return</span> the minute at which the reminder should be shown, default is 0, if canceled -1
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getReminderTime</span><span class="hljs-params">()</span></span>: Pair&lt;<span class="hljs-built_in">Int</span>, <span class="hljs-built_in">Int</span>&gt; {
        <span class="hljs-keyword">return</span> Pair(
            sharedPref.getInt(SHARED_PREF_REMINDER_HOUR, <span class="hljs-number">22</span>),
            sharedPref.getInt(SHARED_PREF_REMINDER_MINUTE, <span class="hljs-number">0</span>)
        )
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setReminderTime</span><span class="hljs-params">(hour: <span class="hljs-type">Int</span>, minute: <span class="hljs-type">Int</span>)</span></span> {
        sharedPref.edit {
            putInt(SHARED_PREF_REMINDER_HOUR, hour)
            putInt(SHARED_PREF_REMINDER_MINUTE, minute)
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">cancelReminder</span><span class="hljs-params">()</span></span> {
        sharedPref.edit {
            putInt(SHARED_PREF_REMINDER_HOUR, -<span class="hljs-number">1</span>)
            putInt(SHARED_PREF_REMINDER_MINUTE, -<span class="hljs-number">1</span>)
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">isDefaultReminderSet</span><span class="hljs-params">()</span></span> = sharedPref.getBoolean(<span class="hljs-string">"is_default_reminder_set"</span>, <span class="hljs-literal">false</span>)

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">saveDefaultReminderIsSet</span><span class="hljs-params">()</span></span> {
        sharedPref.edit { putBoolean(<span class="hljs-string">"is_default_reminder_set"</span>, <span class="hljs-literal">true</span>) }
    }
}
</code></pre>
<p>When the app is launched for the first time, we set the default reminder at 10 PM, which is then customizable by the user. I probably could have removed some redundant methods at the bottom, but couldn't find a feasible solution. Now, let's move to the ViewModel.</p>
<h4 id="heading-mainviewmodel">MainViewModel</h4>
<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> app = application
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> prefStore = PreferenceStore(application)

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">scheduleReminderNotification</span><span class="hljs-params">(hourOfDay: <span class="hljs-type">Int</span>, minute: <span class="hljs-type">Int</span>)</span></span> {
        prefStore.setReminderTime(hourOfDay, minute)
        ReminderNotificationWorker.schedule(app, hourOfDay, minute)
    }

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getReminderTime</span><span class="hljs-params">()</span></span> = prefStore.getReminderTime()

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">cancelReminderNotification</span><span class="hljs-params">()</span></span> {
        log(<span class="hljs-string">"Cancelling reminder notification"</span>)
        prefStore.cancelReminder()
        WorkManager.getInstance(app).cancelAllWorkByTag(TAG_REMINDER_WORKER)
    }

    <span class="hljs-comment">/**
     * This sets the default time at the first launch of the app
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">checkAndSetDefaultReminder</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> (!prefStore.isDefaultReminderSet()) {
            scheduleReminderNotification(DEFAULT_REMINDER_HOUR, DEFAULT_REMINDER_MINUTE)
            prefStore.saveDefaultReminderIsSet()
        }
    }
</code></pre>
<p>We extend the <code>AndroidViewModel</code> class, since the application instance, or rather the <code>Context</code>, is needed for initializing the database instance and the shared preferences store, as well as the <code>WorkManager</code>. I plan on adding <strong>Dependency Injection using Hilt</strong> to the app soon, which would eliminate the need for this. In our ViewModel, the <code>checkAndSetDefaultReminder</code> method is called every time the ViewModel is initialized. This is alright for now (or is it? :p) since we use only a single ViewModel shared across fragments because of Transactions being a relatively small app, but it'll soon be migrated to a more appropriate place like the Application class. The intent of the other methods should be clear enough by the names. Now, let's look at the <code>BottomSheetDialogFragment</code> that allows the user to configure notifications:</p>
<h4 id="heading-notificationsbottomsheet">NotificationsBottomSheet</h4>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NotificationsBottomSheet</span> : <span class="hljs-type">BottomSheetDialogFragment</span></span>() {
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> binding: FragmentNotificationsBottomSheetBinding
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> viewModel <span class="hljs-keyword">by</span> viewModels&lt;MainViewModel&gt;()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreateView</span><span class="hljs-params">(
        inflater: <span class="hljs-type">LayoutInflater</span>,
        container: <span class="hljs-type">ViewGroup</span>?,
        savedInstanceState: <span class="hljs-type">Bundle</span>?
    )</span></span>: View? {
        binding = FragmentNotificationsBottomSheetBinding.inflate(inflater, container, <span class="hljs-literal">false</span>)
        <span class="hljs-keyword">return</span> binding.root
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onViewCreated</span><span class="hljs-params">(view: <span class="hljs-type">View</span>, savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onViewCreated(view, savedInstanceState)

        setInitial()

        binding.checkboxReminder.setOnCheckedChangeListener { buttonView, isChecked -&gt;
            <span class="hljs-keyword">if</span> (isChecked) {
                <span class="hljs-comment">// Was previously removed by user, now user wants to re-enable.</span>
                viewModel.scheduleReminderNotification(DEFAULT_REMINDER_HOUR, DEFAULT_REMINDER_MINUTE)
                binding.textViewReminderTime.isEnabled = <span class="hljs-literal">true</span>
                binding.textViewReminderTime.text = get12HourTime(DEFAULT_REMINDER_HOUR, DEFAULT_REMINDER_MINUTE)
            } <span class="hljs-keyword">else</span> {
                viewModel.cancelReminderNotification()
                binding.textViewReminderTime.isEnabled = <span class="hljs-literal">false</span>
                binding.textViewReminderTime.text = <span class="hljs-string">"Not set"</span>
            }
        }

        binding.textViewReminderTime.setOnClickListener {
            <span class="hljs-keyword">val</span> time = viewModel.getReminderTime()

            <span class="hljs-keyword">val</span> picker = MaterialTimePicker.Builder()
                .setTimeFormat(TimeFormat.CLOCK_12H)
                .setHour(time.first)
                .setMinute(time.second)
                .setTitleText(<span class="hljs-string">"Select Reminder Time"</span>)
                .build()
            picker.show(parentFragmentManager, <span class="hljs-string">"notification-time-picker"</span>)

            picker.addOnPositiveButtonClickListener {
                <span class="hljs-keyword">val</span> hour = picker.hour
                <span class="hljs-keyword">val</span> minute = picker.minute
                viewModel.scheduleReminderNotification(hour, minute)
                <span class="hljs-keyword">val</span> newTime = viewModel.getReminderTime()
                binding.textViewReminderTime.text = get12HourTime(newTime.first, newTime.second)
            }
        }
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setInitial</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">val</span> setTime = viewModel.getReminderTime()
        <span class="hljs-keyword">if</span> (setTime.first == -<span class="hljs-number">1</span> || setTime.second == -<span class="hljs-number">1</span> ) {
            binding.checkboxReminder.isChecked = <span class="hljs-literal">false</span>
            binding.textViewReminderTime.isEnabled = <span class="hljs-literal">false</span>
            binding.textViewReminderTime.text = <span class="hljs-string">"Not set"</span>
        } <span class="hljs-keyword">else</span> {
            binding.checkboxReminder.isChecked = <span class="hljs-literal">true</span>
            binding.textViewReminderTime.isEnabled = <span class="hljs-literal">true</span>
            binding.textViewReminderTime.text = get12HourTime(setTime.first, setTime.second)
        }
    }
}
</code></pre>
<p>Initial values are fetched and set from the Preferences. The user can enable or disable the notifications by clicking on the checkbox. Time can be configured by tapping the text and defaulting to the value of 10 PM. We use the Material <code>TimePicker</code> widget to allow the user to select time easily, and it's also super simple to implement, compared to a separate <code>TimerPickerDialog</code>, which is used elsewhere in the app.</p>
<p>At a fixed interval of every 24 hours, the <code>ReminderNotificationWorker</code> is triggered by the <code>WorkManager</code>. The worker, in turn, creates a notification (this being the <code>Work</code>, tapping which takes the user directly to the app. This allows us to implement the features required above, while also keeping the implementation on the device, by leveraging the awesome AndroidX library that is <code>WorkManager</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649514397186/jSmbe1HSz.png" alt="Screenshot_1649503731.png" /></p>
<p>Transactions is available on the <a target="_blank" href="https://play.google.com/store/apps/details?id=dev.sanskar.transactions">Play Store</a>, and the full source code is available on <a target="_blank" href="https://github.com/sanskar10100/Transactions">GitHub</a>. Leave a thumbs up if you liked the article. Cheers!</p>
]]></content:encoded></item><item><title><![CDATA[QueryBuilder: How I wrote a runtime SQL query generator for an Android app]]></title><description><![CDATA[I've been building an app called Transactions for some time. As the name suggests, Transactions offers you the capability to track your income and expenses made through cash or digital mediums. I mostly wrote it for my personal use, but it caught on ...]]></description><link>https://blog.sanskar10100.dev/querybuilder-how-i-wrote-a-runtime-sql-query-generator-for-an-android-app</link><guid isPermaLink="true">https://blog.sanskar10100.dev/querybuilder-how-i-wrote-a-runtime-sql-query-generator-for-an-android-app</guid><category><![CDATA[Android]]></category><category><![CDATA[SQL]]></category><category><![CDATA[app development]]></category><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Mon, 07 Feb 2022 12:05:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644234773641/z7DyJIt6Z.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been building an app called Transactions for some time. As the name suggests, Transactions offers you the capability to track your income and expenses made through cash or digital mediums. I mostly wrote it for my personal use, but it caught on with my friends and is now published on the <a target="_blank" href="https://play.google.com/store/apps/details?id=dev.sanskar.transactions">Play Store</a>.</p>
<h3 id="heading-lets-get-some-context-about-transactions-to-understand-the-problem-better">Let's get some context about Transactions to understand the problem better</h3>
<p>Transactions use Room ORM under the hood. It's an abstraction layer over the SQLite library and allows you to simplify your database operations on Android</p>
<p>In a typical addition flow in Transactions, the user adds a transaction through the add transaction fragment, which gets parsed and inserted into the database. Then, Room emits a new list of transactions through a Kotlin Flow, which is an observable data type that Room supports natively. So, a typical get query looks like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Query(<span class="hljs-meta-string">"SELECT * FROM `transactions`"</span>)</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getAllTransactions</span><span class="hljs-params">()</span></span>: Flow&lt;List&lt;Transaction&gt;&gt;
</code></pre>
<p>This method emits a new list whenever there's a change in the database. This flow is collected in the ViewModel, which then dispatches the list to the UI controller, that is the fragment. The fragment then submits this list to a DiffUtil based RecyclerView adapter, which efficiently updates the list with the differences along with providing the default animations.</p>
<h3 id="heading-the-challenge-incremental-sorting-and-filtering">The challenge: Incremental Sorting and Filtering</h3>
<p>I wanted to implement incremental sorting and filtering in the app. To understand it better, let's say that I only want to see transactions that are labeled as income. On top of that, I only want to see those transactions which carry an amount greater than 500, and then sort those by time. This gives us the pseudo-query</p>
<pre><code class="lang-kotlin">Show me all transactions that are income and which carry an amount greater than <span class="hljs-number">500</span> ordered <span class="hljs-keyword">by</span> time
</code></pre>
<p>Now, the problem with this incremental filtering approach is that each filter needs to stack on top of the other. How do we implement this?</p>
<h3 id="heading-solution-1-do-filtering-on-kotlin-lists">Solution 1: Do filtering on Kotlin lists</h3>
<p>We of course have the option to get a list of all transactions from the database, and then repeatedly apply the <code>.filter { }</code> or the <code>.sort()</code> method on the resultant lists, but that doesn't seem too efficient. After all, that's what SQL is for. Not using it would be a mistake in this scenario, so I moved to the other choice.</p>
<h3 id="heading-solution-2-write-custom-dao-methods-for-every-combination">Solution 2: Write custom DAO methods for every combination</h3>
<p>The problem with this approach is that this is extremely repetitive, error-prone and the number of queries that are needed to be saved grows exponentially with every new parameter, which is not scalable at all.</p>
<h3 id="heading-solution-3-find-a-way-to-feed-the-list-back-a-custom-sql-view-and-then-apply-filtering-on-the-view-incrementally">Solution 3: Find a way to feed the list back a custom SQL view, and then apply filtering on the view incrementally.</h3>
<p>The problem with this approach is that you have to write a lot of SQLite code bypassing Room, which is incredibly complex for a small project like this.</p>
<h3 id="heading-my-solution-build-query-in-runtime">My Solution: Build Query in Runtime</h3>
<p>I found that SQL provides an annotation that allows you to execute a custom raw SQL query and observer the resultant. The syntax looks like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@RawQuery(observedEntities = [Transaction::class])</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">customTransactionQuery</span><span class="hljs-params">(query: <span class="hljs-type">SupportSQLiteQuery</span>)</span></span>: Flow&lt;List&lt;Transaction&gt;&gt;
</code></pre>
<p>So, now our DAO method takes in a custom query, runs it, and provides us an observable Kotlin Flow (or LiveData) which will update every time there's a change in the queried view. So, now the challenge was to generate the SQL query dynamically based on the filtering parameters that the user has selected at any given instant. To do that, I wrote a QueryBuilder class.</p>
<h2 id="heading-the-querybuilder">The QueryBuilder</h2>
<p>So, I first listed out all the possible choices for any particular filter through the use of enums. For example, a Sort Choices enum looked like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">enum</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SortByChoices</span></span>(<span class="hljs-keyword">val</span> readableString: String) {
    AMOUNT_HIGHEST_FIRST(<span class="hljs-string">"Highest Amount First"</span>),
    AMOUNT_LOWEST_FIRST(<span class="hljs-string">"Lowest Amount First"</span>),
    TIME_EARLIEST_FIRST(<span class="hljs-string">"Earliest Transaction First"</span>),
    TIME_NEWEST_FIRST(<span class="hljs-string">"Latest Transaction First"</span>),
    UNSPECIFIED(<span class="hljs-string">"Default Order"</span>)
}
</code></pre>
<p>The enum takes in a constructor, which is the readable string that can be displayed to a user. To use the QueryBuilder, you have to create an object and call its builder methods that set the value of these parameters. A typical set method looks like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setFilterType</span><span class="hljs-params">(filterTypeChoice: <span class="hljs-type">FilterByTypeChoices</span>)</span></span>: QueryBuilder {
        <span class="hljs-keyword">if</span> (filterTypeChoice != FilterByTypeChoices.UNSPECIFIED) {
            <span class="hljs-keyword">this</span>.filterTypeChoice = filterTypeChoice
            <span class="hljs-keyword">this</span>.filterEnabled = <span class="hljs-literal">true</span>
        }
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>
    }
</code></pre>
<p>So, you specified the values of these choices, and then called the <code>build()</code> method on the QueryBuilder instance. Now, the build method walks over each of the parameter values and generates the SQL query piece by piece. The query generator for Type Filter is shown below:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Type filter</span>
        <span class="hljs-keyword">if</span> (filterTypeChoice != FilterByTypeChoices.UNSPECIFIED) {
            <span class="hljs-keyword">if</span> (previousFilterExists) query.append(<span class="hljs-string">" AND"</span>)
            query.append(<span class="hljs-string">" isExpense"</span>)
            <span class="hljs-keyword">val</span> typeIsExpense = <span class="hljs-keyword">when</span> (filterTypeChoice) {
                FilterByTypeChoices.EXPENSE -&gt; <span class="hljs-string">"= 1"</span>
                FilterByTypeChoices.INCOME -&gt; <span class="hljs-string">"= 0"</span>
                <span class="hljs-keyword">else</span> -&gt; <span class="hljs-string">""</span>
            }
            query.append(<span class="hljs-string">" <span class="hljs-variable">$typeIsExpense</span>"</span>)
            previousFilterExists = <span class="hljs-literal">true</span>
        }
</code></pre>
<p>In the end, the builder returns a <code>SimpleSQLiteQuery</code> object, like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">return</span> SimpleSQLiteQuery(query.toString())
</code></pre>
<p>This query object is then received by the caller and sent to the DAO for execution. Since the DAO gives us an observable, any updates to the queried dataset are updated in the UI automatically.</p>
<p>Once I was done writing the Builder, I wrote a bunch of plain old JUnit4 tests to check if the generated queries were the expected ones. A sample test:</p>
<pre><code class="lang-kotlin">    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">query_filter_amount_greater_and_type_income_and_medium_digital_and_sort_by_time_oldest</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">val</span> query = QueryBuilder()
            .setFilterAmount(FilterByAmountChoices.GREATER_THAN, <span class="hljs-number">100</span>)
            .setFilterType(FilterByTypeChoices.INCOME)
            .setFilterMedium(FilterByMediumChoices.DIGITAL)
            .setSortingChoice(SortByChoices.TIME_EARLIEST_FIRST)
            .build()
        assertEquals(<span class="hljs-string">"SELECT * FROM `transaction` WHERE amount &gt;= 100 AND isExpense = 0 AND isDigital = 1 ORDER BY timestamp ASC"</span>, query.sql)
    }
</code></pre>
<p>Now, in the UI, whenever any of the filter parameters are changed, a new Query is built and executed:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">/**
     * Generates an SQL query from the current configuration and executes it through Room.
     * An observable flow is returned, updates whenever there's a change in the database
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">executeConfig</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">val</span> query = QueryBuilder()
            .setFilterAmount(QueryConfig.filterAmountChoice, QueryConfig.filterAmountValue)
            .setFilterType(QueryConfig.filterTypeChoice)
            .setFilterMedium(QueryConfig.filterMediumChoice)
            .setSortingChoice(QueryConfig.sortChoice)
            .build()
        viewModelScope.launch {
            db.transactionDao().customTransactionQuery(query).collect {
                transactions.value = it
            }
        }
    }
</code></pre>
<p>This allows us to efficiently generate our SQL queries dynamically and handle any number of filter parameters in linear code complexity.</p>
<p>Transactions has a simple philosophy inspired by <a target="_blank" href="https://www.linkedin.com/in/alexander-chiou/">Alex Chiou</a> and <a target="_blank" href="https://www.linkedin.com/in/rpandey1234/">Rahul Pandey</a>: In a side-project, build only a few features, but build them well. The entire source code is available on GitHub: https://github.com/sanskar10100/Transactions</p>
<p>This is what the app looks like now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644234773641/z7DyJIt6Z.png" alt="image.png" /></p>
<p>Transactions is available on the Play Store as well. Install it from: https://play.google.com/store/apps/details?id=dev.sanskar.transactions (At the time of writing the incremental filtering update is still in beta, so it may require some time to show up on your device).</p>
]]></content:encoded></item><item><title><![CDATA[How to get a free .tech domain, connect it to GitHub pages and get a free SSL certificate]]></title><description><![CDATA[.tech domains are excellent personal domain names for developers because they basically put up your tech role upfront without even having to open your website. You can get one for free (for a year) by signing up for the GitHub Student Developer Pack ...]]></description><link>https://blog.sanskar10100.dev/how-to-get-a-free-tech-domain-connect-it-to-github-pages-and-get-a-free-ssl-certificate</link><guid isPermaLink="true">https://blog.sanskar10100.dev/how-to-get-a-free-tech-domain-connect-it-to-github-pages-and-get-a-free-ssl-certificate</guid><category><![CDATA[GitHub]]></category><category><![CDATA[domain]]></category><category><![CDATA[technology]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Fri, 31 Dec 2021 15:21:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1640962438567/PQCNX6MxF.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>.tech domains are excellent personal domain names for developers because they basically put up your tech role upfront without even having to open your website. You can get one for free (for a year) by signing up for the GitHub Student Developer Pack <a target="_blank" href="https://education.github.com/pack">here</a> </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640962438567/PQCNX6MxF.png" alt="image.png" /></p>
<p>After you've received the confirmation for the student developer pack, head over to https://get.tech and find a domain you like. During checkout, just authorize with your GitHub account to get the license for free for a year.</p>
<h2 id="heading-setting-up-the-custom-domain">Setting up the custom domain</h2>
<p>After you get the domain, you'll be redirected to the console. It'll look something like this:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640962558490/LbX-YF2jn.png" alt="image.png" /></p>
<p>Open up the list of orders, and navigate to your recently availed domain name. It'll look something like this:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640962611701/QVwd3Sa4O.png" alt="image.png" /></p>
<p>Now, to connect your GitHub pages website to this domain, you need to head over to this document: https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site</p>
<p>The document is a little complex, so in short, you'd want to follow these steps to link your domain name to the GitHub Pages server:</p>
<ul>
<li>Find the DNS Console
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640962810515/ZpXP9ZPhs.png" alt="image.png" /></li>
<li>Add a CNAME record. Put <code>www</code> in the name field, and your GitHub pages domain in the value field. Leave the TTL at default. After configuring, your CNAME should look like this:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640962980975/h74wUicDP.png" alt="image.png" /></li>
<li>Add a total of 4 A records from the same panel. In all A records, put the hostname as <code>@</code> or <code>www</code>. Leave the TTL at default. In the destination IPv4 addresses, put the following 4 IP addresses sequentially (one IP address in one record):<pre><code>185<span class="hljs-selector-class">.199</span><span class="hljs-selector-class">.108</span><span class="hljs-selector-class">.153</span>
185<span class="hljs-selector-class">.199</span><span class="hljs-selector-class">.109</span><span class="hljs-selector-class">.153</span>
185<span class="hljs-selector-class">.199</span><span class="hljs-selector-class">.110</span><span class="hljs-selector-class">.153</span>
185<span class="hljs-selector-class">.199</span><span class="hljs-selector-class">.111</span><span class="hljs-selector-class">.153</span>
</code></pre></li>
</ul>
<p>Each of your A records should look like this:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640963847717/pM7cihDlb.png" alt="image.png" />
When you're done, you should have this list:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640963875812/tkSOQL36V.png" alt="image.png" />
<strong>Note</strong>: These IP addresses may have changed. Consult this <a target="_blank" href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#configuring-an-apex-domain">page</a> for the latest addresses.</p>
<ul>
<li>It may take some time (up to 72 hours) for the new DNS to propagate. Head over to your GitHub page settings, and add your custom domain. If GitHub can verify your domain, you're all set up, and can now use your custom domain. If not, then you're gonna have to come back later.</li>
</ul>
<h2 id="heading-to-obtain-the-free-ssl-certificate-from-cloudfare">To Obtain the free SSL certificate from CloudFare</h2>
<ol>
<li>Sign up for a Cloudflare account.</li>
<li>Register your custom domain, as below:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640963306379/gfW84M4xG.png" alt="image.png" /></li>
<li>Now you will have to use CloudFare's nameserver for your domain. Cloudflare will provide you with 2 auto-generated nameservers for your domain. Copy them.</li>
<li>Head over to the domain management console, and open up the nameservers option:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640963986072/eGgTaU8u2.png" alt="image.png" /></li>
<li>Update the first two nameservers with the ones provided by CloudFare, and delete the rest:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640963480909/z3rwzVwzd.png" alt="image.png" /></li>
<li>After you've updated the nameserver, it may take up to 4 hours for you to receive a mail from CloudFare about successful website addition. Once that's done, open up the Cloudflare console once more, and head over to the SSL section.</li>
<li>Enable Full SSL/TLS
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640963547359/cQmSEKnzv.png" alt="image.png" /></li>
<li>Enforce HTTPS from GitHub Pages settings:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640963574093/id7U80t6T.png" alt="image.png" /></li>
</ol>
<p>That's it! You just connected your free for 1 year .tech custom domain to your GitHub pages site and got a free universal SSL certificate!</p>
]]></content:encoded></item><item><title><![CDATA[Android App Shortcuts]]></title><description><![CDATA[Most popular apps on Android phones have launcher shortcuts; app-specific destinations or features that appear when you long tap their icon in the launcher.

Users can even drag the shortcuts outside to your home screen as demoed below:

This has bee...]]></description><link>https://blog.sanskar10100.dev/android-app-shortcuts</link><guid isPermaLink="true">https://blog.sanskar10100.dev/android-app-shortcuts</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Android Studio]]></category><category><![CDATA[Mobile apps]]></category><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Sat, 11 Dec 2021 09:31:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639212926995/qtkJl7-Rd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most popular apps on Android phones have launcher shortcuts; app-specific destinations or features that appear when you long tap their icon in the launcher.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639212926995/qtkJl7-Rd.jpeg" alt="App Shortcuts" /></p>
<p>Users can even drag the shortcuts outside to your home screen as demoed below:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639214442545/9keSH0uLu.gif" alt="ezgif-7-ed2aaf7a35c2.gif" /></p>
<p>This has been available since Android Nougat (Android 7.1). Even if your app has a lower minSdkVersion (Around Lollipop or even lower), you can set an attribute <code>tools:targetApi="n_mr1"</code> in the shortcuts.xml that we'll define soon.</p>
<h2 id="heading-static-shortcuts">Static Shortcuts</h2>
<p>In this article, we're gonna look at static shortcuts <strong>only</strong>, which are basically intents that open up different activities in your app. I'll also offer the solution that I used for my fragment-centric app. There are also dynamic shortcuts that apps like WhatsApp and Telegram use which change with time - they won't be covered here. For more information, consider checking the official docs: <a target="_blank" href="https://developer.android.com/guide/topics/ui/shortcuts/">App Shortcuts</a></p>
<h2 id="heading-how-to-add-a-static-shortcut">How to add a static shortcut?</h2>
<p>Create an Android Resource Directory under <code>res</code> and name it <code>xml</code>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639213638621/ctZoGcDO3.png" alt="Screenshot_20211211_143638.png" /></p>
<p>Add a new <strong>XML Resource File</strong> under <code>xml</code> with the name shortcuts
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639213845183/_sZpNv7Il.png" alt="Screenshot_20211211_143810.png" /></p>
<p>A newly generated shortcuts.xml appears. Replace it with this XML:</p>
<pre><code class="lang-XML"><span class="hljs-tag">&lt;<span class="hljs-name">shortcuts</span> <span class="hljs-attr">xmlns:tools</span>=<span class="hljs-string">"http://schemas.android.com/tools"</span>
    <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">shortcut</span>
        <span class="hljs-attr">android:shortcutId</span>=<span class="hljs-string">"your_id_here"</span>
        <span class="hljs-attr">android:enabled</span>=<span class="hljs-string">"true"</span>
        <span class="hljs-attr">android:icon</span>=<span class="hljs-string">"your_icon_drawable_here"</span>
        <span class="hljs-attr">android:shortcutShortLabel</span>=<span class="hljs-string">"your_string_resource_label_here"</span>
        <span class="hljs-attr">android:shortcutLongLabel</span>=<span class="hljs-string">"your_string_resource_long_label_here"</span>
        <span class="hljs-attr">tools:targetApi</span>=<span class="hljs-string">"n_mr1"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">intent</span>
            <span class="hljs-attr">android:action</span>=<span class="hljs-string">"some_action_name"</span>
            <span class="hljs-attr">android:targetPackage</span>=<span class="hljs-string">"your.package.name"</span> 
            <span class="hljs-attr">android:targetClass</span>=<span class="hljs-string">"activity.package.name"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">shortcut</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- More Shortcut Tags --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">shortcuts</span>&gt;</span>
</code></pre>
<p>Here is a screenshot of mine:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639214980695/cwCktyHRI.png" alt="image.png" /></p>
<p>Add your shortcut meta-data under the host or target <code>activity</code> tag in your <code>AndroidManifests.xml</code> like this:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639214758359/FrKsq9G62.png" alt="image.png" /></p>
<h2 id="heading-optional-if-youre-using-fragment-centric-architecture">Optional: If you're using fragment centric architecture</h2>
<p>If you're using a single activity with multiple fragments or similar architecture in your app, just match the intent action attribute inside your host activity with the one you specified in <code>shortcuts.xml</code>, like this:</p>
<pre><code class="lang-Kotlin"><span class="hljs-keyword">when</span> (intent.action) {
           <span class="hljs-comment">// If intent action matches, navigate to that fragment.</span>
            <span class="hljs-string">"shortcut_add_transaction"</span> -&gt; navController.navigate(HomeFragmentDirections.actionHomeFragmentToAddTransactionFragment())
}
</code></pre>
<p>And Ta-Da! You just added an app shortcut! Now users can easily access top destinations in your app, right from the launcher.</p>
]]></content:encoded></item><item><title><![CDATA[To CP or not to CP]]></title><description><![CDATA[Before we start, I'd like to clarify where I'm coming from. I like Nikola Tesla, Guido van Rossum and Steve Wozniak. Money is important, but it's not as important as doing good engineering.
How is this relevant?
I want you to know where I'm coming fr...]]></description><link>https://blog.sanskar10100.dev/to-cp-or-not-to-cp</link><guid isPermaLink="true">https://blog.sanskar10100.dev/to-cp-or-not-to-cp</guid><category><![CDATA[Competitive programming]]></category><category><![CDATA[Career]]></category><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Sun, 14 Nov 2021 13:29:40 GMT</pubDate><content:encoded><![CDATA[<p>Before we start, I'd like to clarify where I'm coming from. I like Nikola Tesla, Guido van Rossum and Steve Wozniak. Money is important, but it's not as important as doing good engineering.</p>
<h3 id="how-is-this-relevant">How is this relevant?</h3>
<p>I want you to know where I'm coming from, because my opinion will be heavily influenced by my background and my goals. I want to be a good engineer, first and foremost. Money comes later.</p>
<h3 id="cp-a-refresher">CP: A refresher</h3>
<p>Competitive Programming, generally shortened as CP, aka Sport Programming is as the name implies, a sport. It's like a puzzle, a mental exercise, something to tickle your brain. You're given a problem, and you have to solve that problem while facing a set of constraints like limited time, language choice and coding environment. Competitive Programming as a sport has existed for about 3+ decades, and is supported by nearly every tech company on the face of the planet.</p>
<h3 id="why-do-people-do-competitive-programming">Why do people do competitive programming?</h3>
<p>Why do people like solving Sudoku? It engages your brain's higher functions, stimulates it, and makes the time pass. If you do it long enough you also become really good at it, and can apply the skills learned from it elsewhere in life too. Same goes for competitive programming. When you get in the habit of building solutions quickly within a set of constraints you're basically preparing yourself to do it more and more in real life, making you a better engineer and problem solver. And, if you have a good competitive programming rank, you'll probably get noticed by big tech recruiters.</p>
<h3 id="the-problem-with-competitive-programming">The problem with Competitive Programming</h3>
<p>Competitive Programming evolved from a sport which people used to enjoy in their downtime to something that is seen as an entrance to the Big Tech companies now. While this is true to a degree, there are other methods to get into Big Tech. One of them is to just be an excellent engineer. Make good apps, or contribute to open source.</p>
<h3 id="the-criticism-of-cp">The criticism of CP:</h3>
<p>The Software community for the most part is as divided on CP as they are on vim vs emacs. There are fighters on both side of the camp and both provide very good arguments as to why their side is correct. Generally, the points that the opposers of CP present are:</p>
<ul>
<li>Bad coding habits: Use of macros, tricks, shortcuts, poor variable names</li>
<li>Doesn't reflect actual software engineering experiences. Software Engineering problems are typically very complex and model real world issues, which is not correctly translated in small, constrained algorithmic problems.</li>
<li>It's a waste of time; programmers should focus their efforts on solving real world problems.</li>
</ul>
<p>My biggest gripe with CP is probably the first point mentioned above. If you ever want to see it for yourself, look at Google's HashCode's problems. They're inspired by a real life engineering challenge that Googlers faced during their day-to-day, and your CP macro tricks won't help you solve them, at all.</p>
<h3 id="the-difference-between-problem-solving-practice-and-cp">The difference between problem solving practice and CP:</h3>
<p>Practicing solving problems is a crucial professional skill for any engineer. But, CP isn't an accurate reflection of the problems that you'll face day to day as an engineer. You'll be more concerned with the overall system and structure of the problem and your solutions will range from thousands to hundreds of thousands of lines of code. But that does not mean that you should absolutely stop solving any algorithmic challenges.</p>
<h3 id="leetcode-is-important-why">Leetcode is important. Why?</h3>
<p>Most of the people in colleges, when they start off with problem solving, will head straight to something like Codechef and start taking the challenges. That's good. Solving those puzzles is a valuable skill, but again, it's not reflective of the kind of problem solving you'll be doing in your professional life later on. Build an app, and tackle the bugs that'll inevitably come with it, and that, in my opinion, will make you a far better engineer. Still, solving these challenges is crucial. Why? Because you most companies will give you these challenges during their interview round, and having lots of practice is a good thing.<br />CS is one of those fields where the interviews are generally tougher than the actual job, so it makes sense that you'd want to practice under the same constraints. But, that's where the differences between Leetcode and Codechef become even more apparent.<br />When you're on Codechef, solving a challenge, your target is to get an AC (Solution Accepted). You're much less concerned with how you're solving the problem, and whether you're using the correct methodology or not. On the other hand, interviews are not competitions. You'll need to design a good, readable solution that can be communicated properly to the interviewer. Leetcode will help there, since the problems are more foundational in nature, and the focus is on writing clean, structured code without constraints. In an interview you'll probably have some time limit, but even then, the environment will be much more relaxed. You'll get hints from the interviewer along the way, and communication is vital. All this gets lost when you're doing pure CP on Codechef or Codeforces.</p>
<h3 id="why-i-dont-do-cp">Why I don't do CP</h3>
<p>Because I'd rather be building apps. I like building apps. Even since I played GTA:SA for the first time, I wanted to build cool apps and games, and that's what I do. That's precisely why I'm in this field, and I feel like building is a more appropriate use of my time.</p>
<h3 id="difficulties">Difficulties</h3>
<p>We're going through campus placements right now, and the lack of practice in solving coding challenges is really apparent to me. I can get through the easier, foundational problems just fine, but when deep concepts like Dynamic Programming and Backtracking are required, I'm of no use. This hurts my chances, I know, and I'm always thinking about doing more Leetcode and solving some challenges, but then again, I can never bring myself to actually do it. Why? First, because they're difficult, and quite demotivating. Second, I am lazy. That's it. It's not like you can't build apps and solve one problem on Leetcode daily.</p>
<h3 id="my-recommendation">My recommendation</h3>
<p>If you like competitive programming, good for you. You've got a bright future ahead. If you're like me, and think that sport programming is not a good fit for you, build. Keep building good apps. Contribute to open source. There are companies now that hire on the basis of take home assignments and how you tackle an engineering problems rather than having your draw an algorithm on a board.</p>
<h3 id="but">BUT</h3>
<p>If you want to get into Big Tech, I have some sad news for you: you'll need to do Leetcode. There's absolutely no other way around it. The way their assessments work is centerd on solving these algorithmic challenges, where they want to see how well you can design a solution to a problem. Aim to do anywhere between 1 to 3-4 problems on Leetcode daily and you'll be fine. Just keep in mind, that this is not a competition. Focus on writing clean, structured, modular code, and try explaining your solution to people. That'll help more than doing CP.</p>
<h2 id="in-conclusion">In Conclusion,</h2>
<p>Doing sport programming boils down a lot to your personal preferences. If you find it stimulating and interesting, by all means, devote your time to it. But, don't do it just because you think it's a gateway to big tech companies. There are other ways to get in, and besides, being a good engineer is far more important in my eyes than working at MAANG. That being said, do try to solve a couple questions daily on Leetcode, but remember to build a clean, modular solution with good conventions. They'll certainly help with your technical interviews.</p>
]]></content:encoded></item><item><title><![CDATA[Why it's completely fine to not work at a MAANG]]></title><description><![CDATA[Perhaps a little bit of clarification is in order: I do want to work at Google. They have an amazing engineering culture and excellent tooling for people like me. I'm just not aiming for it like it's the end of the world and the sole goal of my life....]]></description><link>https://blog.sanskar10100.dev/why-i-do-not-want-to-work-for-maang</link><guid isPermaLink="true">https://blog.sanskar10100.dev/why-i-do-not-want-to-work-for-maang</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[engineering]]></category><category><![CDATA[Career]]></category><category><![CDATA[jobs]]></category><dc:creator><![CDATA[Sanskar Agrawal]]></dc:creator><pubDate>Mon, 08 Nov 2021 16:37:54 GMT</pubDate><content:encoded><![CDATA[<p>Perhaps a little bit of clarification is in order: I do want to work at Google. They have an amazing engineering culture and excellent tooling for people like me. I'm just not aiming for it like it's the end of the world and the sole goal of my life.</p>
<h3 id="so-the-first-thing-that-youd-obviously-ask-why-tho">So, the first thing that you'd obviously ask: Why tho?</h3>
<p>Why. Alright.<br />The first and the last thing that I wanna be is a good engineer that creates value. That's it. You don't necessarily have to be at MAANG to do that. You could do that in a homegrown startup or a small to medium company too. <strong>In my opinion, you'd get to create even more value there.</strong></p>
<h3 id="how-do-i-create-more-value-while-not-working-at-maang">How do I create more value while not working at MAANG?</h3>
<p>Let's first go ahead and take a peek at my perception of what this so-called term <em>value</em> means.<br />When I say I'm creating value, in my mind, I'm doing one of the following things:</p>
<ul>
<li>I'm writing good quality code that will help users and earn my company a good profit.</li>
<li>I'm building an internal tool that'll help me and my fellow developers</li>
<li>I'm helping someone understand something</li>
</ul>
<p>So any positive stuff like that falls into the "creating value" bracket for me.
<strong>Now,</strong> coming to the crux of the question, how do you create more value out of MAANG?</p>
<ul>
<li>You get to decide on the direction of the product more freely</li>
<li>You get to build and roll out cool new features rapidly</li>
<li>You can build tools for automation, auditing, and monitoring</li>
<li>You'll almost be forced to use your engineering skills to solve very unique challenges.</li>
</ul>
<h3 id="everything-comes-at-a-price">Everything comes at a price</h3>
<p>You'll probably get less pay, fewer perks, and most probably longer working hours (if you're working at a startup building its first product, bid your work-life balance goodbye). But if you're ultimately happy while working, it won't matter.</p>
<h3 id="my-personal-take">My personal take</h3>
<p>The world's top engineering talent works at MAANG, but what most people forget is that most of these people were already the world's topmost engineers before they joined. They were hired at MAANG because they had a knack for good engineering, not because they could solve 40 algorithm questions in 10 minutes. Right now, joining MAANG in India has become a bit like JEE. People are mindlessly completing courses and solving DSA questions like idiots, and in the process forget what Engineering is really about. While I myself occasionally enjoy an algorithmic puzzle every now and then, I find more joy in building cool apps.</p>
<p>Engineering is about building good products that can help people. You can do that anywhere, MAANG or not.</p>
<hr />
<p>If you liked this article even the slightest bit, you'd probably want to give this one a read:  <a target="_blank" href="https://stackoverflow.blog/2021/02/17/the-pros-and-cons-of-being-a-software-engineer-at-a-big-tech-company/">The pros and cons of being a software engineer at a BIG tech company</a>. It's much better written than mine.</p>
]]></content:encoded></item></channel></rss>