<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hossain Khan</title>
    <description>The latest articles on DEV Community by Hossain Khan (@hossain).</description>
    <link>https://dev.to/hossain</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F155237%2F5a387f85-38dc-4d6d-ac5b-bdc1c328bb3b.jpeg</url>
      <title>DEV Community: Hossain Khan</title>
      <link>https://dev.to/hossain</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hossain"/>
    <language>en</language>
    <item>
      <title>Fixing GitHub Coding Agent’s Firewall Issue for Android Projects</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Sun, 03 Aug 2025 04:19:23 +0000</pubDate>
      <link>https://dev.to/hossain/fixing-github-coding-agents-firewall-issue-for-android-projects-2gcn</link>
      <guid>https://dev.to/hossain/fixing-github-coding-agents-firewall-issue-for-android-projects-2gcn</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgamq0ztv3qc7wmnbhpr9.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgamq0ztv3qc7wmnbhpr9.jpeg" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ever since &lt;a href="https://github.blog/news-insights/product-news/github-copilot-meet-the-new-coding-agent/" rel="noopener noreferrer"&gt;GitHub coding agent&lt;/a&gt; is released, I have been big fan of async agentic work. I have been primarily working and Android projects and one of the issue constantly bothering me was the Gradle build issue.&lt;/p&gt;

&lt;p&gt;Everyone agent goes off to work, it almost always encounters this issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* What went wrong:
Plugin [id: 'com.android.application', version: '8.12.0', apply: false] was not found in any of the following sources:

- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Included Builds (No included builds contain this plugin)
- Plugin Repositories (could not resolve plugin artifact 'com.android.application:com.android.application.gradle.plugin:8.12.0')
  Searched in the following repositories:
    Google
    MavenRepo
    Gradle Central Plugin Repository

BUILD FAILED in 29s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the PR that is created by agent you will see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; &amp;lt;summary&amp;gt;Firewall rules blocked me from connecting to one or more addresses&amp;lt;/summary&amp;gt;

 #### I tried to connect to the following addresses, but was blocked by firewall rules:

 - `developer.android.com`
   - Triggering command: `curl -s REDACTED` (dns block)
 - `dl.google.com`
   - Triggering command: `/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -Xmx2048m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp /home/REDACTED/.gradle/wrapper/dists/gradle-8.14.3-bin/cv11ve7ro1n3o1j4so8xd9n66/gradle-8.14.3/lib/gradle-daemon-main-8.14.3.jar -javaagent:/home/REDACTED/.gradle/wrapper/dists/gradle-8.14.3-bin/cv11ve7ro1n3o1j4so8xd9n66/gradle-8.14.3/lib/agents/gradle-instrumentation-agent-8.14.3.jar org.gradle.launcher.daemon.bootstrap.GradleDaemon 8.14.3` (dns block)

 If you need me to access, download, or install something from one of these locations, you can either:

 - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled
 - Add the appropriate URLs or hosts to the custom allowlist in this repository's [Copilot coding agent settings](https://github.com/hossain-khan/android-compose-app-template/settings/copilot/coding_agent) (admins only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a preview of how it looks on your PR!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5atzeteu117rvbynkbgo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5atzeteu117rvbynkbgo.png" width="800" height="563"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Preview when firewall blocks connection to Google’s maven repo&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The fix is as simple as reading the warning with patience and &lt;strong&gt;follow the link to agent settings&lt;/strong&gt; in the GitHub repo. Once you go to settings, you just have to add the domains you want to whitelist. Here is screenshot of those steps for reference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91xvvmcnaham5mhpki0v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91xvvmcnaham5mhpki0v.png" width="800" height="418"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Go to Coding Agent settings and click “Custom allowlist”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F340ceqzzt6bsnmxnq3qa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F340ceqzzt6bsnmxnq3qa.png" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Finally add the domain that you want to whitelist / allow&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s it, next time the agent runs, it should be able to continue with Gradle build system that depends on google maven repo or Gradle plugin artifacts that are hosted at Google domains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/customize-the-agent-firewall" rel="noopener noreferrer"&gt;https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/customize-the-agent-firewall&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aicodingagent</category>
      <category>githubcopilot</category>
      <category>android</category>
      <category>githubcodingagent</category>
    </item>
    <item>
      <title>‘Vibe Coding’ to ‘Blind Coding’</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Wed, 11 Jun 2025 04:37:07 +0000</pubDate>
      <link>https://dev.to/hossain/vibe-coding-to-blind-coding-4832</link>
      <guid>https://dev.to/hossain/vibe-coding-to-blind-coding-4832</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I let AI write a JSON5 parser for me using just test cases and vibes. No deep knowledge, just TDD and Agentic AIs. Shockingly, it worked!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmj1vzel4z9uw44kqi2aq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmj1vzel4z9uw44kqi2aq.png" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can you be irresponsibly responsible? ^_^&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was the experiment I wanted to do with a plethora of Agentic AI available for code assist.&lt;/p&gt;

&lt;p&gt;I am sure I don’t need to define &lt;a href="https://en.wikipedia.org/wiki/Vibe_coding" rel="noopener noreferrer"&gt;Vibe Coding&lt;/a&gt; to you — but, what is “Blind Coding” you ask!? I think you have guessed it by now — It’s about almost blindly accepting AI-generated code.&lt;/p&gt;

&lt;p&gt;Sounds very irresponsible, right? But there is a twist and that is part of my experiment 🧪&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I wanted to explore if I could do TDD (&lt;em&gt;Test Driven Development&lt;/em&gt;) with AI on a topic with &lt;strong&gt;&lt;em&gt;minimal understanding&lt;/em&gt;&lt;/strong&gt; of what the suggested code does.&lt;/li&gt;
&lt;li&gt;However, I would have a very good understanding of expected &lt;strong&gt;&lt;em&gt;input &amp;amp; output&lt;/em&gt;&lt;/strong&gt; such that I can have well-defined test cases that I can feed AI Agents to work with.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Test Subject — Build a JSON5 Parser
&lt;/h4&gt;

&lt;p&gt;For this experiment I chose &lt;a href="https://json5.org/" rel="noopener noreferrer"&gt;JSON5&lt;/a&gt; that I recently came across. This is an extension of JSON with the option to add comment and more, making it suitable for configuration definitions (like YAML).&lt;/p&gt;

&lt;p&gt;Since &lt;strong&gt;JSON5&lt;/strong&gt; has a well-defined grammar and specification, it would be easy for me to point to those and &lt;strong&gt;build a library&lt;/strong&gt; that parses JSON5 files using Kotlin language.&lt;/p&gt;

&lt;h4&gt;
  
  
  Results First 📊
&lt;/h4&gt;

&lt;p&gt;Before I dive deep into the ‘Blind Coding’ strategy, let me reveal what was achieved first using LLM based Agentic AI 🤖&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fully functional JSON5 de/serializer of Kotlin Data classes using kotlinx.serialization&lt;/li&gt;
&lt;li&gt;Fully functional JSON5 parser that complies with specification and provides parsed data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is &lt;a href="https://github.com/hossain-khan/json5-kotlin/tree/main/benchmark" rel="noopener noreferrer"&gt;benchmark&lt;/a&gt; of generated library vs two relevant contender 💪&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;JSON5-AI — AI generated library built with ‘Blind Coding’&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Kotlin/kotlinx.serialization" rel="noopener noreferrer"&gt;kotlinx.serialization&lt;/a&gt; — JetBrains library used &lt;em&gt;only&lt;/em&gt; for JSON serialization and deserialization. Which has less complexity compared to JSON5.&lt;/li&gt;
&lt;li&gt;Syntaxerror’s&lt;a href="https://github.com/Synt4xErr0r4/json5" rel="noopener noreferrer"&gt;JSON5 Java Library &lt;/a&gt;— an actively maintained implementation of JSON5. Marked as ‘3P-External-JSON5’ in the chart below.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96inkwzg0c52yfh7w69n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96inkwzg0c52yfh7w69n.png" width="800" height="431"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Total processing time by library&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybq68gfx3j8zcf33zoyu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybq68gfx3j8zcf33zoyu.png" width="800" height="536"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Total time spent for each test category&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Regardless of the generated library being ~4x and ~1.5x less performant compared to kotlinx.serialization and Syntaxerror’s Java library respectively, the experiment is a ✅ success in my book! Because it’s functional and has very acceptable performance for initial phase.&lt;/p&gt;

&lt;p&gt;If you are interested to look under the hood, take a look into &lt;strong&gt;lib&lt;/strong&gt; module in this GitHub project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuh4imikoozh6bjcseyuf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuh4imikoozh6bjcseyuf.png" width="800" height="803"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hossain-khan/json5-kotlin" rel="noopener noreferrer"&gt;GitHub - hossain-khan/json5-kotlin: JSON5 implementation for Kotlin/JVM&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Behind the Scenes
&lt;/h4&gt;

&lt;p&gt;Believe it or not, JSON5 has been around since 2012 and has all the implementation details and specification on their GitHub repos (&lt;a href="https://github.com/json5" rel="noopener noreferrer"&gt;https://github.com/json5&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;All I had to do was import their JavaScript based implementation and specification and &lt;strong&gt;&lt;em&gt;iteratively&lt;/em&gt;&lt;/strong&gt; ask agents to build functionalities.&lt;/p&gt;

&lt;p&gt;⏳ &lt;strong&gt;How long did it take to build the JSON5 library?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
About 4 days of weekend and after work sessions (roughly 10–12 hours)&lt;/p&gt;

&lt;p&gt;😅 &lt;strong&gt;Was there challenges along the way?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Yes, some of the parsing tests were failing and agent was repeatedly failing to fix it. I almost lost hope. Then GitHub Copilot Agent rescued me.&lt;/p&gt;

&lt;p&gt;🤖 &lt;strong&gt;What are the Agents that were used to achieve this?&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Sonnet 3.7 (Agent Mode — IDE integration)
&lt;/li&gt;
&lt;li&gt;GPT 4.1 (Agent Mode — IDE integration)
&lt;/li&gt;
&lt;li&gt;Google Jules (async agent)
&lt;/li&gt;
&lt;li&gt;GitHub Copilot Agent (async agent — used for majority of the project)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎉 &lt;strong&gt;So, what’s the verdict?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I had absolute blast just watching agents do magical stuff that I couldn’t have done by myself. Even the benchmarking solution was build by Agent.&lt;br&gt;&lt;br&gt;
Seeing the benchmark result also gave me hope and sense of accomplishment. Sure, I might not be able to build the next Dagger, or OkHttp or anything of that sort, however anything simple should clearly be within reach with the limited time we have in our lives. That is a huge win for me! 🏆&lt;/p&gt;

&lt;p&gt;Now, on to something more useful! 🤓 Feel free to reach out or ask question. Cheers! ✌🏽&lt;/p&gt;

</description>
      <category>json5</category>
      <category>agenticai</category>
      <category>copilotprogramming</category>
      <category>experiment</category>
    </item>
    <item>
      <title>Using AI to build modern Android apps — are we there yet?</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Tue, 18 Feb 2025 13:55:04 +0000</pubDate>
      <link>https://dev.to/hossain/using-ai-to-build-modern-android-apps-are-we-there-yet-426p</link>
      <guid>https://dev.to/hossain/using-ai-to-build-modern-android-apps-are-we-there-yet-426p</guid>
      <description>&lt;h3&gt;
  
  
  Using AI to build modern Android apps — are we there yet?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1gy6xxibobb3xy57x2o.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1gy6xxibobb3xy57x2o.jpeg" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Building is more fun with AI 🤖&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’ve had my GitHub Copilot subscription for over a year now, but I haven’t leveraged the service until very recently I decided to build some tools and Android apps using it.&lt;/p&gt;

&lt;p&gt;I have extensively used ChatGPT 4o &amp;amp; o1, Google Gemini, and Claude Sonnet to build an app related to weather even though I am very aware of the following phrase:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If all you have is a hammer, everything looks like a nail.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  The Rationale Behind the App
&lt;/h4&gt;

&lt;p&gt;You get many amazing things living in Canada, one of them is snow in winter. After moving to a home, I needed a way for me to be better prepared for snow days where I have to clean my driveway and potentially neighbour’s if the snowblower has enough charge.&lt;/p&gt;

&lt;p&gt;So, I saw a “nail” that I can hammer, I built a single purpose Android app that notifies me if there will be enough snow, a threshold set by me. This allows me to make sure I charge the snowblower batteries and park the cars inside the garage. Profit!&lt;/p&gt;

&lt;p&gt;Sure, there are some outstanding weather apps including Google’s own that can provide similar info, however the tinkering engineer wants to use the hammer! 😂&lt;/p&gt;

&lt;h4&gt;
  
  
  The AI Code Assist Experience
&lt;/h4&gt;

&lt;p&gt;It’s been absolutely fun working with AI to come up with solutions, in this case the Compose UI Screens, Presenters, and unit tests for them. From my experience, the LLM provided solution are &lt;strong&gt;80–98% correct&lt;/strong&gt; and can just be copy pasted with some imports to fix. Sometimes I do have to refactor or move things around, but it gets me 80% there and I just finish the last 20% to make a finished app screen.&lt;/p&gt;

&lt;p&gt;I feel like, this will be an essential tool for all kinds of engineers to reduce the cognitive load and iterate on the products they want to build.&lt;/p&gt;

&lt;h4&gt;
  
  
  Is the AI/LLM Ready to Print Out Full App?
&lt;/h4&gt;

&lt;p&gt;Short answer, NO &lt;em&gt;(based on my experience)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I have encountered some YouTube videos where they showcase building beautiful iOS apps using LLMs. It’s possible that I was not a good prompt engineer or GitHub Copilot is not the right product to build fully functional app.&lt;/p&gt;

&lt;p&gt;Another limitation I noticed is, when asked to build full app, the LLM is likely limited by the length of response (tokens), so it responds in shorter chunks for each component of the app and does not provide fully detailed implementation. When asked in smaller chunks with specific steps, it can provide detailed code.&lt;/p&gt;

&lt;p&gt;It might get there soon enough. Or, maybe if we use multi-modal LLM and provide fully designed mocks of the app, it will be able to build it.&lt;/p&gt;

&lt;p&gt;Regardless, I am very happy to use LLM and learn from it through the process of building small apps.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Learning Opportunity
&lt;/h4&gt;

&lt;p&gt;One of the best part of leveraging AI and looking at the response is that you get to learn new function, tools and so on.&lt;/p&gt;

&lt;p&gt;Along the way building the app, I got to 🧑‍💻 exercise my knowledge, learn and grow. Here are some of the cool stuff I have done while building the app&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extensively leverage AI powered code assist, namely GitHub Copilot, ChatGPT, Google Gemini 🤖. I think more than 50% of the code is done by AI.&lt;/li&gt;
&lt;li&gt;Learn ⚡ &lt;a href="https://slackhq.github.io/circuit/" rel="noopener noreferrer"&gt;Circuit&lt;/a&gt;, a modern Android app architecture from Slack (that’s what we also use at Slack)&lt;/li&gt;
&lt;li&gt;Use Jetpack Room database for app, and also the SQLite (Kotlin Multiplatform) library for bundling city database used in the app&lt;/li&gt;
&lt;li&gt;Jetpack Compose for building modern UI that follows Material 3 guidelines with dark and light mode support&lt;/li&gt;
&lt;li&gt;Go through lengthy Google Play publishing process 😅&lt;/li&gt;
&lt;li&gt;And all the other usual suspects like Retrofit, OkHttp, Dagger Anvil, Moshi, Firebase and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the app that is available on GitHub&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hossain-khan/android-weather-alert" rel="noopener noreferrer"&gt;GitHub - hossain-khan/android-weather-alert: A simple app to provide configured weather alert so that you are ready for next hour or day! Built using Android Jetpack Compose and Circuit UDF.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s also available on &lt;a href="https://play.google.com/store/apps/details?id=dev.hossain.weatheralert&amp;amp;pcampaignid=web_share" rel="noopener noreferrer"&gt;Google Play&lt;/a&gt;, if the use-case mentioned talks to you :-)&lt;/p&gt;

</description>
      <category>android</category>
      <category>github</category>
      <category>githubcopilot</category>
      <category>androidappdevelopmen</category>
    </item>
    <item>
      <title>Android remote logging to Airtable using Timber</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Tue, 10 Sep 2024 04:03:01 +0000</pubDate>
      <link>https://dev.to/hossain/android-remote-logging-to-airtable-using-timber-15l0</link>
      <guid>https://dev.to/hossain/android-remote-logging-to-airtable-using-timber-15l0</guid>
      <description>&lt;p&gt;Have you ever needed to save your Android Logcat logs for later analysis?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq87nkzf2g04o1vkmmm4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq87nkzf2g04o1vkmmm4.jpeg" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently built a service-based Android app, and I needed to monitor its activity to validate its correctness. For that I could not sit in front of the computer monitor for 24+ hours, I needed a way to store the logs with app behavior to validate it’s performing as it should.&lt;/p&gt;

&lt;p&gt;I leveraged the following 2 tools to accomplish this task&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/JakeWharton/timber" rel="noopener noreferrer"&gt;&lt;strong&gt;Timber&lt;/strong&gt;&lt;/a&gt;: &lt;em&gt;A logger with a small, extensible API which provides utility on top of Android’s normal Log class.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.airtable.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Airtable&lt;/strong&gt;&lt;/a&gt;: &lt;em&gt;Airtable is a hybrid solution that mixes the intuitiveness of spreadsheets and the power and functionalities of databases.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Airtable service can be replaced by any other similar service that allows REST API to add records that can be viewed online.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Why Airtable?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;It provides an easy interface to view data in a spreadsheet-like experience.&lt;/li&gt;
&lt;li&gt;It has REST API to easily add data from Android app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are not familiar with the amazing Android Timber library, I suggest you spend a few minutes reading the &lt;a href="https://github.com/JakeWharton/timber/blob/trunk/README.md" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. We will be building an &lt;strong&gt;AirtableLoggingTree&lt;/strong&gt; that sends all the logs logged via Timber to the Airtable spreadsheet/database using their REST API.&lt;/p&gt;

&lt;p&gt;Here are some pseudo task we need to accomplish this&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save all the logs to a queue to process and send to REST API&lt;/li&gt;
&lt;li&gt;Format the logs and send a batched request (to avoid rate limit)&lt;/li&gt;
&lt;li&gt;Make the REST call based on API specification for &lt;a href="https://airtable.com/developers/web/api/create-records" rel="noopener noreferrer"&gt;creating a record&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, let’s build a Kotlin data class to capture some log info that should be sent to Airtable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;LogMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;throwable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Throwable&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;logTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let’s create the AirtableLoggingTree that queues the logs and sends logs in batches using REST API via OkHttp client.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💁Inline comments have been added where applicable.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AirtableLoggingTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;// Create your token from https://airtable.com/create/tokens&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Example: https://api.airtable.com/v0/appXXXXXXXXX/Table%20Name&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;endpointUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Timber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;logQueue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConcurrentLinkedQueue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LogMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;flushJob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;

    &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/**
         * The API is limited to 5 requests per second per base.
         */&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;MAX_LOG_COUNT_PER_SECOND&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

        &lt;span class="cm"&gt;/**
         * The API is limited to 10 records per request.
         * - https://airtable.com/developers/web/api/create-records
         */&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;MAX_RECORDS_PER_REQUEST&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;startFlushJob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Throwable&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Adds the log to queue for batch processing&lt;/span&gt;
        &lt;span class="n"&gt;logQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LogMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flushJob&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;flushJob&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;isCancelled&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// If there is enough log, it will start sending them &lt;/span&gt;
            &lt;span class="nf"&gt;startFlushJob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;startFlushJob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;flushJob&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;CoroutineScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;flushLogs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                    &lt;span class="c1"&gt;// Wait long enough before sending next request &lt;/span&gt;
                    &lt;span class="c1"&gt;// https://airtable.com/developers/web/api/rate-limits&lt;/span&gt;
                    &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_100L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createLogMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LogMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Builds the JSON payload structure with multiple messages based on API spec&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;records&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JSONArray&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLogRecord&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"records"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getMaximumAllowedLogs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LogMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;logs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LogMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNotEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nc"&gt;MAX_RECORDS_PER_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;log&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;flushLogs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;sentLogCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="c1"&gt;// Collects all the queued logs and sends them in batches&lt;/span&gt;
        &lt;span class="c1"&gt;// Based on rate-limit a total of 5x10 = 50 reconds can be sent per second&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentLogCount&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nc"&gt;MAX_LOG_COUNT_PER_SECOND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;jsonPayload&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLogMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getMaximumAllowedLogs&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonPayload&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;sendLogToApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;sentLogCount&lt;/span&gt;&lt;span class="p"&gt;++&lt;/span&gt;

                &lt;span class="c1"&gt;// This delay is added to ensure the order of log is maintained.&lt;/span&gt;
                &lt;span class="c1"&gt;// However, there is no guarantee that the log will be sent in order.&lt;/span&gt;
                &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;flushJob&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Finally, this is the OkHttp client that sends HTTP POST request to add those log records&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;sendLogToApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logPayloadJson&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mediaType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"application/json; charset=utf-8"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toMediaTypeOrNull&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logPayloadJson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRequestBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mediaType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpointUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bearer $authToken"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: okhttp3.&lt;/span&gt;&lt;span class="nc"&gt;Callback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;okhttp3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;Timber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to send log to API: ${e.localizedMessage}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;okhttp3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;okhttp3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// This ensures the response body is closed&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isSuccessful&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="nc"&gt;Timber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="s"&gt;"Log is rejected: HTTP code: ${response.code}, "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                                    &lt;span class="s"&gt;"message: ${response.message}, body: ${response.body?.string()}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is great except there is one piece of missing code that uses the API LogMessage.toLogRecord() in the AirtableLoggingTree. The snipped is shown below for clarity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;records&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JSONArray&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLogRecord&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function implementation will be very specific to how you have setup your Airtable spreadsheet.&lt;/p&gt;

&lt;p&gt;For my case, I have two columns for the table in the Airtable base:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"Device" — to store the device model. Single-line text.&lt;/li&gt;
&lt;li&gt;"Log" — to store the log message. Multiline text.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;toLogRecord&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;JSONObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;logMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nf"&gt;buildString&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Priority: ${priority}\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tag: $tag\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Message: $message\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Throwable: ${throwable.localizedMessage}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"App Version: ${BuildConfig.VERSION_NAME}\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Log Time: ${logTime}\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fields&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Device"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could essentially have multiple columns for each of the properties.&lt;/p&gt;

&lt;p&gt;✅ And that’s it. You have a working Timber tree that can send all logcat logs to the Airtable base (spreadsheet) that adheres to their API rate limit.&lt;/p&gt;

&lt;h4&gt;
  
  
  See it in action
&lt;/h4&gt;

&lt;p&gt;You can take a look at the fully &lt;a href="https://github.com/hossain-khan/android-keep-alive/blob/main/app/src/main/java/dev/hossain/keepalive/log/ApiLoggingTree.kt" rel="noopener noreferrer"&gt;implemented tree&lt;/a&gt; in the following project&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hossain-khan/android-keep-alive" rel="noopener noreferrer"&gt;GitHub - hossain-khan/android-keep-alive: A simple app to keep alive specific apps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a screenshot of how the log looks in the Airtable&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgsc2yh6i0c9sm33sj31a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgsc2yh6i0c9sm33sj31a.png" width="800" height="482"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Demo of remotly logged messages in the Airtable&lt;/em&gt;&lt;/p&gt;

</description>
      <category>timber</category>
      <category>android</category>
      <category>remotelogging</category>
      <category>androidappdevelopmen</category>
    </item>
    <item>
      <title>How to update a Docker Container using Portainer</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Sat, 10 Aug 2024 20:30:52 +0000</pubDate>
      <link>https://dev.to/hossain/how-to-update-a-docker-container-using-portainer-50g7</link>
      <guid>https://dev.to/hossain/how-to-update-a-docker-container-using-portainer-50g7</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6amcjwlozj9k3jo89fu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6amcjwlozj9k3jo89fu8.png" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ℹ️ This is part of the self-learning log as I explore Docker and Portainer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently, I have been playing with &lt;a href="https://www.portainer.io/" rel="noopener noreferrer"&gt;Portainer &lt;/a&gt;— a Container Management system for Docker. Because of my curiosity, I constantly try different containers for different solutions and sometimes stick to some useful apps.&lt;/p&gt;

&lt;p&gt;Once in a while, I keep seeing Docker containerized app notification that there is an update available. However, It was tricky for me to figure out how to update the container using Portainer. It turns out, it’s as simple as just pressing edit container and update container image. Here is a visual guide for my future self :-)&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating Docker Container
&lt;/h3&gt;

&lt;p&gt;First, go to your list of containers and select the container that needs to be updated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff45vkq9iwuxruv7cw86m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff45vkq9iwuxruv7cw86m.png" width="800" height="212"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Select “Duplicate/Edit” option.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you select “Edit”, you will have option for container image version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7pv7tjrgzmawg6u2nux1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7pv7tjrgzmawg6u2nux1.png" width="800" height="529"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Select the container image version here.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the “Image” section you can specify the version or keep latest or stable to get the latest image based on the container you are using. If you are unsure, go to the docker hub or source of the container to get the exact tag.&lt;/p&gt;

&lt;p&gt;Finally, click on the “Deploy the container”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1cwlay0mlo5d5u1kovy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1cwlay0mlo5d5u1kovy.png" width="678" height="277"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Once done, deployment will trigger the image update.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This will pull the latest image and re-deploy the container with latest image.&lt;/p&gt;

&lt;p&gt;⚠️ A few important caveats to keep in mind&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This assumes that the container configuration is persisted in the host machine, so that when it redeploys it will be able to resume from where you left off with the container app.&lt;/li&gt;
&lt;li&gt;Downgrading may have unintended consequences.&lt;/li&gt;
&lt;li&gt;Always look at the project/app documentation on upgrading if there is something special that needs to be done to migrate your app.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>portainer</category>
      <category>docker</category>
    </item>
    <item>
      <title>Using SQLDelight 2.0 with PostgreSQL for JVM</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Wed, 06 Sep 2023 23:06:26 +0000</pubDate>
      <link>https://dev.to/hossain/using-sqldelight-20-with-postgresql-for-jvm-4cb1</link>
      <guid>https://dev.to/hossain/using-sqldelight-20-with-postgresql-for-jvm-4cb1</guid>
      <description>&lt;p&gt;Recently I wanted to do some experiments with saving JSON data into a database and found out that PostgreSQL supports JSON as a data type.&lt;/p&gt;

&lt;p&gt;I also wanted to use the fantastic SQLDelight library that creates type-safe models and queries for any database (it’s also multi-platform supported).&lt;/p&gt;

&lt;p&gt;While trying to follow the recently released SQLDelight 2.0 &lt;a href="https://cashapp.github.io/sqldelight/2.0.0/jvm_postgresql/" rel="noopener noreferrer"&gt;guide&lt;/a&gt;, I stumbled into missing pieces to make the PostgreSQL work with it.&lt;/p&gt;

&lt;p&gt;In this post, I will try to fill in the gaps and build a sample project showcasing both working together ✌️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn01qf9e9ff7rvb07bnr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn01qf9e9ff7rvb07bnr.png" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;PostgreSQL and SQLDelight. Source: &lt;a href="https://kinsta.com/" rel="noopener noreferrer"&gt;https://kinsta.com/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few of the missing pieces that I had to figure out were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;strong&gt;DataSource&lt;/strong&gt; (“hikari” or other connection managers were mentioned in the guide)&lt;/li&gt;
&lt;li&gt;Using &lt;strong&gt;HikariCP&lt;/strong&gt; as data source to connect to PostgreSQL&lt;/li&gt;
&lt;li&gt;Write all the glue pieces to use the SQLDelight code to perform CRUD operations on the PostgreSQL Database&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use HikariCP to create PostgreSQL DataSource&lt;/p&gt;

&lt;p&gt;First import the hikari and PostgreSQL library into your JVM project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="c1"&gt;// https://github.com/brettwooldridge/HikariCP#artifacts&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"com.zaxxer:HikariCP:5.0.1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// This is needed for the PostgreSQL driver&lt;/span&gt;
&lt;span class="c1"&gt;// https://mvnrepository.com/artifact/org.postgresql/postgresql&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"org.postgresql:postgresql:42.6.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you need to build the HikariConfig with the right data set to connect to the database and then create the HikariDataSource which is what is needed for the SQLDelight&lt;/p&gt;

&lt;p&gt;Here is a snippet taken from the &lt;a href="https://github.com/hossain-khan/SQLDelight-PostgreSQL-JVM-sample/blob/main/src/main/kotlin/dev/hossain/postgresqldelight/SportsRepository.kt" rel="noopener noreferrer"&gt;sample project&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Creates a [DataSource] using [HikariDataSource].
 */&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getDataSource&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// https://jdbc.postgresql.org/documentation/use/&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HikariConfig&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;jdbcUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"jdbc:postgresql://localhost:5432/dbname"&lt;/span&gt;
        &lt;span class="n"&gt;driverClassName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.postgresql.Driver"&lt;/span&gt;
        &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"dbusername"&lt;/span&gt;
        &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"dbpassword"&lt;/span&gt;
        &lt;span class="n"&gt;maximumPoolSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
        &lt;span class="n"&gt;isAutoCommit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
        &lt;span class="n"&gt;transactionIsolation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TRANSACTION_REPEATABLE_READ"&lt;/span&gt;
        &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HikariDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it, now you can follow the official SQLDelight guide on creating databse to create tables and do CRUD operations.&lt;/p&gt;

&lt;p&gt;For example, here is a simplified snippet to give the whole picture&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SqlDriver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asJdbcDriver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// NOTE: The `SportsDatabase` and `PlayerQueries` are from SQLDelight&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;database&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SportsDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;playerQueries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PlayerQueries&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;playerQueries&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hockeyPlayers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;playerQueries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectAll&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;executeAsList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Existing ${hockeyPlayers.size} records: $hockeyPlayers"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Prints following 👇&lt;/span&gt;
&lt;span class="c1"&gt;// - - - - - - - - - -&lt;/span&gt;
&lt;span class="c1"&gt;// Existing 15 records: &lt;/span&gt;
&lt;span class="c1"&gt;// [HockeyPlayer(player_number=10, full_name=Corey Perry), ...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full snippet is available &lt;a href="https://github.com/hossain-khan/SQLDelight-PostgreSQL-JVM-sample/blob/main/src/main/kotlin/dev/hossain/postgresqldelight/SportsRepository.kt#L41-L53" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;See the GitHub project for a complete example with gradle dependencies and SQLDelight configuration needed to make it work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hossain-khan/SQLDelight-PostgreSQL-JVM-sample" rel="noopener noreferrer"&gt;GitHub - hossain-khan/SQLDelight-PostgreSQL-JVM-sample: A sample project exercising PostgreSQL with SQLDelight 2.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to hear feedback or any corrections to do this in a better way.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EDIT: After I wrote the article, I found similar article about it (which could have saved me ton of time), so do take a look at it too 😊&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://medium.com/@theendik00/sqldelight-for-postgresql-on-kotlin-jvm-b95d14d96134" rel="noopener noreferrer"&gt;SQLDelight for PostgreSQL on Kotlin JVM&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgresqlsqldelight</category>
      <category>postgres</category>
      <category>database</category>
    </item>
    <item>
      <title>Kotlin coroutines error handling strategy — `runCatching` and `Result` class</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Sun, 02 May 2021 20:45:33 +0000</pubDate>
      <link>https://dev.to/hossain/kotlin-coroutines-error-handling-strategy-runcatching-and-result-class-h2a</link>
      <guid>https://dev.to/hossain/kotlin-coroutines-error-handling-strategy-runcatching-and-result-class-h2a</guid>
      <description>&lt;h3&gt;
  
  
  Kotlin coroutines error handling strategy — &lt;code&gt;runCatching&lt;/code&gt; and &lt;code&gt;Result&lt;/code&gt; class
&lt;/h3&gt;

&lt;p&gt;I am trying to learn Kotlin coroutines, and was trying to learn more about how to handle errors from suspended functions. One of the &lt;a href="https://developer.android.com/kotlin/coroutines"&gt;recommended way&lt;/a&gt; by Google is to create a “Result” class like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sealed class Result&amp;lt;out R&amp;gt; {
    data class Success&amp;lt;out T&amp;gt;(val data: T) : Result&amp;lt;T&amp;gt;()
    data class Error(val exception: Exception) : Result&amp;lt;Nothing&amp;gt;()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to take advantage of Kotlin’s when like following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;when (result) {  
 is Result.Success&amp;lt;LoginResponse&amp;gt; -&amp;gt; // Happy path  
 else -&amp;gt; // Show error in UI  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, I have recently &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html"&gt;stumbled into&lt;/a&gt; Kotlin’s runCathing {} API that makes use of Result class &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/"&gt;available in standard lib&lt;/a&gt; since 1.3&lt;/p&gt;

&lt;p&gt;Here I will try to explore how the native API can replace the recommended example in the Android Kotlin &lt;a href="https://developer.android.com/kotlin/coroutines"&gt;training guide&lt;/a&gt; for &lt;em&gt;simple&lt;/em&gt; use cases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nf_vFj94--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Akm6L-1H-5oTFFKaU3LwwgA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nf_vFj94--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Akm6L-1H-5oTFFKaU3LwwgA.png" alt=""&gt;&lt;/a&gt;Here is a basic idea of how &lt;code&gt;runCatching {}&lt;/code&gt; can be used from Android ViewModel.&lt;/p&gt;

&lt;p&gt;Based on Kotlin standard lib &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html"&gt;doc&lt;/a&gt;, you can use runCatching { } in 2 different ways. I will focus on one of them, since the concept for other one is similar.&lt;/p&gt;

&lt;p&gt;To handle a function that may throw an exception in coroutines or regular function use this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val statusResult: Result&amp;lt;String&amp;gt; = runCatching {
    // function that may throw exception that needs to be handled
    repository.userStatusNetworkRequest(username)
}.onSuccess { status: String -&amp;gt; 
  println("User status is: $status")
}.onFailure { error: Throwable -&amp;gt;  
  println("Go network error: ${error.message}")
}

// Assuming following supposed* long running network API
suspend fun userStatusNetworkRequest(username: String) = "ACTIVE"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the ‘&lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/"&gt;Result&lt;/a&gt;’ returned from the runCatching this is where the power comes in to write semantic code to handle errors.&lt;/p&gt;

&lt;p&gt;The onSuccess and onFailrue callback is part of Result class that allows you to easily handle both cases.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to handle Exceptions
&lt;/h4&gt;

&lt;p&gt;In addition to nice callbacks, the Result class provides multiple ways to recover from the error and provide a default value or fallback options.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Using &lt;code&gt;getOrDefault()&lt;/code&gt; and &lt;code&gt;getOrNull()&lt;/code&gt; API&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val status: String = statusResult.getOrDefault("STATUS_UNKNOWN")

// Or if nullable data is acceptable use:
val status: String? = statusResult.getOrNull()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the onSuccess and onFailure returns Result you can chain most of these API calls like following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val status: String = runCatching { 
  repository.userStatusNetworkRequest("username")
}
.onSuccess {}
.onFailure {}
.getOrDefault("STATUS_UNKNOWN")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Using &lt;code&gt;recover { }&lt;/code&gt; API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The recover API allows you to handle the error and recover from there with a fallback value of the same data type. See the following example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val status: Result&amp;lt;String&amp;gt; = runCatching { 
  repository.userStatusNetworkRequest("username")
}
.onSuccess {}
.onFailure {}
.recover { error: Throwable -&amp;gt; "STATUS_UNKNOWN" }  

println(status.isSuccess) // Prints "true" when error is received
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Using &lt;code&gt;fold {}&lt;/code&gt; API to map data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fold extension function allows you to map the error to a different data type you wish. In this example, I kept the user status as String.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val status: String = runCatching {
  repository.userStatusNetworkRequest("username")
} 
.onSuccess {} 
.onFailure {} 
.fold(
    onSuccess = { status: String -&amp;gt; status },
    onFailure = { error: Throwable -&amp;gt; "STATUS_UNKNOWN" } 
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aside from these, there are some additional useful functions and extension functions for Result , take a look at &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/"&gt;official documentation&lt;/a&gt; for more APIs.&lt;/p&gt;

&lt;p&gt;I hope this was useful or a new discovery for you as it was for me 😊&lt;/p&gt;

</description>
      <category>kotlinresult</category>
      <category>kotlinerrorhandling</category>
      <category>kotlincoroutines</category>
      <category>kotlinruncatching</category>
    </item>
    <item>
      <title>Source code syntax highlighting on Android — Taking full control</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Sat, 18 Jul 2020 22:13:00 +0000</pubDate>
      <link>https://dev.to/hossain/source-code-syntax-highlighting-on-android-taking-full-control-4p5d</link>
      <guid>https://dev.to/hossain/source-code-syntax-highlighting-on-android-taking-full-control-4p5d</guid>
      <description>&lt;h3&gt;
  
  
  Source code syntax highlighting on Android — Taking full control
&lt;/h3&gt;

&lt;p&gt;Android and it’s community has evolved a lot over past decade. Now a days we can find open-source libraries to do almost anything — Zoomable ImageView, CameraX, RecyclerView Sticky Header, Tooltip, and many more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TEjxJRUz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AYwlp-1OAf-_EgtyR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TEjxJRUz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AYwlp-1OAf-_EgtyR" alt=""&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@hishahadat?utm_source=medium&amp;amp;utm_medium=referral"&gt;Shahadat Rahman&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Syntax highlighting is one of them and there are &lt;a href="https://github.com/kbiakov/CodeView-Android"&gt;few&lt;/a&gt; &lt;a href="https://github.com/PDDStudio/highlightjs-android"&gt;libraries&lt;/a&gt; &lt;a href="https://github.com/Badranh/Syntax-View-Android"&gt;for&lt;/a&gt; &lt;a href="https://github.com/testica/codeeditor"&gt;that&lt;/a&gt; too. If this is a solved problem, then why am I writing about another solution then?&lt;/p&gt;

&lt;p&gt;Well, some of the libraries I have explored are outdated, and some of them might not be as feature rich. So, I wanted to explore how to do-it-myself and write about it so that anybody can take full advantage of well-know JavaScript libraries for syntax highlighting in their Android app.&lt;/p&gt;

&lt;p&gt;👍 &lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete control over how JS plugin is used in the app&lt;/li&gt;
&lt;li&gt;Complete control over the Android code that renders it using WebView&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👎 &lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to have some understanding of web technologies, namely HTML, CSS and JavaScript&lt;/li&gt;
&lt;li&gt;You need to build your own Android CustomView or Fragment to render highlighted syntax. &lt;em&gt;(No worries — this is where this article comes in to help you do that _🤗&lt;/em&gt;)_&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the pros and cons in mind, lets explore some well-established and proven syntax highlighting libraries&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://prismjs.com/index.html"&gt;&lt;strong&gt;PrismJS&lt;/strong&gt;&lt;/a&gt; — Very light weight 2KB-100+KB with extensive plugin collection
“&lt;em&gt;Lightweight, robust, elegant syntax highlighting.&lt;/em&gt;”&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://highlightjs.org/"&gt;&lt;strong&gt;highlight.js&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt; &lt;/strong&gt; — Fully loaded 25KB-100KB and simple syntax highlighting
“&lt;em&gt;Syntax highlighting for the Web”&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://craig.is/making/rainbows"&gt;&lt;strong&gt;Rainbow&lt;/strong&gt;&lt;/a&gt; — Another popular lightweight 6KB-25KB highlighter
“&lt;em&gt;Rainbow is a code syntax highlighting library written in Javascript.&lt;/em&gt;”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many more libraries that does the job, however for rest of the example I will be using PrismJS because of it’s highly modular nature. Note that, the process of using different library will be &lt;em&gt;almost&lt;/em&gt; the same.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 TIP: All the example code provided here is available in great detail in the GitHub repository mentioned below👇.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Setting up PrismJS Template
&lt;/h4&gt;

&lt;p&gt;We will be leveraging Android’sWebView to load syntax highlighting library with the source code that we want to be highlighted.&lt;/p&gt;

&lt;p&gt;This setup is going to be specific to library of your choice. Since we are focusing on PrimsJS, we will follow it’s &lt;a href="https://prismjs.com/#basic-usage"&gt;official guideline&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After you &lt;a href="https://prismjs.com/download.html"&gt;download&lt;/a&gt; the library JS and CSS file, you essentially need following HTML to render the highlighted source.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"themes/prism.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"prism.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"language-kotlin"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    data class Student(val name: String)
    &lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;TIP: You can download different CSS theme, and you can choose list of language and plugin you want to support (eg. Kotlin, Show line number)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Put the downloaded files in your Android app’s assets directory, ideally in www subfolder, like — src/main/assets/www/&lt;/p&gt;

&lt;p&gt;Now that we have a baseline for the template, next part is to convert it to Kotlin function that can take additional parameter to customize different plugin options. Here is template with some additional data needed for mobile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;prismJsHtmlContent&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;formattedSourceCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;showLineNumbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"""&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;

    &amp;lt;link href="www/prism.css" rel="stylesheet"/&amp;gt;
    &amp;lt;script src="www/prism.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;pre class="${if ( showLineNumbers ) "line-numbers" else ""}"&amp;gt;
&amp;lt;code class="language-${language}"&amp;gt;${formattedSourceCode}&amp;lt;/code&amp;gt;
&amp;lt;/pre&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
"""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Defining Syntax Highlighting Custom-View
&lt;/h4&gt;

&lt;p&gt;Making your own custom view is a great way to enhance capabilities. In this case we want to make our custom-view extend from &lt;code&gt;WebView&lt;/code&gt; so that we can load the template we just defined with source code at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;your.app.code&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SyntaxHighlighterWebView&lt;/span&gt; &lt;span class="nd"&gt;@JvmOverloads&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AttributeSet&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;defStyleAttr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebView&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defStyleAttr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ANDROID_ASSETS_PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"file:///android_asset/"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Our exposed function to show highlighted syntax&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;bindSyntaxHighlighter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;formattedSourceCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;showLineNumbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;javaScriptEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;

         &lt;span class="nf"&gt;loadDataWithBaseURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;ANDROID_ASSETS_PATH&lt;/span&gt; &lt;span class="cm"&gt;/* baseUrl */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nf"&gt;prismJsHtmlContent&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
               &lt;span class="n"&gt;formattedSourceCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
               &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
               &lt;span class="n"&gt;showLineNumbers&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cm"&gt;/* html-data */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"text/html"&lt;/span&gt; &lt;span class="cm"&gt;/* mimeType */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="cm"&gt;/* encoding */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="cm"&gt;/* failUrl */&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Using the Custom-View in App
&lt;/h4&gt;

&lt;p&gt;Now that we have our &lt;code&gt;SyntaxHighlighterWebView&lt;/code&gt; custom-view with &lt;code&gt;bindSyntaxHighlighter()&lt;/code&gt; function, we can use it from Activity or Fragment layout.&lt;/p&gt;

&lt;p&gt;All we need to do is use the view in XML layout like following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;your.app.code.SyntaxHighlighterWebView&lt;/span&gt;
  &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/syntax_highlighter_webview"&lt;/span&gt;
  &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"match_parent"&lt;/span&gt;
  &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"match_parent"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And from your Activity or Fragment get reference to the view and bind the source code like following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;highlighter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SyntaxHighlighterWebView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syntax_highlighter_webview&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;highlighter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bindSyntaxHighlighter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;formattedSourceCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"data class Student(val name: String)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"kotlin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;showLineNumbers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SBEIKe14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AMvZ2lVaLqGOW5j0hC6Mitg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SBEIKe14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AMvZ2lVaLqGOW5j0hC6Mitg.png" alt=""&gt;&lt;/a&gt;Highlighted Syntax Loaded via SyntaxHighlighterWebView&lt;/p&gt;

&lt;p&gt;That’s it, once you load the app you should see highlighted syntax on the screen where you have put the custom-view.&lt;/p&gt;

&lt;p&gt;All the example source code provided here is available in following GitHub repository. As bonus, I have also provided example of how to use highlight.js too.&lt;/p&gt;

&lt;p&gt;If you find any issue, leave a comment here or report an issue at GitHub repository. Hope it helps somebody. ✌️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/amardeshbd/android-syntax-highlighter"&gt;amardeshbd/android-syntax-highlighter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w15tBSxw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AmOgncKuhm9vj_kpE1LupSg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w15tBSxw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AmOgncKuhm9vj_kpE1LupSg.jpeg" alt=""&gt;&lt;/a&gt;Set of screenshots taken from the demo app&lt;/p&gt;

</description>
      <category>androiddev</category>
      <category>androidsyntaxhighlig</category>
      <category>android</category>
      <category>syntaxhighlighting</category>
    </item>
    <item>
      <title>Setup Android Gradle based Firebase App Distribution with GitHub Actions CI</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Tue, 16 Jun 2020 12:50:37 +0000</pubDate>
      <link>https://dev.to/hossain/setup-android-gradle-based-firebase-app-distribution-with-github-actions-ci-2nen</link>
      <guid>https://dev.to/hossain/setup-android-gradle-based-firebase-app-distribution-with-github-actions-ci-2nen</guid>
      <description>&lt;h3&gt;
  
  
  Setup Android Gradle based Firebase App Distribution with Github Actions CI
&lt;/h3&gt;

&lt;p&gt;This is a quick guide on how you can easily set up Github Actions CI workflow to automatically post APK to &lt;a href="https://firebase.google.com/docs/app-distribution"&gt;Firebase App Distribution&lt;/a&gt; on merge to release or master (or soon to be known as main) branch.&lt;/p&gt;

&lt;p&gt;Firebase already has an &lt;a href="https://firebase.google.com/docs/app-distribution/android/distribute-gradle"&gt;&lt;strong&gt;excellent guide&lt;/strong&gt;&lt;/a&gt; 🏆 on how to set up the Gradle task on your Android project to post APK to App Distribution. However, I will quickly touch those areas using the easiest path ✌️.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;PREREQUISITE&lt;/strong&gt; : You already have Firebase project setup for the app with google-services.json file in your Android app project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Setup Gradle for the App
&lt;/h4&gt;

&lt;p&gt;On your root &lt;code&gt;build.gradle&lt;/code&gt; file add the Gradle plugin&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    // .. other existing deps here
    classpath 'com.google.firebase:firebase-appdistribution-gradle:2.0.0'
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, apply the plugin in your Android app’s &lt;code&gt;build.gradle&lt;/code&gt; and distribution properties. See the &lt;a href="https://firebase.google.com/docs/app-distribution/android/distribute-gradle#step_3_configure_your_distribution_properties"&gt;official guide&lt;/a&gt; for the full list of supported parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apply plugin: 'com.google.firebase.appdistribution'

android {
    buildTypes {
        release { // NOTE: `debug` can have different cofigs
            firebaseAppDistribution {
                releaseNotes="Release notes at bit.ly/notes"
                groups="qa" // see docs for more options 
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Generate Firebase Token
&lt;/h4&gt;

&lt;p&gt;Firebase has &lt;a href="https://firebase.google.com/docs/app-distribution/android/distribute-gradle#step_2_authenticate_with_firebase"&gt;3 different documented ways&lt;/a&gt; you can authenticate to be able to upload the APK. Generating the token is one of the easiest that I will focus on with snapshot images so that you can easily relate to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3iOB9lZq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/824/1%2A_EC-NBCkx-VwM9m_0O1D6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3iOB9lZq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/824/1%2A_EC-NBCkx-VwM9m_0O1D6g.png" alt=""&gt;&lt;/a&gt;From the root of your android app project, run following Gradle command which will give you a URL to authenticate for the Firebase project that app uses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./gradlew appDistributionLogin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Copy the URL &lt;a href="https://accounts.google.com/o/..../auth/cloud-platform"&gt;https://accounts.google.com/o/..../auth/cloud-platform&lt;/a&gt; and paste it in browser and login with the Google account which has write access to the Firebase project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3d-ojx8m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/814/1%2ABWREQVxtFP-tTRq0QrbXYw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3d-ojx8m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/814/1%2ABWREQVxtFP-tTRq0QrbXYw.png" alt=""&gt;&lt;/a&gt;Once authorized, you should get &lt;code&gt;FIREBASE_TOKEN&lt;/code&gt; in the console. Save it for later use.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup Secrets for GitHub CI Workflow
&lt;/h4&gt;

&lt;p&gt;Go to your GitHub project and the &lt;code&gt;FIREBASE_TOKEN&lt;/code&gt; as secret property.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C3CILGGf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A5E6cFFg4aMiJv58WLyS9uA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C3CILGGf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A5E6cFFg4aMiJv58WLyS9uA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup GitHub Actions CI Workflow
&lt;/h4&gt;

&lt;p&gt;Based on git-flow, we want to setup the CI job such that whenever we merge commit to master branch it triggers the release build. You can obviously &lt;a href="https://help.github.com/en/actions/reference/events-that-trigger-workflows"&gt;customize the behavior&lt;/a&gt; based on your need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Firebase App Distribution

on:
  push:
    branches:
      - master

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: set up JDK 1.8
        uses: actions/setup-java@v1
        with:
          java-version: 1.8

      - name: Firebase App Distribute
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
        run: ./gradlew assembleRelease appDistributionUploadRelease
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That’s it, you should have a working GitHub workflow that automatically sends APK to Firebase App Distribution as soon as there is a commit on master branch.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://github.com/amardeshbd/android-police-brutality-incidents/pull/117/files"&gt;pull-request I made&lt;/a&gt; to add support for this in one of my side projects.&lt;/p&gt;

&lt;p&gt;If you find any issues please let me know I will try to help. Good luck 👍&lt;/p&gt;

&lt;h4&gt;
  
  
  Additional Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Firebase App Distribution — &lt;a href="https://firebase.google.com/docs/app-distribution"&gt;https://firebase.google.com/docs/app-distribution&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Actions — &lt;a href="https://help.github.com/en/actions"&gt;https://help.github.com/en/actions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pull Request to support Firebase App Distribution — &lt;a href="https://github.com/amardeshbd/android-police-brutality-incidents/pull/117/files"&gt;https://github.com/amardeshbd/android-police-brutality-incidents/pull/117/files&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>firebaseappdistribution</category>
      <category>githubactions</category>
      <category>android</category>
      <category>androidappdevelopment</category>
    </item>
    <item>
      <title>Android app to browse and share incidents during protest for George Floyd </title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Thu, 11 Jun 2020 19:17:30 +0000</pubDate>
      <link>https://dev.to/hossain/android-app-to-browse-and-share-incidents-during-protest-for-george-floyd-4e2i</link>
      <guid>https://dev.to/hossain/android-app-to-browse-and-share-incidents-during-protest-for-george-floyd-4e2i</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aQxmjcgN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8ej5jtggsaobg9k1zrlw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aQxmjcgN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8ej5jtggsaobg9k1zrlw.png" alt="Android App for 2020 Police Brutality Incidents"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the midst of COVID-19, the past 2 weeks have been horrific from many fronts, especially after the murder of &lt;code&gt;#GeorgeFloyd&lt;/code&gt; on May 25h. It gives me hope to see how the issue resonated among Americans and Canadians. It IS time to speak up, to be silent is to be complicit.&lt;/p&gt;

&lt;p&gt;As an engineer, I am overwhelmed to see how communities came together both online and offline. One online community that is trying to actively &lt;a href="https://github.com/2020PB/police-brutality"&gt;document&lt;/a&gt; and preserve all #PoliceBrutality incidents during peaceful  protest is on Reddit at &lt;a href="https://www.reddit.com/r/2020PoliceBrutality/"&gt;r/2020PoliceBrutality/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also wanted to participate in spreading the words &lt;code&gt;#BlackLivesMatter&lt;/code&gt; &lt;code&gt;#JusticeForGeorgeFloyd&lt;/code&gt; by making a simple Android app that uses data collected by the subreddit &lt;a href="https://www.reddit.com/r/2020PoliceBrutality/"&gt;r/2020PoliceBrutality/&lt;/a&gt; community. I hope more people become aware and take smaller actions towards making the world a better place.&lt;/p&gt;

&lt;p&gt;Currently, the app is pending approval to be published in Google Play store, however, if you want to test drive it, it's available on &lt;a href="https://github.com/amardeshbd/android-police-brutality-incidents"&gt;GitHub&lt;/a&gt; to manually download and install on your phone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/amardeshbd/android-police-brutality-incidents/releases"&gt;https://github.com/amardeshbd/android-police-brutality-incidents/releases&lt;/a&gt;&lt;/p&gt;

</description>
      <category>2020policebrutality</category>
      <category>blacklivesmatter</category>
      <category>justiceforgeorgefloyd</category>
      <category>androidapp</category>
    </item>
    <item>
      <title>How to take your beginner Android skills to the next level by studying open-source Android Apps</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Thu, 02 Apr 2020 03:06:58 +0000</pubDate>
      <link>https://dev.to/hossain/how-to-take-your-beginner-android-skills-to-the-next-level-by-studying-open-source-android-apps-35hh</link>
      <guid>https://dev.to/hossain/how-to-take-your-beginner-android-skills-to-the-next-level-by-studying-open-source-android-apps-35hh</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tyGcg1tZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AiJVVNMgDbOefR2A3XFNRyQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tyGcg1tZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AiJVVNMgDbOefR2A3XFNRyQ.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This list of open-source Android apps may come in handy if you have grasped all necessary concepts to develop Android application and you think you are ready to work on an application that follows industry standards. By industry standards, I mean, an app that has good architecture, is scalable and maintainable in the long run.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;FYI: Google has an 📚&lt;a href="https://developer.android.com/jetpack/docs/guide"&gt;&lt;strong&gt;official architecture guide for Android application&lt;/strong&gt;&lt;/a&gt; which captures what most of the sample application does. I highly recommend you read that article first before studying any open-source applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ⚙️ Android Architecture Blueprints v2
&lt;/h3&gt;

&lt;p&gt;This is Google’s official app that showcases the usage of some of the key &lt;a href="https://developer.android.com/jetpack"&gt;Jetpack&lt;/a&gt; components to make a sustainable app. This is a good starting point to get an understanding of how to architect an app.&lt;/p&gt;

&lt;p&gt;They do have a vanilla implementation in master branch, however, I will focus on dagger-android branch that has dagger support &lt;em&gt;(see README)&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Kotlin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture:&lt;/strong&gt; MVVM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Injection:&lt;/strong&gt; Dagger&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation:&lt;/strong&gt; Jetpack Navigation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests:&lt;/strong&gt; JUnit and Espresso&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/android/architecture-samples"&gt;https://github.com/android/architecture-samples&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👔 Plaid 2.0 — Showcasing Material Design
&lt;/h3&gt;

&lt;p&gt;In early days Plaid 1.0 application (created in 2014) was &lt;a href="https://twitter.com/crafty"&gt;Nick Butcher&lt;/a&gt;’s app where he showcased how &lt;a href="https://material.io/"&gt;material design&lt;/a&gt; and animation can bring joy and life to an Android application. After years of improving, the Plaid app has come to a point where it makes perfect sense to make this app a reference app that showcases how an ideal Android application can be built using material design and fluid animation. So, in 2019, Nick did exactly that, they have moved the Plaid Github repo to Google’s official repository — &lt;a href="https://medium.com/@crafty/restitching-plaid-9ca5588d3b0a"&gt;here is the article explaining the move and goal&lt;/a&gt; &lt;em&gt;(I highly recommend to read it)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;NOTE: The Plaid 2.0 is still under heavy development, which as a bonus, gives you an opportunity to learn how an application is migrated to modern architecture and Kotlin. See the Github project page with different technical articles explaining how the app is being migrated to 2.0&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key Features:&lt;/strong&gt; Material Design, Android Theming, Dark Mode, Multi-Module, Animation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Kotlin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture:&lt;/strong&gt; MVVM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Injection:&lt;/strong&gt; Dagger&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation:&lt;/strong&gt; Plain (intent based)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests:&lt;/strong&gt; JUnit and Espresso&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/android/plaid"&gt;https://github.com/android/plaid&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🌻 Sunflower — Showcasing Android Jetpack
&lt;/h3&gt;

&lt;p&gt;This is another Google’s official app that showcases many Jetpack components in one application.&lt;/p&gt;

&lt;p&gt;This is a minimal application that is great for learning.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key Features:&lt;/strong&gt; Dark Mode, Animation, Room (Database), WorkManager&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Kotlin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture:&lt;/strong&gt; MVVM (LiveData, ViewModel, Lifecycle, Data Binding)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Injection:&lt;/strong&gt; Dagger&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation:&lt;/strong&gt; Jetpack Navigation (Single Activity)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests:&lt;/strong&gt; JUnit and Espresso&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/android/sunflower"&gt;https://github.com/android/sunflower&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VPLi7vpS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A-GJ_m-AWu8Ro235pXt8_AA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VPLi7vpS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A-GJ_m-AWu8Ro235pXt8_AA.png" alt=""&gt;&lt;/a&gt;Sunflower — Demo Screenshots&lt;/p&gt;

&lt;h3&gt;
  
  
  🔖 CatchUp — All in one
&lt;/h3&gt;

&lt;p&gt;This app aggregates articles and posts from different services like Hackernews, Medium, Reddit, Slashdot, Dribble, Uplabs and so on. This is a very recent app from &lt;a href="https://www.zacsweers.dev/"&gt;Zac Sweers&lt;/a&gt; who has put a significant amount of time to develop this app. The app architecture is &lt;a href="https://github.com/ZacSweers/CatchUp#influences"&gt;inspired&lt;/a&gt; by Plaid and U+2020 app. CatchUp is being actively developed, you can clone and build locally to try it out.&lt;/p&gt;

&lt;p&gt;Please note, this is kinda large scale complex application that is well done and contains many advanced techniques. So, if you are a beginner, I would postpone looking into the application towards the end of your study ^_^&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ps. I, myself have &lt;a href="https://twitter.com/rharter/status/1240773309316378626?s=20"&gt;found&lt;/a&gt; this application recently. Personally I like how the Gradle script is organized using Kotlin based Gradle scripts (kts), and how the dagger is used extensively to manage dependencies of different component and service implementations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key Features:&lt;/strong&gt; Dark Mode, Animation, Advanced Dagger,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Kotlin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture: &lt;/strong&gt; — (Not sure, I need to study this app more)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Injection:&lt;/strong&gt; Dagger (Advanced usage)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation:&lt;/strong&gt; Basic (Intent Based)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests:&lt;/strong&gt; Some JUnit tests exist (not priority).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/ZacSweers/CatchUp"&gt;https://github.com/ZacSweers/CatchUp&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Mnclz5T2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AxBXnT6pYfL_4LFI1r7rp5Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Mnclz5T2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AxBXnT6pYfL_4LFI1r7rp5Q.png" alt=""&gt;&lt;/a&gt;CatchUP — Demo Screenshots&lt;/p&gt;

&lt;h3&gt;
  
  
  Honorable mention
&lt;/h3&gt;

&lt;p&gt;Here are some open-source apps that are worth mentioning:&lt;/p&gt;

&lt;h4&gt;
  
  
  🎤 Google I/O Annual Conference App
&lt;/h4&gt;

&lt;p&gt;Since 2011 the Google I/O companion application has been the spotlight application by Google that showcases the latest Android features. It recently incorporated Android Wear and Auto variants too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/google/iosched/issues/309"&gt;Historically&lt;/a&gt; the source code of the app is released 3–5 months after the conference. Still, it is a great complete application for studying.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key Features:&lt;/strong&gt; Multiform factor (Mobile, Tablet, Wear OS, Auto, TV), Firebase, WorkManager, Dark Mode (2019)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Kotlin (2018-2019+), Java (2011–2017)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture:&lt;/strong&gt; MVVM (2019)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Injection:&lt;/strong&gt; Dagger&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation:&lt;/strong&gt; Jetpack Navigation (2019)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests:&lt;/strong&gt; JUnit and Espresso&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/google/iosched"&gt;https://github.com/google/iosched&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VA04ljL3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AwYRpiq0kWWgx9q1TVJzapg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VA04ljL3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AwYRpiq0kWWgx9q1TVJzapg.png" alt=""&gt;&lt;/a&gt;Google I/O 2019 — Demo Screenshots&lt;/p&gt;

&lt;h4&gt;
  
  
  📺 Tivi — Track Show Tracking
&lt;/h4&gt;

&lt;p&gt;Tivi app is currently in the early stages developed by &lt;a href="https://twitter.com/chrisbanes"&gt;Chris Banes&lt;/a&gt;, a member of the Android Developer Relations team. He closely works with &lt;a href="https://twitter.com/crafty"&gt;Nick Butcher&lt;/a&gt;, author of Plaid.&lt;/p&gt;

&lt;p&gt;The app uses all the latest libraries and recommendations from Google. This app has a very modular structure, almost everything is divided into modules (about 29 modules as of writing). This app will be a good study material when the app goes into the beta stage. Now, it’s too early to recommend as main study material.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key Features:&lt;/strong&gt; Dark Mode, Multi-Module, Multi-Service (Trakt, TMDb), Room (Database), RxJava 2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Kotlin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture:&lt;/strong&gt; MVVM + Lifecycle + LiveData&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Injection:&lt;/strong&gt; Dagger&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation:&lt;/strong&gt; Jetpack Navigation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests:&lt;/strong&gt; JUnit (Under active development)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/chrisbanes/tivi"&gt;https://github.com/chrisbanes/tivi&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XS8ukAWI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AlBKt8TfnSonAiD5F904TQg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XS8ukAWI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AlBKt8TfnSonAiD5F904TQg.png" alt=""&gt;&lt;/a&gt;Tivi — Demo Screenshots&lt;/p&gt;




&lt;p&gt;I will continue to extend this article to include more community projects. Feel free to share projects that are ideal for learning. Thanks 🙏&lt;/p&gt;

</description>
      <category>androidapps</category>
      <category>android</category>
      <category>androiddev</category>
    </item>
    <item>
      <title>Clickable link text for Android TextView — Kotlin Extension</title>
      <dc:creator>Hossain Khan</dc:creator>
      <pubDate>Thu, 02 Jan 2020 00:53:57 +0000</pubDate>
      <link>https://dev.to/hossain/clickable-link-text-for-android-textview-kotlin-extension-2k9j</link>
      <guid>https://dev.to/hossain/clickable-link-text-for-android-textview-kotlin-extension-2k9j</guid>
      <description>&lt;h3&gt;
  
  
  Clickable link text for Android TextView — Kotlin Extension
&lt;/h3&gt;

&lt;p&gt;Recently I have had to create UI that required user tappable/clickable text in the same text view. I know this is kind of unusual as the touch target for the view will likely be smaller compared to a button with no outline style. However, I wanted to share a quick Kotlin extension function that is dynamic and well tested.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--itFnMh1I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/868/1%2A_xZdmLNOUR7vrQ-jQYomxA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--itFnMh1I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/868/1%2A_xZdmLNOUR7vrQ-jQYomxA.png" alt=""&gt;&lt;/a&gt;Android TextView where “Register Now” is a tappable link with a callback.&lt;/p&gt;

&lt;p&gt;Here is an example usage for generating clickable link within the same TextView&lt;/p&gt;

&lt;p&gt;So, if your project requires something like this take a look at the following extension function for android.widget.TextView.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;You should be able to easily convert this to Java if needed. Let me know if you find this useful. Cheers ✌️&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlinextensionfun</category>
      <category>androidtextview</category>
      <category>kotlin</category>
    </item>
  </channel>
</rss>
