<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Suraj Sau on Medium]]></title>
        <description><![CDATA[Stories by Suraj Sau on Medium]]></description>
        <link>https://medium.com/@surajsau?source=rss-caea8550ebc------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*hrFfkh7pLAF0xGcs.jpg</url>
            <title>Stories by Suraj Sau on Medium</title>
            <link>https://medium.com/@surajsau?source=rss-caea8550ebc------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 10:22:05 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@surajsau/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Parallax Effect with SensorManager using Jetpack Compose]]></title>
            <link>https://proandroiddev.com/parallax-effect-with-sensormanager-using-jetpack-compose-a735a2f5811b?source=rss-caea8550ebc------2</link>
            <guid isPermaLink="false">https://medium.com/p/a735a2f5811b</guid>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[parallax]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Suraj Sau]]></dc:creator>
            <pubDate>Fri, 27 May 2022 10:06:17 GMT</pubDate>
            <atom:updated>2022-05-27T16:45:28.868Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PCrihlFLPUmSGfes3YUHlw.jpeg" /><figcaption>Alessio Soggetti・<a href="https://unsplash.com/photos/zxcBR3zNc7I">Unsplash</a></figcaption></figure><p>Recently, an implementation of stunning Parallax effect in SwiftUI by <a href="https://medium.com/u/3c8d33901e82">Philip Davis</a> has been making rounds in the Android Community too.</p><p>So, I decided to try implementing the same using Jetpack Compose instead. The results turned out pretty decent and also well received by our lovely Android Twitter Community. 😁</p><h3>suraj・🍜・🍙 on Twitter: &quot;Tried it on #JetpackCompose !🥳 #AndroidDev @JimSproch https://t.co/Ic1LMruPzD pic.twitter.com/vxTJDiF1Wm / Twitter&quot;</h3><p>Tried it on #JetpackCompose !🥳 #AndroidDev @JimSproch https://t.co/Ic1LMruPzD pic.twitter.com/vxTJDiF1Wm</p><h3>Implementation</h3><p>The following explanation is completely based on this <a href="https://gist.github.com/surajsau/b178e2646a1240f883774811e15bbb6a">gist</a>.</p><h4>Fetching Sensor data</h4><p>The @Composable views will be repositioned according to the orientation of the device. And we’ll be needing the orientation values along the ‘Pitch’ and the ‘Roll’ axes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/714/1*OslQ2UR2uAgGA1UR6hTV9A.png" /></figure><p>I’ve referred to this StackOverflow reply for my implementation of fetching the Pitch and Roll values.</p><p><a href="https://stackoverflow.com/a/6804786">Android phone orientation overview including compass</a></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/20f8dd6a1f44e9163858071d5c704f8f/href">https://medium.com/media/20f8dd6a1f44e9163858071d5c704f8f/href</a></iframe><h4>Register SensorManager listeners</h4><p>Using DisposableEffect we’ll be initialising the SensorManager and also register listeners for the <a href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_GRAVITY">GRAVITY</a> &amp; <a href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_MAGNETIC_FIELD">MAGNETIC_FIELD</a> sensors.</p><p>Finally the Pitch and Roll are received within the @Composable using Channel.receiveAsFlow().collect().</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ddd29697e990270ceb36546b81b2d8fc/href">https://medium.com/media/ddd29697e990270ceb36546b81b2d8fc/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4dae77f0d649f8c35bcaa1b9c78f95c7/href">https://medium.com/media/4dae77f0d649f8c35bcaa1b9c78f95c7/href</a></iframe><p>One may say that we could’ve LaunchedEffect instead since we’re only collecting our sensor’s data Flow. I’ll explain why I went with a DisposableEffect in the end (it has something to do with onDispose{}).</p><p>Now, once we have the Pitch and Roll values from our sensor, we’ll now be looking into how we can reposition various @Composable views to achieve the parallax effect. <a href="https://medium.com/u/3c8d33901e82">Philip Davis</a> has himself graciously hinted at his implementation:</p><ul><li>repositioning the Image Card</li><li>repositioning the Glow Shadow (reverse of the Card). I used <a href="https://github.com/philipcdavis/swiftui-photoTintedShadows-example">Philip’s Glow Shadow implementation</a> as reference for this.</li><li>showing a subtle Card edge when tilted, for the pronounced 3D effect</li><li>adding a parallax to Image inside (reverse of the Card)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VF3bZtwiowN_CNjbt0odxQ.png" /><figcaption>View repositioning directions based on title direction</figcaption></figure><h4>Image Card</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/042a30ae697b001b24125b58a7433810/href">https://medium.com/media/042a30ae697b001b24125b58a7433810/href</a></iframe><p>[1] we’re using Modifier.offset to reposition our @Composable views.</p><p>[2] Roll is along the y-axis while Pitch is along the x-axis. So, the x-offset will be decided by the roll while y-offset by the pitch.</p><p>[3] We bring about the inside Image’s parallax effect by adjusting the horizontalBias of @Composable Image() alignment parameter.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*uiQE8QO9q0Hftq-AGnkOIg.gif" /><figcaption>Image Card’s motion along the devices tilted orientation</figcaption></figure><h4>Glow Shadow</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a55139e28a0e4ff37f74a7f8a4d94c77/href">https://medium.com/media/a55139e28a0e4ff37f74a7f8a4d94c77/href</a></iframe><p>[1] Glow Shadow’s offset will reposition quicker than the Image Card and in the opposite direction.</p><p>[2] It’s size will be smaller than the Image Card.</p><ul><li>When the device is kept on a Flat surface (all orientation values close to 0), the glow shadow should not be visible.</li><li>The darker portion of shadows are generally smaller than the actual object.</li></ul><p>[3] Instead of a traditional shadow, use Modifier.blur() to make the Image Card appear translucent which is letting in light onto the surface.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*rNHCZPoPjj5EXS8KrEVeZQ.gif" /><figcaption>Glow Shadow’s motion along with the Image Card</figcaption></figure><h4>Card Edge</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1ebb914ab6553428c5aa6690a258da5e/href">https://medium.com/media/1ebb914ab6553428c5aa6690a258da5e/href</a></iframe><p>[1] Card Edge’s offset repositioning will be slightly flower than Image Card.</p><ul><li>The edge shouldn’t be visible when kept on a Flat surface.</li><li>The edge should ‘just’ be visible when the device is tilted to give the impression of a thin 3D card.</li></ul><p>[2] It’s size will be same of the Image Card.</p><figure><img alt="Demonstration of Card Edge" src="https://cdn-images-1.medium.com/max/300/1*QsM1lUG0cn_JJoJH49vzgg.gif" /><figcaption>Card Edge’s motion along with the Image Card</figcaption></figure><h3>Points of Caution</h3><h4>Unregister cancel fetching of sensor data in DisposableEffect.onDispose{..}</h4><p>We must prevent unnecessary leaking of SensorManager when this view is removed from the composition. That is the reason why I used DisposableEffect instead of LaunchedEffect since we get this convenient onDispose{} callback where we can clean up any unnecessary data upon leaving composition.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2285c63b234adcf0d819ee7a9bcf84ae/href">https://medium.com/media/2285c63b234adcf0d819ee7a9bcf84ae/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/18ed69072649ad3686b80a1eee723af6/href">https://medium.com/media/18ed69072649ad3686b80a1eee723af6/href</a></iframe><h4>Use the right Modifier.offset method</h4><p>Jetpack Compose provides two kinds of Offset modifiers:</p><ul><li>.offset(x: Dp = 0.dp, y: Dp = 0.dp)</li><li>.offset(offset: Density.() -&gt; IntOffset)</li></ul><p>While these do the same thing i.e., set positional offset to the @Composable view, <strong>IF we are going to use dynamic offsets (for example, offsets changed due to certain animation or user action etc.) instead of fixed offsets, we should always use </strong><strong>.offset(offset: Density.() -&gt; IntOffset </strong>i.e., the lambda version. The reason being, the latter method <strong>avoids recomposition when the offset is changing values</strong>, thus significantly improving the performance. Even <a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.ui.Modifier).offset(kotlin.Function1)">the documentation</a> says,</p><blockquote>This modifier is designed to be used for offsets that change, possibly due to user interactions. It avoids recomposition when the offset is changing, and also adds a graphics layer that prevents unnecessary redrawing of the context when the offset is changing.</blockquote><h3>Concluding</h3><p>While this isn’t the most performance efficient implementation, hope this can help as a handy reference for the same.</p><p>Jetpack Compose and Swift UI are the best tools to realise high quality visual effects in such a short period time. I certainly believe that these two platforms will empower Mobile Developers to create beautiful experiences for the users. 🥳</p><p>Do share your solutions, suggestions and feedback on <a href="https://twitter.com/_SUR4J_">Twitter</a>. Can’t wait to see what the community comes up with next 😁 It’s a nice time to be an Mobile Developer.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a735a2f5811b" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/parallax-effect-with-sensormanager-using-jetpack-compose-a735a2f5811b">Parallax Effect with SensorManager using Jetpack Compose</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[On-device Google Translate with Jetpack Compose & MLKit]]></title>
            <link>https://proandroiddev.com/on-device-google-translate-with-jetpack-compose-mlkit-7a48f5b11948?source=rss-caea8550ebc------2</link>
            <guid isPermaLink="false">https://medium.com/p/7a48f5b11948</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Suraj Sau]]></dc:creator>
            <pubDate>Thu, 02 Dec 2021 14:47:43 GMT</pubDate>
            <atom:updated>2021-12-03T00:29:37.111Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sCVEKkjoocwcAAeqqbPa-Q.jpeg" /></figure><p>In this article will do a code walkthrough of implementing Google Translate’s character recognition (along with text translation) in Jetpack Compose using MLKit’s DigitalInk and On-device Translation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*bTo9HFo6tTX6S6T90aee5Q.gif" /></figure><p>Here’s the source code of my implementation.</p><p><a href="https://github.com/surajsau/ML-Android">GitHub - surajsau/ML-Android: Implementing various ML usecases in Android</a></p><h3>Drawing on Canvas</h3><p>Let’s setup a @Composable Canvas where users will draw the characters on screen.</p><p>This is a standard use-case where we intercept the touch events on the View and translate it as a Path on the canvas. Extending it to Jetpack Compose,</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/80509e9ba340d0d9f1ad7eef6b7dbd42/href">https://medium.com/media/80509e9ba340d0d9f1ad7eef6b7dbd42/href</a></iframe><ul><li>Using <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilter.android.kt;l=69?q=pointerinterop&amp;sq=">Modifier.pointerInteropFilter</a> we get the MotionEvent similar to that obtained in android.view.View OnTouchListener.</li><li>For convenience I’ve created sealed class DrawEvent which will be mapped to the corresponding MotionEvent.</li><li>remember the Path object, since it’ll not be changing over recompositions. Setting value to mutableStateOf&lt;DrawEvent?&gt; within the callback will trigger recompositions where either a new subpath is created or the current one is modified.</li><li>path.lineTo() would also do instead of path.quadraticBezierTo(). I’ve gone with a quadratic subpath only to smoothen out sharp corners.</li><li>Finally, at the end of each composition calldrawPath()</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/280/1*DBb7tICdjGwKfCiYogLA3Q.gif" /></figure><h3>MLKit’s Digital Ink</h3><p><a href="https://developers.google.com/ml-kit">MLKit by Google</a> provides <a href="https://developers.google.com/ml-kit/vision/digital-ink-recognition">Digital Ink</a>, a ready-to-use package for character recognition for over 300+ languages. It is also powers the handwriting recognition for services like Gboard, Google Translate.</p><p>The main components that we will be primarily dealing with are <strong>Ink.Stroke</strong>, <strong>DigitalInkRecognitionModel</strong> and <strong>DigitalInkRecognizer</strong>.</p><p>Let’s initialise them as we discuss their purpose.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/105cd070a12ce697cfb8e776198930a9/href">https://medium.com/media/105cd070a12ce697cfb8e776198930a9/href</a></iframe><ul><li><strong>Ink.Stroke : </strong>this data object contains the list of coordinates (Ink.Point) information of the drawn shape which is the input to our RecognitionModel. MotionEvent coordinates obtained in the callback are then added to this.</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ebb104f9754150e6736560b2c544fd70/href">https://medium.com/media/ebb104f9754150e6736560b2c544fd70/href</a></iframe><ul><li><strong>DigitalInkRecognitionModel</strong> : this is the ML model corresponding to the specific language whose characters are to be recognized. The builder takes in a ‘modelIndentifier’ to specify the language of character recognition. For this example we’ll be using the Japanese Language model, so we pass DigitalInkRecognitionModelIdentifier.<strong>JA</strong></li><li><strong>DigitalInkRecognizer</strong> : this takes in the generated Ink.Stroke as input to the ML model and outputs a result of characters predicted by the model.</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/304ffa79abfd53a6670eaf349b8aa6f7/href">https://medium.com/media/304ffa79abfd53a6670eaf349b8aa6f7/href</a></iframe><h4>Checking Model’s availability</h4><p>Before we use Digital Ink, for it to support language-specific character recognition the language specific ML model data (roughly, 20MB per language) needs to be downloaded and available locally.</p><p>You can checkout <a href="https://developers.google.com/ml-kit/vision/digital-ink-recognition/android#managing_model_downloads">Digital Ink | Managing model downloads</a> for more details. Here’s my code for reference.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7ad132296c05ab0fe8d1544b50f59b3b/href">https://medium.com/media/7ad132296c05ab0fe8d1544b50f59b3b/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1e6fbb50274d71690ed7fe6849787487/href">https://medium.com/media/1e6fbb50274d71690ed7fe6849787487/href</a></iframe><h3>Building Ink.Stroke &amp; Recognizing</h3><p>With a drawable Canvas and MLKit model ready, let’s add callbacks to handle building Ink.Stroke and triggering Recognizer.recognize() when done.</p><p>For convenience I’ve added sealed class DrawEvent mapped to the corresponding MotionEvent.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a7ee63f881051cf979c6e071247e0a17/href">https://medium.com/media/a7ee63f881051cf979c6e071247e0a17/href</a></iframe><p>Inside the onDrawEvent callback,</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/48d13e759dd6d8addf9f810d570da14d/href">https://medium.com/media/48d13e759dd6d8addf9f810d570da14d/href</a></iframe><ul><li><strong>MotionEvent.ACTION_DOWN</strong>, <strong>MotionEvent.ACTION_MOVE</strong>: This indicates that the user is touching the canvas to draw. We add the obtained event coordinates to Ink.Stroke.Builder</li><li><strong>MotionEvent.ACTION_UP</strong>: This indicates that user has stopped touching the canvas thus signalling end of drawing. We can now build the generated Ink.Stroke and pass it to Recognizer.recognize(). <br>In onSuccess we get back the prediction results (RecognitionResult.candidates) for display. In onComplete we reset the Ink.Stroke.Builder to start taking in coordinates of the next character to be drawn.</li></ul><p>At this point the above code snippet runs fine. However, in the example below the predictions results aren’t correct at all.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/280/1*eU4RlV5y0VMJlXGq7FQpOQ.gif" /><figcaption>without debounce</figcaption></figure><p>While some characters can be written in a single stroke, characters like i, t &amp; complex characters like 終, even in cursive, cannot be written in one stroke. Every MotionEvent.ACTION_UP calls Recognizer.recognize() where we reset Ink.Stroke after receiving the results. So instead of passing coordinates of all subpaths combined, we’re calling .recognize() for each individual subpath.</p><p>This issue can be mitigated, by adding a ‘<a href="https://rxmarbles.com/#debounce">debounce</a>’ like behavior which will prevent successive .recognize() calls for a specific time period after MotionEvent.ACTION_UP.</p><h4>Adding debounce</h4><p>To implement debounce, we have to cancel out all successive calls to Recognizer.recognize()for a specific time period (the debounce offset) after MotionEvent.ACTION_UP. There might be better ways to implement it, however this is the solution I came up with.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c1f965fed309bce1fe12b0373388355c/href">https://medium.com/media/c1f965fed309bce1fe12b0373388355c/href</a></iframe><p>We add a delay(offset) within a coroutine before calling Recognizer.recognize() in MotionEvent.ACTION_UP. We can then cancel the launched coroutine in the next MotionEvent.ACTION_DOWN.</p><p>If user touches the Canvas within the specified delay, the launched coroutine is cancelled thus avoiding Recognizer.recognize(). This will allow users to add multiple strokes as long as they start drawing the next stroke (trigger the next MotionEvent.ACTION_DOWN within this specific offset.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/280/1*w4MD6aF-I-NRwRZVzvHLeQ.gif" /></figure><h3>MLKit’s On-device Translation</h3><p>Implementing text translation is pretty much similar as above, instead we’ll be using MLKit’s <a href="https://developers.google.com/ml-kit/language/translation">On-device Translation</a>. Like the name suggests the translation model runs on your device without requiring internet, which is why it might be slightly inaccurate at times.</p><p>Like DigitalInkRecognizer in Digital Ink, we’ve <strong>Translator</strong> here. This handles both downloading the translation as well as translation. While initialising Translator we’ve to specify the source and target languages. Rest of the implementation is pretty self explanatory.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3421e5f192d858fd4391d23a47cd6e49/href">https://medium.com/media/3421e5f192d858fd4391d23a47cd6e49/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/280/1*qeoGormYsTTDSOLMQCJk_w.gif" /></figure><h3>Finally .close() everything</h3><p>Even in the documentation it is mentioned clearly to .close() the Translator &amp; Recognizer objects if no longer used. We can add a lifecycle observer and close those objects in LifeCycle.Event.ON_STOP.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f8c665fe3b6fc6f509339a0d86a9766e/href">https://medium.com/media/f8c665fe3b6fc6f509339a0d86a9766e/href</a></iframe><p>Thank you for sticking along this pretty long read! 😅</p><p><strong>This article is a part of </strong><a href="https://qiita.com/advent-calendar/2021/goodpatch"><strong>Goodpatch’s Advent Calendar 2021</strong></a>🎄 We have a series of wonderful articles lined up for you this holiday season. While most of the articles will be in Japanese, if you are interested in going through the contents, you can use <a href="https://www.deepl.com/en/translator">DeepL translator</a>. It gives pretty accurate translations for technical articles in Japanese ♨️</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7a48f5b11948" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/on-device-google-translate-with-jetpack-compose-mlkit-7a48f5b11948">On-device Google Translate with Jetpack Compose &amp; MLKit</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Basic Drag-n-Drop in Jetpack Compose]]></title>
            <link>https://proandroiddev.com/basic-drag-n-drop-in-jetpack-compose-a6919ba58ba8?source=rss-caea8550ebc------2</link>
            <guid isPermaLink="false">https://medium.com/p/a6919ba58ba8</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[kotlin]]></category>
            <dc:creator><![CDATA[Suraj Sau]]></dc:creator>
            <pubDate>Tue, 31 Aug 2021 11:51:33 GMT</pubDate>
            <atom:updated>2021-09-02T17:48:30.365Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MUicVu3Yj817nv4cfRujAQ.png" /></figure><p>Drag and Drop reordering in Recyclerview can be achieved with <a href="https://developer.android.com/reference/androidx/recyclerview/widget/ItemTouchHelper">ItemTouchHelper</a> (checkout implementation <a href="https://www.journaldev.com/23208/android-recyclerview-drag-and-drop">reference</a>).</p><p>Jetpack Compose v1.0 has been out for quite some time now 🎉</p><h3>Android Developers on Twitter: &quot;🚀 Version 1.0 of #JetpackCompose is here! See how Android&#39;s modern, native UI toolkit is fully stable and ready for you to adopt in production ↓ https://t.co/IF2Z281hWn / Twitter&quot;</h3><p>🚀 Version 1.0 of #JetpackCompose is here! See how Android&#39;s modern, native UI toolkit is fully stable and ready for you to adopt in production ↓ https://t.co/IF2Z281hWn</p><p>At the time of writing this article in v1.0, Compose’s RecyclerView counterpart LazyColumn・LazyRow don’t have an implementation for Drag and drop, though <strong>Support Drag and Drop</strong> is very well a part of the <a href="https://developer.android.com/jetpack/androidx/compose-roadmap">Compose Roadmap</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*dybi8SG9Bi4Wk9v4B2E5sg.png" /><figcaption><a href="https://developer.android.com/jetpack/androidx/compose-roadmap">https://developer.android.com/jetpack/androidx/compose-roadmap</a></figcaption></figure><p>In the meantime, I tried implementing basic Drag-n-Drop with the existing APIs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/324/1*geqnoZVDjoEDYOeUEz1saw.gif" /><figcaption>Drag-n-Drop in LazyList</figcaption></figure><p><a href="https://gist.github.com/surajsau/f5342f443352195208029e98b0ee39f3">Check this gist</a> for the detailed and entire implementation of Drag-n-Drop.</p><p>In this article I’ll walk you over the key portions of code to help you understand the implementation.</p><h3>Drag on Long-press</h3><p>LazyListacts similar to RecyclerView, where reordering of data doesn’t reorder the View instances, instead recycles to reuse them.</p><p>So in this implementation, instead of applying Long-press gesture detector to the Modifier of each individual list element, which may require complex handling of View-Data mapping, we’ll add it to the Modifier of the LazyList itself. We can then translate the captured drag event of the LazyList onto the individual elements.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/105c78929159a931b14a1274504ea0b4/href">https://medium.com/media/105c78929159a931b14a1274504ea0b4/href</a></iframe><h4>Determining the element to be dragged</h4><p>With LazyColumn’s LazyListState keeping track of the visible elements (check <a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo#visibleItemsInfo()">LazyListState.visibleItemsInfo</a>), we determine the element to be dragged by checking whether the drag’s start offset (in onDragStartcallback) lies within the bounds of any of the elements.</p><p>This will help us ‘remember’ the initial offset from where drag event had begun throughout further recompositions.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3d65e1d56c180c534be71035a4259c02/href">https://medium.com/media/3d65e1d56c180c534be71035a4259c02/href</a></iframe><p>With the initial offsets and element to be dragged determined in onDragStart handled, let’s move onto onDrag when the actual drag happens.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/324/1*ZlPxDFCMk5semHPtvhAVpA.gif" /><figcaption>Translate LazyList’s onDrag offset to an element</figcaption></figure><h3>While Dragging</h3><p>Once the dragged element has been identified, we now determine the element over which the dragged element is currently hovering on. We then shift it up or down (based on drag direction) and shift the item in the List` or `Array` also to trigger recomposition of the `LazyList`.</p><h4>Translating the dragged element</h4><p>With the drag event’s initial offset determined, we can translate the drag event’s offset to the dragged element by the cumulative sum of offset value from the onDrag callback.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8f3e856badea3d0527321d1f608f1a36/href">https://medium.com/media/8f3e856badea3d0527321d1f608f1a36/href</a></iframe><h4>Identifying currently hovered items</h4><p>As an element is dragged, it’ll be crossing over the other elements and based on the direction, once the element crosses a particular “threshold” of the element it is currently crossing (or hovering upon), reordering of the List will be triggered.</p><p>The elements which fall beyond the top or bottom of the dragged element are not considered as hovered upon.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/99c5b456896b5b6ab690537cc97421ed/href">https://medium.com/media/99c5b456896b5b6ab690537cc97421ed/href</a></iframe><h4>Determining the element to be replaced</h4><p>Once the list of hovered elements are determined we then choose the first element, based on drag direction, whose the top or bottom (the threshold in our case) has been crossed over by the dragged element.<br>This element will be the one that the dragged element will be replacing.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ca18d7be0ae569e942ccf17b260aff5f/href">https://medium.com/media/ca18d7be0ae569e942ccf17b260aff5f/href</a></iframe><h4>Trigger reordering of List</h4><p>With the to-be-replaced element determined, we can trigger a recomposition of the LazyColumn with the List of updated positions.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d751a8eb9db13dd043b1247ef73ac0b8/href">https://medium.com/media/d751a8eb9db13dd043b1247ef73ac0b8/href</a></iframe><h3>Handling overscroll</h3><p>In any typical Drag-n-Drop reordering list, it is expected that the list autoscrolls when an element reaches either visibile thresholds of the screen.</p><p>To implement this, we first check if the dragged element has crossed either of the visible thresholds and calculate the overscolled offset. If there’s a non-zero offset, we can then initiate a scroll by calling `LazyListState.scrollBy(offset)`.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6d1316df4a35ab347d536b79a1cf7cf2/href">https://medium.com/media/6d1316df4a35ab347d536b79a1cf7cf2/href</a></iframe><h4>Checking for any overscroll</h4><p>In case of a positive draggedDistance which indicates drag in the downward direction, if the ‘bottom’ of the dragged element crosses the visible bottom threshold (check <a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo#viewportEndOffset()">viewPortEndOffset</a><a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo#viewportEndOffset()))">)</a> of the LazyList we can confirm that the element has over-scrolled beyond the visible bottom.</p><p>On the other hand, a negative value we would be looking for if the ‘top’ of the dragged element crosses the visible top threshold (check <a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo#viewportStartOffset()">viewPortStartOffset</a>) of the LazyList.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ffb09ea2a2ffa063e390c42e085f2533/href">https://medium.com/media/ffb09ea2a2ffa063e390c42e085f2533/href</a></iframe><h3>Drag Interrupted</h3><h4>Reset variables on interrupting the Drag</h4><p>On cancelling or ending the drag, all the remembered values are to be reset. so that the dragged element settles at its latest position in the LazyColumn</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d74e1fc993c14968aa9a9009f1c7163e/href">https://medium.com/media/d74e1fc993c14968aa9a9009f1c7163e/href</a></iframe><h3>Conclusion</h3><p>While this might not be the most optimal solution in terms of composition optimisation for Drag-n-Drop. Hopefully this gave a little insight into the APIs of LazyList・LazyRow provided in v1.0.</p><p>Meanwhile, I wait curiously to see how the Compose team implements the Drag-n-Drop support 😁</p><p>Happy coding 💻 and stay safe during these trying times 😷</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a6919ba58ba8" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/basic-drag-n-drop-in-jetpack-compose-a6919ba58ba8">Basic Drag-n-Drop in Jetpack Compose</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Testing deeplinks using ki]]></title>
            <link>https://proandroiddev.com/testing-deeplinks-using-ki-cd69f062fd04?source=rss-caea8550ebc------2</link>
            <guid isPermaLink="false">https://medium.com/p/cd69f062fd04</guid>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[deeplink]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Suraj Sau]]></dc:creator>
            <pubDate>Wed, 28 Apr 2021 12:59:03 GMT</pubDate>
            <atom:updated>2021-04-28T16:20:44.197Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AFIejgTe0cJsiNVFpenLhw.jpeg" /></figure><p><strong>ki</strong> is a Kotlin Interactive Shell <a href="https://blog.jetbrains.com/kotlin/2021/04/ki-the-next-interactive-shell-for-kotlin/">developed by the folks at JetBrains</a>.</p><p>One of my colleagues recently shared a<strong> </strong><a href="https://gist.github.com/thedoritos/91b086e46ea6da7c943b8fe4e910a22c"><strong>neat little Ruby script</strong></a><strong> </strong>to get an ad-hoc localhost webpage up and running with all deeplink URLs.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9b99413fa7a435ec5d5bb274f4edcdde/href">https://medium.com/media/9b99413fa7a435ec5d5bb274f4edcdde/href</a></iframe><p>We’ll be doing a similar exercise with ki.</p><p>Even though <a href="https://ktor.io/">ktor</a> would be the popular choice for this article only for simplicity sake (number of modules to be loaded to get things up and running), I’ll be using <a href="https://javalin.io/"><strong>Javalin</strong></a> as the web framework of choice.</p><h3>Let’s get started</h3><p>A very helpful feature of ki is that you can import libraries on the fly. Let’s import javalin by using the :dependsOn function. Once downloaded, import io.javalin.Javalin into your shell.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OX0C3cXVON7__Ef9cS_anQ.gif" /></figure><p>Using the :paste mode, we’ll be assigning our HTML string to a variable val html</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*L4l6MUX2YaLaJkIKNarTiw.gif" /></figure><p>With Javalin imported and HTML string ready for use lets create our server and provide an endpoint /deeplink which will provide our ad-hoc webpage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oFNZBFB1D54k1LL4ZbnJ2w.gif" /></figure><p>Then, javalin.start() with your desired port (7000 in this case), to fire up the localhost server.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*15fOzJTE86F7hWc8LfvYew.gif" /></figure><h3>Testing on emulator</h3><p>With javalin started on localhost:7000 we now verify our ad-hoc webpage on an emulator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/586/1*LErgmhmcg7zAl5WnP2HvYA.gif" /></figure><p>Use your system’s ip-address (using ifconfig on Mac) to access the webpage from your device’s browser.</p><p>In the example,</p><ul><li>running ifconfig gives 172.20.10.3 as the system’s ip-address.</li><li>since we started Javalin on the port 7000, we fire 172.20.10.3:7000/deeplink</li><li>Google External has https://www.google.com as the URL and hence opens the webpage.</li><li>The Twitter link has twitter://user?id=monsterhunter the URL which is the deeplink for the Twitter apps. Hence it opens the Twitter app instead.</li></ul><h3>Running a .kts script instead</h3><p>ki also allows you to load an already created .kts script file into the shell. So So can save some time and effort by consolidating all the above-written code into a .kts file instead.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/49a0d01bc41792e23baa9ed60b4b1168/href">https://medium.com/media/49a0d01bc41792e23baa9ed60b4b1168/href</a></iframe><p>This script exposes a Javalin object which can be used to start a localhost server.</p><p>Use :l command to load the script and then use :ls to check for the variables and methods exposed by the script.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JGd9tHOzVsRBJctOJQIVbw.gif" /></figure><p>Well we already have a way to test deeplinks using adb. Apart from notifications, web pages are another popular source of launching deeplinks. So, I just wanted you to introduce some simple utilities of the <strong>ki </strong>Kotlin-shell and hope this article could do some justice to that 😄</p><p>Happy coding 💻 and stay safe during these trying times 😷</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cd69f062fd04" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/testing-deeplinks-using-ki-cd69f062fd04">Testing deeplinks using ki</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jetpack Compose over a Cup of Hot Chocolate ☕️]]></title>
            <link>https://surajsau.medium.com/jetpack-compose-over-a-cup-of-hot-chocolate-%EF%B8%8F-9c3c29857437?source=rss-caea8550ebc------2</link>
            <guid isPermaLink="false">https://medium.com/p/9c3c29857437</guid>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Suraj Sau]]></dc:creator>
            <pubDate>Mon, 07 Dec 2020 19:43:46 GMT</pubDate>
            <atom:updated>2020-12-07T19:49:46.809Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B5_wpwoUW9FXR52JxHc3OQ.png" /><figcaption>Merry Christmas everyone! <a href="https://youtu.be/zHXaxeEYk6c">https://youtu.be/zHXaxeEYk6c</a></figcaption></figure><p><strong>This article is #8 in </strong><a href="https://qiita.com/advent-calendar/2020/goodpatch"><strong>Goodpatch’s Advent Calendar 2020</strong></a><strong>🎄.</strong></p><p>2020 has been tough for everyone. We are eagerly waiting for it to conclude and the good thing is we’re almost there! Since it’s getting pretty chilly around here, I thought it would be nice sharing snippets of few of my Jetpack Compose implementations as I sip over a Cup of Hot Chocolate playing <a href="https://www.nintendo.com/games/detail/monster-hunter-generations-ultimate-switch/">Monster Hunter XX</a> on my Switch.</p><p>Just a heads up, <strong>I will be updating this article timely</strong> as I share few more snippets in the future as Compose keeps growing. <br>Also, the snippets I am sharing aren’t optimised or perfect (they aren’t earth shattering for sure). All feedbacks and suggestions are most welcome 🙂</p><h3><strong>Parallax Scrolling 🏙</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*P1BdWRhIWn56pRjRcOomvg.gif" /></figure><p>Inspired by the <a href="https://dribbble.com/shots/3917248-Mobile-App-Prototype-in-InVision-Studio">awesome dribbble shot from Charles Patterson</a>, this snippet discusses about how to intercept scrolling offsets of Views and communicating it over to other views to create a parallax effect.</p><p><a href="https://medium.com/@theRoshogulla/compose-snippet-parallax-scroll-3bd987a5f8f3"><strong>[Check it out here]</strong></a></p><h3><strong>Neomorphic Button 🔘</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/414/1*OTUgeqh29eZDuPslNEVROQ.gif" /></figure><p>I have my own personal reservations of whether Neomorphism is a practical UI design pattern, but implementing it using Compose was interesting and relatively simple nonetheless.<br>Here I touch upon Compose’s state-based animation frameworktransition() and how to intercept user events like ‘press-and-hold’.</p><p><a href="https://medium.com/@theRoshogulla/compose-snippet-neomorphic-button-7aa7abd43c91"><strong>[Check it out here]</strong></a></p><h3><strong>Slot machine animation 🎰</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/598/1*bRpCQZSWgWYSDc11eMum-A.gif" /></figure><p>Slot machine animations will always be there where the UI implements an Odometer or a similar concept. Implementing it in Compose isn’t that difficult at all.</p><p><a href="https://medium.com/@theRoshogulla/compose-snippet-slot-machine-animation-f12621392a2b"><strong>[Check it out here]</strong></a></p><p><a href="https://qiita.com/advent-calendar/2020/goodpatch">Goodpatch’s Advent Calendar 2020</a> has a series of wonderful articles lined up for you. I understand that most of the articles are in Japanese. However, if you are interested in going through the contents, then you can use <a href="https://www.deepl.com/en/translator">DeepL translator</a>. It gives pretty accurate translations for technical articles in Japanese ♨️</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9c3c29857437" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[Compose Snippet] Slot machine Animation ]]></title>
            <link>https://surajsau.medium.com/compose-snippet-slot-machine-animation-f12621392a2b?source=rss-caea8550ebc------2</link>
            <guid isPermaLink="false">https://medium.com/p/f12621392a2b</guid>
            <category><![CDATA[slot-machines]]></category>
            <category><![CDATA[animation]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <dc:creator><![CDATA[Suraj Sau]]></dc:creator>
            <pubDate>Mon, 07 Dec 2020 19:13:58 GMT</pubDate>
            <atom:updated>2020-12-08T05:21:56.967Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RMGKLnY5ipYd3PytZDI4XA.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/196/1*7JuBw203KonnPAJpYiOKnQ.png" /><figcaption>version used for the code snippets</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/598/1*bRpCQZSWgWYSDc11eMum-A.gif" /></figure><p>The basic idea behind implementing a slot-machine animation, is running a slide-up/down animation for consecutive digits (3 to 4, 7 to 8 etc.) and upon completion, replace those with the next set of consecutive digits and continue with the animation. Repeat this until you reach the target digit.</p><pre>If target is 7 starting from 4:<br>animate(4, 5) -&gt; animate(5, 6) -&gt; animate(6, 7)</pre><p>Setting the current iteration’sfrom value, to the previous iteration’s to value, will give the impression of continuity across multiple animations.</p><p>Let’s take a look at an individual animation loop.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/21b5aedb41e214c5cbdb954b75762cba/href">https://medium.com/media/21b5aedb41e214c5cbdb954b75762cba/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/386/1*JGc5ML2w-baAZcy2S8pKwQ.gif" /><figcaption>Individual loop from 0 to 1</figcaption></figure><p>[1] The current from value would be the floor of currentSlotValue.</p><p>[2] Animating from to to is done using transition() as discussed in the <a href="https://medium.com/@theRoshogulla/compose-snippet-neomorphic-button-7aa7abd43c91">Neomorph Button</a> example. To calculate the <strong>incremental increase </strong>(delta) we subtract the floor value of currentSlotValue from its actual value.</p><p>[3] Next we define the underlying @Composable Text() y-offset value based on this incremental delta.</p><p>[4] This is the@Composable Text() for each of the digit infromvalue. It has an initial y-offset of 0.dp.</p><p>[5] This is the@Composable Text() for the digit of the correspondingto value (which is from + 1). <br>Also to keep in mind that, afrom value ending with 9 will have itsto value ending with a 0. <br>Depending on the preferred direction of scroll, its initial y-offset of is set beyond the height of the current @Composable (in this case +50.dp).</p><p>Let’s combine these individual animation loops into a single from to to animation as we’d discussed earlier. This part would be rather simple to understand since we’ve already discussed about transition() in the <a href="https://medium.com/@theRoshogulla/compose-snippet-neomorphic-button-7aa7abd43c91">Neomorphic button</a> example.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e52bd19352978250b96c55351cff52db/href">https://medium.com/media/e52bd19352978250b96c55351cff52db/href</a></iframe><p>[1] We’ve two states, from being state(0) and to being state(1). As we’ve decided to ‘animate’ between two digits, we need to use Floatfor granularity purposes. <br>Since fromcan be less than to or vice versa, the current implementation would mean, that the animation direction could reverse based on the values passed.</p><p>[2] We use a tween() AnimationSpec with a duration of 300ms per animation loop set dynamically over the entire duration of from to to.</p><h3>But currently the animation reverses?</h3><p>As mentioned above and also in the example screenshot at the beginning of this article, we see the direction of animation reversing when from &gt; to. How do we avoid this reversing and preserve a set direction of the animation?</p><p>All we have to do is, simply consider this case as an overflow over 9 and add 10 to the to value. For, example, if from = 5, to = 4, it would effectively mean from = 5, to = 14.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d51684e501bf4562d37c8fe1c0183b45/href">https://medium.com/media/d51684e501bf4562d37c8fe1c0183b45/href</a></iframe><p>And inside @Composable SlotText(), set the current to the last digit from the overflowed value.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/685ee782895a6560632c641c5d081324/href">https://medium.com/media/685ee782895a6560632c641c5d081324/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/552/1*5ZDoa5tO40H-ug2Tm9fP_A.gif" /><figcaption>Scroll direction preserved</figcaption></figure><p>Hopefully that made sense 😅</p><p>And that concludes my short snippet on implementing a Slot machine scroll animation using Jetpack Compose.</p><p>My implementation may not be the most optimised or perfect, but hopefully it could help you with few references to Compose implementations.</p><p>I’m documenting all my snippets at a single place. Check out <a href="https://medium.com/@theRoshogulla/jetpack-compose-over-a-cup-of-hot-chocolate-%EF%B8%8F-9c3c29857437">Jetpack Compose over a Cup of Hot Chocolate ☕️</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f12621392a2b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[Compose Snippet] Parallax Scroll ]]></title>
            <link>https://proandroiddev.com/compose-snippet-parallax-scroll-3bd987a5f8f3?source=rss-caea8550ebc------2</link>
            <guid isPermaLink="false">https://medium.com/p/3bd987a5f8f3</guid>
            <category><![CDATA[animation]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[parallax-scrolling]]></category>
            <dc:creator><![CDATA[Suraj Sau]]></dc:creator>
            <pubDate>Mon, 07 Dec 2020 18:52:46 GMT</pubDate>
            <atom:updated>2020-12-16T15:56:58.139Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/758/1*W1S11LgvSW3_HpZGO4xqMw.jpeg" /><figcaption>Courtesy: <a href="https://www.andulfgames.com/2017/12/17/parallax-scrolling-background/">https://www.andulfgames.com/2017/12/17/parallax-scrolling-background/</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/196/1*7JuBw203KonnPAJpYiOKnQ.png" /><figcaption>version used for the code snippets</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*P1BdWRhIWn56pRjRcOomvg.gif" /></figure><p>Giving a parallax effect to scrolling views involves a simple concept.</p><p>With one view scrolling at the User’s intended speed, the rest of the views should scroll at relatively different rates, in the same direction.</p><p>In this case, we’ve a horizontal scroll view with Cards in them. As we scroll horizontally, we intercept the scroll offset of the scroll view and programatically set the displacement offset of the Image inside the Cards.</p><p>Our Card view @Composable will need the Scroll view’s x-offset on scrolling.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ab4f549060a76c2cff366e629a8b3229/href">https://medium.com/media/ab4f549060a76c2cff366e629a8b3229/href</a></iframe><p>[1] @Composable Image() alignment’s x-bias is responsible for its positioning within the @Composable ItemCard() . One can adjust this value as per requirement to increase or reduce the displacement speed relative to the scroll view’s scroll speed.</p><h3>rememberScrollState()</h3><p>Now, we’ll need to trigger @Composable ItemCard() recomposition in sync with the scroll view’s scroll.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/11c92c2827b595c86a6287b3fb85e46c/href">https://medium.com/media/11c92c2827b595c86a6287b3fb85e46c/href</a></iframe><p>[1] Compose provides a convenient <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt;l=82?q=rememberScrollState&amp;sq=">rememberScrollState()</a> to locally ‘remember’ any scroll’s scroll-offset. Here’s a snippet from its documentation,</p><pre><em>Create and [remember] the [ScrollState] based on the currently appropriate scroll configuration to allow changing scroll position or observing scroll behavior.</em></pre><p>Consider this just as a remember { mutableStateOf(..) } State object. Any child @Composable dependent on it will have its recomposition triggered whenever ScrollState changes value. In this way, we are able to communicate the parent’s scroll-offset to the children.</p><p>[2] We are using @Composable ScrollableRow() as the scroll container for the current example. However, any custom @Composable whose Modifier.horizontalScroll(state=..) or Modifier.verticalScroll(state=..) implemented will work just fine.</p><p>There you have it. A short snippet on how to listen to scroll offsets and achieve parallax scrolling using Jetpack Compose.</p><p>My implementation may not be the most optimised or perfect, but hopefully it could help you with few references to Compose implementations.</p><p>I’m documenting all my snippets at a single place. Check out <a href="https://medium.com/@theRoshogulla/jetpack-compose-over-a-cup-of-hot-chocolate-%EF%B8%8F-9c3c29857437">Jetpack Compose over a Cup of Hot Chocolate ☕️</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3bd987a5f8f3" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/compose-snippet-parallax-scroll-3bd987a5f8f3">[Compose Snippet] Parallax Scroll 🏙</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[Compose Snippet] Neomorphic Button ]]></title>
            <link>https://proandroiddev.com/compose-snippet-neomorphic-button-7aa7abd43c91?source=rss-caea8550ebc------2</link>
            <guid isPermaLink="false">https://medium.com/p/7aa7abd43c91</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[animation]]></category>
            <category><![CDATA[neomorphism]]></category>
            <dc:creator><![CDATA[Suraj Sau]]></dc:creator>
            <pubDate>Mon, 07 Dec 2020 18:41:36 GMT</pubDate>
            <atom:updated>2020-12-15T20:32:00.040Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LTL6u00DztNZGXY22U_mDQ.png" /><figcaption>Courtesy: <a href="https://www.pinterest.jp/pin/395753886007567718/">https://www.pinterest.jp/pin/395753886007567718/</a></figcaption></figure><h3>[Compose Snippet]Neomorphic Button 🔘</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/196/1*7JuBw203KonnPAJpYiOKnQ.png" /><figcaption>version used for the code snippets</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/414/1*OTUgeqh29eZDuPslNEVROQ.gif" /></figure><p>Implementing Neomorphic views (hopefully I haven’t slaughtered the terminology here 😅) is explained to a greater clarity in this <a href="https://medium.com/@fornewid/neumorphism-in-android-9cf15e2122dc">awesome article by gihub/fornewid</a>.</p><p>As a TLDR; to the article, the impression of smooth elevation and subtle depression upon ‘pressing’ is achieved by animating two separate blur-edged shapes of two colors, a lighter shade and a darker shade of the general surface color.</p><p>Below is a demonstration of the same implementation above but, without the center gray circle at the top.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/414/1*lNzJTv9M5T8x0qKcFXoQ_g.gif" /></figure><p>Basically, we diagonally displace the two blurred circles relative to the center one.</p><p>The diagonal displacement is achieved by animation frameworks provided in Compose. Another thing to notice here is that, the displacement animations are triggered only when we ‘touch’ the view or remove our ‘touch’. This calls for a little different approach than simply triggering the animation in onClick() callbacks.</p><h3>Modifier.pressIndicatorGestureFilter()</h3><p>With Compose we have a <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/">variety of GestureFilters</a> at our disposal. In this particular case we’re going to use<a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/gesture/PressIndicatorGestureFilter.kt">PressIndicatorGestureFilter</a> , whose documentation reads,</p><pre><em>This gesture detector has callbacks for when a press gesture starts and ends for the purposes of displaying visual feedback for those two states.</em></pre><pre>fun Modifier.pressIndicatorGestureFilter(..)</pre><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8092cf8d33e685ed186cd99a935b27b4/href">https://medium.com/media/8092cf8d33e685ed186cd99a935b27b4/href</a></iframe><p>Going through the above snippet,</p><p>[1] buttonState serves the purpose of triggering a recomposition of @Composable NeomorphButton() when its value is changed.</p><p>[2] Given a change in buttonState value, we need to trigger an animation from the previous state to the current value of buttonState, hence the toggle logic.</p><p>[3] For proper understanding of implementingtransition() in Compose, I would highly recommend this <a href="https://www.raywenderlich.com/13282144-jetpack-compose-animations-tutorial-getting-started">Raywenderlich tutorial</a>. As a summary, transition() is Compose’s state-animation framework. In this case, transition from PRESSED to IDLE or vice versa, will alter buttonStateProgress value ‘using’ the predefined <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationSpec.kt;drc=2bca47614ca08f7e4f822c7aabf2188b11003e4e;l=93">tween()</a> animator.</p><p>[4] As we toggle the buttonState on interacting with @Composable NeomorphButton(), we have to toggle the ‘from’ &amp; ‘to’ states of transition() This will ensure that the animation reverses on toggling buttonState.</p><p>[5] Implementing Modifier.pressIndicatorGestureFilter(), we listen to the onStart() and onStop() callbacks and alter buttonState value accordingly.</p><h3>Pass the TransitionState</h3><p>The TransitionState obtained from defining the transition() in @Composable NeomorphButton() is then passed on as a parameter to @Composable CircleNeomorph(). As per documentation, any child @Composable depending on a TransitionState will have their recomposition triggered whenever, theTransitionState value changes.</p><pre><em>[transition] takes a transition definition, a target state and child composables.<br>These child composables will be receiving a [TransitionState] object as an argument, which captures all the current values of the animation. Child composables should read the animation values from the [TransitionState] object, and apply the value wherever necessary.</em></pre><pre>@Composable<br>fun &lt;T&gt; transition(..): TransitionState</pre><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ad10e06b78cd8df6a5689d74623258b2/href">https://medium.com/media/ad10e06b78cd8df6a5689d74623258b2/href</a></iframe><p>[1] From the TransitionState passed on, we obtain the buttonStateProgress (animated in its parent@Composable NeomorphButton()) which is used to calculate the x &amp; y alignment biases of the blurred circles. As the value of buttonStateProgress animates from PRESSED to IDLE or vice versa, so do the alignment biases.</p><p>[2] This is the upper blurred circle with the lighter shade. I’m achieving blur here by using RadialGradient brush for Modifier.background().</p><p>[3] Since the upper circle will be displaced towards top-left, both of its alignment bias values should be negative &amp; equal.</p><p>[4] This is the lower blurred circle with the darker shade.</p><p>[5] Since the lower circle will be displaced towards bottom-right, both of its alignment bias values should be positive &amp; equal.</p><p>[6] This is the central circle whose color matches the overall surface color.</p><p>There you have it, a simple implementation of Neomorphic animation in Jetpack Compose. 😀</p><p>My implementation may not be the most optimised or perfect, but hopefully it could help you with few references to Compose implementations.</p><p>I’m documenting all my snippets at a single place. Check out <a href="https://medium.com/@theRoshogulla/jetpack-compose-over-a-cup-of-hot-chocolate-%EF%B8%8F-9c3c29857437">Jetpack Compose over a Cup of Hot Chocolate ☕️</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7aa7abd43c91" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/compose-snippet-neomorphic-button-7aa7abd43c91">[Compose Snippet] Neomorphic Button 🔘</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[@propertyWrapper is fun]]></title>
            <link>https://surajsau.medium.com/propertywrapper-is-fun-be6d4130515a?source=rss-caea8550ebc------2</link>
            <guid isPermaLink="false">https://medium.com/p/be6d4130515a</guid>
            <category><![CDATA[beginner]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[property-wrapper]]></category>
            <category><![CDATA[experiment]]></category>
            <dc:creator><![CDATA[Suraj Sau]]></dc:creator>
            <pubDate>Sat, 08 Aug 2020 15:08:16 GMT</pubDate>
            <atom:updated>2020-08-08T15:08:16.650Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pm4xLwqTtyILHHfyZR-Hbw.jpeg" /><figcaption>Haven’t played Monster Hunter tri, but visually this was my favourite</figcaption></figure><p>While I was going through on <a href="https://www.raywenderlich.com/6587213-alamofire-5-tutorial-for-ios-getting-started">how to make HTTP calls in Swift with Alamofire</a> and subsequent URLSession tutorials, the HTTP calls looked something like,</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ccf05bae8e2700aede7b8a552da9cef4/href">https://medium.com/media/ccf05bae8e2700aede7b8a552da9cef4/href</a></iframe><p>While Alamofire does a pretty impressive job in cutting down a lot of the boilerplate, I soon realised that for projects with a lot many HTTP calls, maintenance of the API functions could be a lot easier if the generation of the Request URL and processing of response in completionHandler are separated.</p><p>Being an Android developer, I’m very used to how Retrofit (Android’s goto Networking Library) lets us separate the HTTP call URL generation and underneath calling of functions from the responseListener. Something like,</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cbf39e675754e6999a1fb9168a2aa799/href">https://medium.com/media/cbf39e675754e6999a1fb9168a2aa799/href</a></iframe><p>interface are nothing but protocol and each function represents the entirety of an HTTP call. All you need to do is call the function which will return an Observable of the Response. Neat and simple right. <br>What Retrofit in Kotlin/Java does under the hood is that, for each of the function annotated with @GET or @POST, the compiler synthesises all the required boilerplate to convert them into full-fledged HTTP calls with callbacks.</p><p>I was wondering if something like this could be done in Swift too and then I struck gold when I came across@propertyWrapper in Swift. The official documentation it reads,</p><pre>A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property.</pre><p>For example, if we wanted to have String fields to always return value in the “CamelCased” format, we can write a @propertyWrapper as follows,</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bccb122514aa71e774f6b428a85af945/href">https://medium.com/media/bccb122514aa71e774f6b428a85af945/href</a></iframe><p>The parallel I could draw in Kotlin would be either the custom getters and setters or even better, Property Delegates. In Kotlin, the above example would look something like this,</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/88308f3d8b878f24ea8bece11f1ea919/href">https://medium.com/media/88308f3d8b878f24ea8bece11f1ea919/href</a></iframe><p>Using @propertyWrapper we would try achieving a Retrofit-like syntax for our Alamofire boilerplates. Something like,</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/90cc2cff454dd3b0a76bdfae0ed18d7e/href">https://medium.com/media/90cc2cff454dd3b0a76bdfae0ed18d7e/href</a></iframe><p>Let’s begin by typealiasing our completionHandler closure to Observer&lt;T&gt;.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2ba7d85ef1bb0fc409743342928ce215/href">https://medium.com/media/2ba7d85ef1bb0fc409743342928ce215/href</a></iframe><p>Next, let us implement a GET struct as a @propertyWrapper.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/41056498ade901ca1f31842f1451c495/href">https://medium.com/media/41056498ade901ca1f31842f1451c495/href</a></iframe><p>So what is going on here,<br><strong>1.</strong> we have a local variable url which is being set at init.</p><p><strong>2.</strong> the calculated value wrappedValue is of type Observer&lt;T&gt; which is going to behave as our completionHandler from wherever we make the HTTP call.</p><p><strong>3.</strong> inside the get we return a closure where the entire Alamofire boilerplate resides. So, every time I fetch the variable which has been tagged with this @GET propertyWrapper, all I’ve deal it is the completionHandler in the closure without worrying of the underneath HTTP call boilerplate.</p><p>So now, we can ‘declare’ our HTTP calls and use them with minimal code as follows,</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eeedb42a0bd84ee312e8a03450311894/href">https://medium.com/media/eeedb42a0bd84ee312e8a03450311894/href</a></iframe><h3>So…</h3><p>Though this example showed the implementation of a wrapper @GET(:url)is a small portion of all the possible types of HTTP calls. The main reason for me to write this up was to demonstrate how @propertyWrapper can be used to do a lot of heavy lifting to make our code more readable and manageable in the long run.</p><p>I’m still very new to iOS and working my way through it. So, if you have any suggestions or corrections feel free to reach me on <a href="https://twitter.com/su____ji">Twitter</a> or let me know in the comments below.</p><p>Also, I love reading about Monster Hunter and the vast foray of ecological varieties it provides. I’ve started maintaining a <a href="https://www.notion.so/f3fab45fab564e6b88bc4244beb2616e?v=809935bba8ae40e3950377a044b16890">personal collection of ecology</a> and lores from various sources I come across. Interested people do give it look 🐲</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=be6d4130515a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Toolbar.setTitle() is interesting ]]></title>
            <link>https://tech.dailyrounds.org/toolbar-settitle-is-interesting-6799bab16b49?source=rss-caea8550ebc------2</link>
            <guid isPermaLink="false">https://medium.com/p/6799bab16b49</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[source-code]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Suraj Sau]]></dc:creator>
            <pubDate>Fri, 07 Aug 2020 08:23:40 GMT</pubDate>
            <atom:updated>2021-05-18T04:38:33.960Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4HhoTCrI2dNk6DfDkOuvcg.jpeg" /><figcaption>Alatreon: The Blazing Black Dragon</figcaption></figure><p>In most cases, we use Toolbar as follows</p><pre>&lt;androidx.appcompat.widget.Toolbar<br>    android:id=&quot;@+id/toolbar&quot;<br>    android:layout_width=&quot;match_parent&quot;<br>    android:layout_height=&quot;wrap_content&quot;<br>    app:navigationIcon=&quot;@drawable/ic_arrow&quot;<br>    <strong>app:title=&quot;@string/screen_title&quot;</strong><br>    <strong>app:titleTextAppearance=&quot;@style/ToolbarTitle&quot;</strong> /&gt;</pre><p>where we set the title and title’s style in the layout xml itself.</p><p>Let’s consider the following use case:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*5mhA5bldNBlJ_AAjw2a_KA.gif" /><figcaption>open the image in a new tab if the gif doesn’t playback</figcaption></figure><p>For this, I’d set the initial title of Toolbar as empty (let’s assume that @style/ToolbarTitle sets bold font to the text)</p><pre>&lt;androidx.appcompat.widget.Toolbar<br>    android:id=&quot;@+id/toolbar&quot;<br>    android:layout_width=&quot;match_parent&quot;<br>    android:layout_height=&quot;wrap_content&quot;<br>    app:navigationIcon=&quot;@drawable/ic_arrow&quot;<br>    <strong>app:title=&quot;&quot;</strong><br>    <strong>app:titleTextAppearance=&quot;@style/ToolbarTitle&quot;</strong> /&gt;</pre><p>and, add the logic of setting Toolbar title in OnScrollChangeListener as follows,</p><pre>scrollView.setOnScrollChangeListener { .. -&gt;<br>    <strong>toolbar.title = if(scrollY &gt; 50) task.name else &quot;&quot;</strong><br>}</pre><p>Makes complete sense right? On running the code, it looked like this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*3mcaThTOg0t8Iz1J5W_e3w.gif" /><figcaption>open the image in a new tab if the gif doesn’t playback</figcaption></figure><p>Notice something? The @style/ToolbarTitle doesn’t seem to have any effect. Any reason why? Well, there is an issue with this implementation and a rather interesting piece of code behind this kind of limitation. Let’s have a look at it then.</p><h3>Inside Toolbar.setTitle( )</h3><p>Toolbar.setTitle() is called initially in the constructor of Toolbar itself. Where, the Charsequence title is taken from app:title and passed on as parameter.</p><p>Let us have a look at the implementation of Toolbar.setTitle() in <a href="https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/appcompat/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java">AOSP</a> (as per <strong>androidx.appcompat:appcompat:1.1.0</strong>)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f6b13fbecb572f30490f8e709f8a9a20/href">https://medium.com/media/f6b13fbecb572f30490f8e709f8a9a20/href</a></iframe><p>From <strong>Line#10</strong>onwards, we see that <strong>if </strong><strong>title is empty</strong>, mTitleTextView.setTextAppearance() isn’t called. So, style set inapp:titleTextAppearance won’t have any effect on the Toolbar’s title.</p><p>Fine. Then, how about we programatically set the style using Toolbar.setTitleTextAppearance(R.style.ToolbarTitle) ? That should work.</p><p>Nope, that doesn’t work too. Why?</p><h3>Inside Toolbar.setTitleTextAppearance( )</h3><p>Let’s have a look at the <a href="https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/appcompat/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java">AOSP</a>’s implementation of Toolbar.setTitleTextAppearance()</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/512e8b1cab612db9bf93334d097afa45/href">https://medium.com/media/512e8b1cab612db9bf93334d097afa45/href</a></iframe><p>I would like to draw your attention momentarily towards Toolbar.setTitle() <strong>Line#13 </strong>for once. We observe that <strong>when </strong><strong>titleis empty</strong>, mTitleTextView isn’t initialized.</p><p>In that case, even inToolbar.setTitleTextAppearance() we see that mTitleView.setTextAppearance() sits behind a null check. Hence, even programmatically setting Toolbar.setTitleTextAppearance() won’t have any effect on the appearance of Toolbar’s title.</p><h3>Solution?</h3><p>So as per the intentions of the author of Toolbar.setTitle(), in order for app:titleTextAppearance to have any effect on the Toolbar’s title appearance, one needs to at least set a <strong>default non-empty title </strong>in the layout file for Toolbar . Hence after adding a non-empty text in app:title things worked!</p><pre>&lt;androidx.appcompat.widget.Toolbar<br>    android:id=&quot;@+id/toolbar&quot;<br>    android:layout_width=&quot;match_parent&quot;<br>    android:layout_height=&quot;wrap_content&quot;<br>    app:navigationIcon=&quot;@drawable/ic_arrow&quot;<br>    <strong>app:title=&quot; &quot;</strong><br>    app:titleTextAppearance=&quot;@style/ToolbarTitle&quot; /&gt;</pre><p>Even though <a href="https://developer.android.com/reference/kotlin/androidx/appcompat/widget/Toolbar?hl=en">Android’s Toolbar documentation</a> doesn’t specify anything about the design decision behind this restriction, I thought it would be fun sharing this nice little tidbit about one of the most frequently used View classes in Android Developement.</p><p>I also love reading about Monster Hunter and the vast foray of ecological varieties it provides. I’ve started maintaining a <a href="https://www.notion.so/f3fab45fab564e6b88bc4244beb2616e?v=809935bba8ae40e3950377a044b16890">personal collection of ecology</a> and lores from various sources I come across. Interested people do give it look 🐲</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6799bab16b49" width="1" height="1" alt=""><hr><p><a href="https://tech.dailyrounds.org/toolbar-settitle-is-interesting-6799bab16b49">Toolbar.setTitle() is interesting 🔬</a> was originally published in <a href="https://tech.dailyrounds.org/">Dailyrounds Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>