<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Offline-First-App on MangoDriod</title><link>https://md.eknath.dev/tags/offline-first-app/</link><description>Recent content in Offline-First-App on MangoDriod</description><generator>Hugo -- 0.141.0</generator><language>en-us</language><lastBuildDate>Sun, 25 May 2025 08:18:39 +0530</lastBuildDate><atom:link href="https://md.eknath.dev/tags/offline-first-app/index.xml" rel="self" type="application/rss+xml"/><item><title>Mastering Raw Queries in Room: Why, When &amp; When Not to Use Them (#OF06)</title><link>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-6/</link><pubDate>Sun, 25 May 2025 08:18:39 +0530</pubDate><guid>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-6/</guid><description>&lt;p>In the &lt;a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-5/">previous article&lt;/a>, we explored the power and flexibility of DAOs with Room’s built-in query annotations. But what if your query needs don’t fit into Room’s constraints? Enter @RawQuery – the most flexible and dangerous tool in the Room arsenal.&lt;/p>
&lt;p>Let’s dive into what &lt;code>@RawQuery&lt;/code> is, when it shines and shuns.&lt;/p>
&lt;hr>
&lt;h2 id="what-is-rawquery">What is @RawQuery?&lt;/h2>
&lt;p>&lt;code>@RawQuery&lt;/code> allows you to execute &lt;strong>SQL statements that aren’t validated at compile time&lt;/strong>. Unlike &lt;code>@Query&lt;/code>, which Room parses and validates during compilation, raw queries are evaluated at runtime.&lt;/p></description><content:encoded><![CDATA[<p>In the <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-5/">previous article</a>, we explored the power and flexibility of DAOs with Room’s built-in query annotations. But what if your query needs don’t fit into Room’s constraints? Enter @RawQuery – the most flexible and dangerous tool in the Room arsenal.</p>
<p>Let’s dive into what <code>@RawQuery</code> is, when it shines and shuns.</p>
<hr>
<h2 id="what-is-rawquery">What is @RawQuery?</h2>
<p><code>@RawQuery</code> allows you to execute <strong>SQL statements that aren’t validated at compile time</strong>. Unlike <code>@Query</code>, which Room parses and validates during compilation, raw queries are evaluated at runtime.</p>
<p>You typically use <code>@RawQuery</code> with either SupportSQLiteQuery (for fully dynamic queries) or plain String (though the latter is limited and discouraged).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Dao</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">ProductDao</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@RawQuery</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">getProductsWithRawQuery</span>(query: SupportSQLiteQuery): List&lt;Product&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// This will be called from the repository
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@Transaction</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">getProductsAbovePrice</span>(minPrice: Int): List&lt;Product&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> query = SimpleSQLiteQuery(
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;SELECT * FROM products WHERE price &gt; ?&#34;</span>,
</span></span><span style="display:flex;"><span>        arrayOf(minPrice)
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> productDao.getProductsWithRawQuery(query)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="-when-should-you-use-rawquery">✅ When Should You Use @RawQuery?</h3>
<p>Despite its risks, there are legitimate use cases for <code>@RawQuery</code> which come handy when the app is completely offline and need to do operations similar to server with available data for example sorting, filtering and searching etc.</p>
<p>There are some cases where you need advanced joins, unions, subqueries Room’s <code>@Query</code> might fall short an example i can think of are expense list screen where you need to get associated budgets,tags, users who created and approved them, etc getting this merged data specific filter and sort type will be extremely hard with conventional methods in cases like these <code>@RawQuery</code> are a boon.</p>
<p>This is one of my dao&rsquo;s functions that is triggered when the there applies a filter, since there was no network i apply the filter and show the available data assuming the user already knows he is in offline mode,we have an indicator that should handle conveying of the message. So take a look and the params and tell me can we acheve this using conventional method faster than this ? i don&rsquo;t think so but do share your views.</p>
<p>Lets check out my code for</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@RawQuery</span> <span style="color:#75715e">// Query runner
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">getExpensesWithParamsQueryRunner</span>(query: SupportSQLiteQuery): List&lt;LocalExpenseWithDetails&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@Transaction</span> <span style="color:#75715e">// this will be called as a fallback option when offline
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">getExpensesWithParams</span>(
</span></span><span style="display:flex;"><span>        type: String?,
</span></span><span style="display:flex;"><span>        budgetId: Long?,
</span></span><span style="display:flex;"><span>        sessionId: Long?,
</span></span><span style="display:flex;"><span>        filterParam: FilterParams,
</span></span><span style="display:flex;"><span>        sortParam: SortParam,
</span></span><span style="display:flex;"><span>        searchQuery: String?,
</span></span><span style="display:flex;"><span>        page: Int
</span></span><span style="display:flex;"><span>    ): List&lt;LocalExpenseWithDetails&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">val</span> typeId = <span style="color:#66d9ef">if</span> (type <span style="color:#f92672">!=</span> <span style="color:#66d9ef">null</span> <span style="color:#f92672">&amp;&amp;</span> type <span style="color:#f92672">!=</span> <span style="color:#a6e22e">BaseExpenseType</span>.ALL) getExpenseTypeWithName(type.serverKey) <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">null</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> getExpensesWithParamsQueryRunner(
</span></span><span style="display:flex;"><span>            query = getExpenseQuery( <span style="color:#75715e">// query generator
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                typeId = typeId,
</span></span><span style="display:flex;"><span>                budgetId = budgetId,
</span></span><span style="display:flex;"><span>                sessionId = sessionId,
</span></span><span style="display:flex;"><span>                filterParam = filterParam,
</span></span><span style="display:flex;"><span>                sortParam = sortParam,
</span></span><span style="display:flex;"><span>                search = searchQuery,
</span></span><span style="display:flex;"><span>                page = page
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><ol start="3">
<li>Performance Testing / Debugging
During development, you might want to test different raw SQL statements quickly without baking them into DAOs.</li>
</ol>
<h3 id="-when-not-to-use-rawquery">⚠️ When NOT to Use @RawQuery</h3>
<p>Just because you can doesn’t mean you should. Raw queries come with trade-offs:</p>
<ul>
<li>❌ No Compile-Time Safety</li>
</ul>
<p>Room won’t validate your SQL. Typos, wrong column names, or invalid SQL will only fail at runtime – often with vague errors.</p>
<ul>
<li>❌ No Type Inference</li>
</ul>
<p>Unlike @Query, Room won’t know what result type to expect unless you specify it manually and correctly.</p>
<ul>
<li>❌ Risk of SQL Injection
If you’re concatenating SQL strings, you open yourself to SQL injection vulnerabilities. Always use parameterized queries or filter the vulnerable queries.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// Bad ❌
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">val</span> query = SimpleSQLiteQuery(<span style="color:#e6db74">&#34;SELECT * FROM products WHERE name = &#39;</span><span style="color:#e6db74">$name</span><span style="color:#e6db74">&#39;&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Good ✅
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">val</span> query = SimpleSQLiteQuery(<span style="color:#e6db74">&#34;SELECT * FROM products WHERE name = ?&#34;</span>, arrayOf(name))
</span></span></code></pre></div><h3 id="-tips-for-safely-using-raw-queries">📋 Tips for Safely Using Raw Queries</h3>
<ul>
<li>✅ Always use SimpleSQLiteQuery with parameterized arguments.</li>
<li>✅ Keep the query logic isolated and well-documented.</li>
<li>✅ Prefer @Query whenever possible.</li>
<li>✅ Avoid user-generated input directly in SQL strings.</li>
<li>✅ Write tests to validate dynamic query paths.</li>
</ul>
<h3 id="-anti-patterns-to-avoid">🚫 Anti-Patterns to Avoid</h3>
<ul>
<li>❌ Using raw queries as your primary query method.</li>
<li>❌ Skipping query reuse – dynamic doesn’t mean you can’t structure it.</li>
<li>❌ Using @RawQuery when @Query or DAO methods would suffice.</li>
<li>❌ Ignoring test coverage for raw query logic.</li>
</ul>
<p>I’ve even created my own query builder to simplify this process in my codebase. Whether you should use it or not depends on your needs, but I’m sharing it just so you know it exists because it was extremely useful for me. The code for my query builder can be found here: <a href="https://gist.github.com/Eganathan/6692e2ea77fe51daf02f9e34a036f1b5">CustomQueryBuilder</a></p>
<h2 id="-conclusion">🚀 Conclusion</h2>
<p><code>@RawQuery</code> is the escape hatch when Room’s abstraction becomes a cage. Use it when needed, but use it wisely. Think of it like the goto statement of Room – powerful but potentially dangerous if overused or misused.</p>
<p>In most offline-first app cases, well-structured DAOs using Room’s annotations will suffice. But in edge cases where flexibility is key, <code>@RawQuery</code> gives you that last-mile control.</p>
<p><strong>Next up, we’ll explore Query Optimization Tips to keep your Offline-First App lightning fast, even at scale.</strong></p>
<p><strong>Next we will explore Data Access Objects</strong>, Stay tuned for the next article in this series! 🚀</p>
<h2 id="final-thoughts"><strong>Final Thoughts</strong></h2>
<p>Raw queries are sharp tools — excellent in skilled hands, but risky for the unprepared. If you’ve got a use case or edge case where @RawQuery saved your app or made something possible, I’d love to hear it!</p>
<p>Feel free to connect and share your stories:
📩 <strong><a href="mailto:mail@eknath.dev">Email</a></strong><br>
🌍 <strong><a href="https://eknath.dev">Website</a></strong><br>
💫 <strong><a href="https://www.linkedin.com/posts/eganathan_offlinefirstandroid-offlinefirst-android-activity-7294912159627546624-TG77?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAABYcOpgBgvDfy-0uUjfX0HTNqzzLfKZQAQU">LinkedIn-Post for comments and feedbacks</a></strong></p>
<p>🔖 <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-5/">Previous Article in this Series</a>
🚀 <strong>Stay tuned for Part 7!</strong> 🚀</p>
<!-- 🔖 [Next Article in this Series](https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-7/) -->]]></content:encoded></item><item><title>DAO: The Backbone of Offline-First Apps (#OF05)</title><link>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-5/</link><pubDate>Sun, 09 Mar 2025 13:18:39 +0530</pubDate><guid>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-5/</guid><description>&lt;p>In our &lt;a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-4/">previous article&lt;/a>, we covered &lt;strong>Entities&lt;/strong> in Room. Now, let’s explore &lt;strong>DAOs (Data Access Objects)&lt;/strong> – the bridge between your app and the database. DAOs allow you to execute queries that allow is to insert, update, and delete records efficiently and thats exactly what we are going to explore today.&lt;/p>
&lt;hr>
&lt;h2 id="what-is-a-dao">What is a DAO?&lt;/h2>
&lt;p>A DAO is an interface annotated with @Dao that defines methods for interacting with the database. The Room compiler generates the necessary implementation for these interfaces and its methods, which enables us to access the database efficient and type safe.&lt;/p></description><content:encoded><![CDATA[<p>In our <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-4/">previous article</a>, we covered <strong>Entities</strong> in Room. Now, let’s explore <strong>DAOs (Data Access Objects)</strong> – the bridge between your app and the database. DAOs allow you to execute queries that allow is to insert, update, and delete records efficiently and thats exactly what we are going to explore today.</p>
<hr>
<h2 id="what-is-a-dao">What is a DAO?</h2>
<p>A DAO is an interface annotated with @Dao that defines methods for interacting with the database. The Room compiler generates the necessary implementation for these interfaces and its methods, which enables us to access the database efficient and type safe.</p>
<p>A typical DAO will look simar as the code below:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Dao</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">ProductDao</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@Query</span>(<span style="color:#e6db74">&#34;SELECT * FROM products&#34;</span>)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">getAllProducts</span>(): List&lt;Product&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@Query</span>(<span style="color:#e6db74">&#34;SELECT * FROM products WHERE id = :productId&#34;</span>)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">getProductById</span>(productId: Long): Product?
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@Insert</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">insertProduct</span>(product: Product)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@Insert</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">insertProducts</span>(products: List&lt;Product&gt;)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@Update</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">updateProduct</span>(product: Product)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@Delete</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">deleteProduct</span>(product: Product)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@Upsert</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">upsertProduct</span>(product: Product)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>While the <code>@Dao</code> interface is created by the developer the instance/Implementation of the DAO&rsquo;s are created by the compiler when the Database is created and the generated implementation file can be looked at the following path:</p>
<blockquote>
<p><code>app/build/generated/source/kapt/debug/YOUR_PACKAGE_NAME/database/ProductDao_Impl.java</code></p>
</blockquote>
<p>The file will contain the instance with <code>impl</code> as post fix and all the corresponding functions will have its own implementation accessing the SQLLite Database for example the <strong>select query</strong> annotated with <code>@Query</code> on the above <strong>DAO</strong> will generate an implementation code that is shared below, <strong>if you are to use SQLLite Directly this is the code you would have to write manually for each query</strong>:</p>
<p>The implementation of select query will look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#a6e22e">@Override</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> Object <span style="color:#a6e22e">getAllProducts</span>(Continuation<span style="color:#f92672">&lt;?</span> <span style="color:#66d9ef">super</span> List<span style="color:#f92672">&lt;</span>Product<span style="color:#f92672">&gt;&gt;</span> continuation) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">final</span> String _sql <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;SELECT * FROM products&#34;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">final</span> RoomSQLiteQuery _statement <span style="color:#f92672">=</span> RoomSQLiteQuery.<span style="color:#a6e22e">acquire</span>(_sql, 0);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> CoroutinesRoom.<span style="color:#a6e22e">execute</span>(__db, <span style="color:#66d9ef">false</span>, continuation, <span style="color:#66d9ef">new</span> Callable<span style="color:#f92672">&lt;</span>List<span style="color:#f92672">&lt;</span>Product<span style="color:#f92672">&gt;&gt;</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@Override</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> List<span style="color:#f92672">&lt;</span>Product<span style="color:#f92672">&gt;</span> <span style="color:#a6e22e">call</span>() <span style="color:#66d9ef">throws</span> Exception {
</span></span><span style="display:flex;"><span>            Cursor _cursor <span style="color:#f92672">=</span> DBUtil.<span style="color:#a6e22e">query</span>(__db, _statement, <span style="color:#66d9ef">false</span>, <span style="color:#66d9ef">null</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>                List<span style="color:#f92672">&lt;</span>Product<span style="color:#f92672">&gt;</span> _result <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> ArrayList<span style="color:#f92672">&lt;&gt;</span>();
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">while</span> (_cursor.<span style="color:#a6e22e">moveToNext</span>()) {
</span></span><span style="display:flex;"><span>                    Product _item <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> Product();
</span></span><span style="display:flex;"><span>                    _item.<span style="color:#a6e22e">id</span> <span style="color:#f92672">=</span> _cursor.<span style="color:#a6e22e">getLong</span>(_cursor.<span style="color:#a6e22e">getColumnIndexOrThrow</span>(<span style="color:#e6db74">&#34;id&#34;</span>));
</span></span><span style="display:flex;"><span>                    _item.<span style="color:#a6e22e">name</span> <span style="color:#f92672">=</span> _cursor.<span style="color:#a6e22e">getString</span>(_cursor.<span style="color:#a6e22e">getColumnIndexOrThrow</span>(<span style="color:#e6db74">&#34;name&#34;</span>));
</span></span><span style="display:flex;"><span>                    _item.<span style="color:#a6e22e">price</span> <span style="color:#f92672">=</span> _cursor.<span style="color:#a6e22e">getFloat</span>(_cursor.<span style="color:#a6e22e">getColumnIndexOrThrow</span>(<span style="color:#e6db74">&#34;price&#34;</span>));
</span></span><span style="display:flex;"><span>                    _result.<span style="color:#a6e22e">add</span>(_item);
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span> _result;
</span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">finally</span> {
</span></span><span style="display:flex;"><span>                _cursor.<span style="color:#a6e22e">close</span>();
</span></span><span style="display:flex;"><span>                _statement.<span style="color:#a6e22e">release</span>();
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> });
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Isn&rsquo;t it so nice how <strong>instead of writing 24 lines we just have to write 1</strong></p>
<p>🙏 <strong>Thank You, Android and Google Team for making this extremely robust and performant.</strong></p>
<p>Now that we got an intro on <strong>Dao</strong> and what happens behind the scenes, let&rsquo;s go through the supported query annotations.</p>
<h2 id="supported-query-annotations">Supported Query Annotations</h2>
<p>In the previous section of DAO example code, you might have noticed the annotations (<code>@Insert</code>,<code>@Delete</code>&hellip;) on each functions, these annotations help the compiler to generate query object on your behalf so we can smoothly interact with the database, Let&rsquo;s go through each of them to understand how and when to use them effectively.</p>
<h3 id="insert---insert-data-into-the-database">@Insert - Insert Data into the Database</h3>
<p>Used for <strong>inserting single or multiple entries to a table</strong>, this will <strong>skip validation of checking if a duplicate entry with the same primary id exists</strong>, so it&rsquo;s faster for insertion when you are clear that duplicate entries are impossible. In case <strong>you inset a duplicate entry the app will crash</strong> with the exception primary key already exists.</p>
<p>Here is an example of how the <code>@Insert</code> can be used:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#f92672">..</span>.
</span></span><span style="display:flex;"><span> <span style="color:#75715e">//Inserting single item
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">@Insert</span>(onConflict = <span style="color:#a6e22e">OnConflictStrategy</span>.REPLACE)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">insertProduct</span>(product: Product)     
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span> <span style="color:#75715e">//Inserting multiple items
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">@Insert</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">insertProducts</span>(products: List&lt;Product&gt;)     
</span></span><span style="display:flex;"><span><span style="color:#f92672">..</span>.
</span></span></code></pre></div><h4 id="conflict-strategy">Conflict Strategy</h4>
<p>Defining the conflict strategy on the query tells the room how to handle the conflict effectively by default it <code>NONE</code>:</p>
<ul>
<li><code>NONE</code>: Default strategy when you insert duplicate it crashes the app.</li>
<li><code>REPLACE</code>: Keep the old data and ignore the new data.</li>
<li><code>ABORT</code>: Aborts the transaction entirely so none of the entries will be updated.</li>
<li><code>IGNORE</code>: Skip the entry and proceed to next</li>
</ul>
<p>You can set your preference as per your requirement, but if you want the behavior of <code>REPLACE</code> hold on there is another annotation that we will learn about soon that is a better option.</p>
<p>⚠️ Beware: inserting duplicate entries will crash the app if you don&rsquo;t provide a conflict strategy.</p>
<h3 id="update---modify-existing-data">@Update - Modify Existing Data</h3>
<p>Used for updating all columns of an existing row where the primary key matches, If the table does not contain a matching primary key, the update fails silently (no error or no exception) but the unmatched entries remain unchanged (they are not inserted or modified).</p>
<p>Like the <code>@insert</code> you can <code>@update</code> single or multiple entries, a sample code will look something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#f92672">..</span>.
</span></span><span style="display:flex;"><span> <span style="color:#75715e">//Updating single item
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">@Update</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">updateProduct</span>(product: Product)   
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span> <span style="color:#75715e">//Updating multiple items
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">@Update</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">updateProducts</span>(products: List&lt;Product&gt;)     
</span></span><span style="display:flex;"><span><span style="color:#f92672">..</span>.
</span></span></code></pre></div><p>The update overrides the provided value to matched entries so if you accidentally provide a null value, that is exactly what the matched row&rsquo;s column will be, for example, if you want to mark the product as sold-out you will think you have to send only a product object with <code>id</code> and the <code>sold</code> value but if you do that all other values like name, date, and other values will be set as null so ensure you provide all values.</p>
<p>⚠️ You are overriding all values, so ensure you provide all values in each entry. <br>
⚠️ Unmatched rows will not be inserted, no errors or exceptions will be thrown</p>
<h3 id="upsert---insert-or-update-in-one-call">@Upsert - Insert or Update in One Call</h3>
<p><code>@Upsert</code> is a combination of <code>@Insert</code> and <code>@Update</code>, if the entry matches the <strong>primary-key</strong> it updates them, otherwise it inserts them smoothly, you could use <code>@Insert</code> with <code>REPLACE</code> if your table has no primary key otherwise this is better.</p>
<p>This is an example of how the ``@Upsert` will be used in a dao</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// upsert single item
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">@Upsert</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">upsertProduct</span>(product: Product)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// upsert bulk Item
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">@Upsert</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">upsertProducts</span>(products: List&lt;Product&gt;)
</span></span></code></pre></div><p>One thing to note is that the conflict is handled only for the primary key, if your table has other columns that are set as unique the upsert may still fail if that rule is ever broken due to new data.</p>
<p>⚠️ Requires the primary key  <br>
⚠️ If your table has unique constraints on other columns, @Upsert may still fail.</p>
<h3 id="delete---remove-entries-from-the-database">@Delete - Remove Entries from the Database</h3>
<p>Enables you to delete single and multiple entities blissfully, if the row does not exist it fails silently which means that the entries that exist will be deleted and the other will not since it does not exist, here is how typical delete functions will look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// delete single item
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">@Delete</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">deleteProduct</span>(product: Product)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// delete bulk Item
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">@Delete</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">deleteProducts</span>(products: List&lt;Product&gt;)
</span></span></code></pre></div><p>I don&rsquo;t like to pass the entire entity for deletions so I prefer to use <code>@Query</code> for it so I can just pass the primary keys for deletion, but for simplicity, you can use the <code>@Delete</code> as well.</p>
<h3 id="query---the-most-powerful-annotation">@Query - The Most Powerful Annotation</h3>
<p>The <code>@Query</code> Swiss knife allows you to write custom SQL queries to interact with your database. It provides more flexibility than other annotations (<code>@Insert</code>, <code>@Update</code>, <code>@Delete</code>) and is essential for performing simple/complex operations depending on our use cases.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#f92672">..</span>.
</span></span><span style="display:flex;"><span><span style="color:#75715e">// bulk get
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">@Query</span>(<span style="color:#e6db74">&#34;SELECT * FROM products&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">getAllProducts</span>(): List&lt;Product&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// single get 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">@Query</span>(<span style="color:#e6db74">&#34;SELECT * FROM products WHERE id = :productId&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">getProductById</span>(productId: Long): Product?
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// search Query
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">@Query</span>(<span style="color:#e6db74">&#34;SELECT * FROM products WHERE name LIKE &#39;%&#39; || :searchQuery || &#39;%&#39;&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">searchProducts</span>(searchQuery: String): List&lt;Product&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// deleting item with ID
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">@Query</span>(<span style="color:#e6db74">&#34;DELETE FROM products WHERE id = :productId&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">deleteProductById</span>(productId: Long)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// delete multiple items
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">@Query</span>(<span style="color:#e6db74">&#34;DELETE FROM products WHERE id IN (:productIds)&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">deleteProductById</span>(productIds: List&lt;Long&gt;)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// deleting all entries (truncates)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">@Query</span>(<span style="color:#e6db74">&#34;DELETE FROM products&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">deleteAllProducts</span>()
</span></span><span style="display:flex;"><span><span style="color:#f92672">..</span>.
</span></span></code></pre></div><p>Swiss Knife it is, you can do all kinds of operations with it, its gives us more flexibility but as we know &ldquo;With Great Power comes to Great Responsibility&rdquo;. Although <code>@Query</code> is powerful, it can fail in various ways due to syntax errors, missing data, type mismatches, database constraints, etc.. so use it cautiously and expect exceptions and app crashes.</p>
<h3 id="rawquery---fully-dynamic-sql-execution">@RawQuery - Fully Dynamic SQL Execution</h3>
<p>The <code>@RawQuery</code> annotation in Room allows you to write and execute fully dynamic SQL queries, will will explore this later in this series it breaks the simplicity boundary because there is no compile-time safety on this.</p>
<h2 id="-conclusion">🚀 Conclusion</h2>
<p>Data Access Objects <strong>(DAOs) are the backbone of efficient database operations in Room</strong>. They provide a clean, structured way to interact with your database while leveraging compile-time safety and reducing boilerplate SQL code. With annotations like @Insert, @Update, @Delete, @Upsert, and @Query, DAOs make it easier to perform CRUD operations while maintaining performance and maintainability.</p>
<p>By using DAOs, you ensure that your Offline-First App has a seamless data layer, enabling a smooth and efficient user experience.</p>
<p><strong>Next, we will discuss about @RawQuery and its pro&rsquo;s and cons&hellip;</strong></p>
<h2 id="final-thoughts"><strong>Final Thoughts</strong></h2>
<p>This is my journey in <strong>building an offline-first app</strong>. I’d love to hear your feedback, suggestions, or questions!</p>
<p>Feel free to connect with me on:<br>
📩 <strong><a href="mailto:mail@eknath.dev">Email</a></strong><br>
🌍 <strong><a href="https://eknath.dev">Website</a></strong><br>
💫 <strong><a href="https://www.linkedin.com/posts/eganathan_offlinefirstandroid-offlinefirst-android-activity-7294912159627546624-TG77?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAABYcOpgBgvDfy-0uUjfX0HTNqzzLfKZQAQU">LinkedIn-Post for comments and feedbacks</a></strong></p>
<p>🔖 <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-4/">Previous Article in this Series</a>
🚀 <strong>Stay tuned for Part 6!</strong> 🚀</p>
<p>🔖 <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-5/">Next Article in this Series</a></p>
]]></content:encoded></item><item><title>Room Entity and its intricacies (#OF04)</title><link>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-4/</link><pubDate>Sun, 02 Mar 2025 15:39:05 +0530</pubDate><guid>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-4/</guid><description>&lt;p>In our &lt;a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-3/">previous article&lt;/a>, we explored the key components of Room. Now, let’s take a deep dive into Room Entities, their importance, and the various ways to customize them.&lt;/p>
&lt;p>&lt;strong>Entities&lt;/strong> are the foundation of Room—they define how your data is stored in the database. Properly structuring your entity ensures efficient querying, maintainability, and scalability. Let&amp;rsquo;s break it down! 🛠️&lt;/p>
&lt;h2 id="-what-is-an-entity">🏗️ What is an Entity?&lt;/h2>
&lt;p>An &lt;strong>Entity in Room represents a table in the database&lt;/strong>. Each instance of the entity corresponds to a row in the table, Room generates corresponding SQL table schema based on how you create an entity class.&lt;/p></description><content:encoded><![CDATA[<p>In our <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-3/">previous article</a>, we explored the key components of Room. Now, let’s take a deep dive into Room Entities, their importance, and the various ways to customize them.</p>
<p><strong>Entities</strong> are the foundation of Room—they define how your data is stored in the database. Properly structuring your entity ensures efficient querying, maintainability, and scalability. Let&rsquo;s break it down! 🛠️</p>
<h2 id="-what-is-an-entity">🏗️ What is an Entity?</h2>
<p>An <strong>Entity in Room represents a table in the database</strong>. Each instance of the entity corresponds to a row in the table, Room generates corresponding SQL table schema based on how you create an entity class.</p>
<p><strong>Defining an Entity:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Entity</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">data</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">LocalHabitTracker</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@PrimaryKey</span>(autoGenerate = <span style="color:#66d9ef">true</span>) <span style="color:#66d9ef">val</span> id: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> associatedHabitId: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> positionX: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> positionY: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> note: String
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p><strong>Breaking It Down:</strong></p>
<ul>
<li><code>@Entity</code> tells Room that this class is a database table.</li>
<li><code>@PrimaryKey</code> is used to uniquely identify each row.</li>
<li><code>autoGenerate = true</code> ensures Room generates unique IDs automatically.</li>
<li><code>@Ignore</code> → Excludes a field from being stored in the database.</li>
</ul>
<p>Before we procced further, Lets check out the sql query generated by the room</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> LocalHabitTracker (
</span></span><span style="display:flex;"><span>    id INTEGER <span style="color:#66d9ef">PRIMARY</span> <span style="color:#66d9ef">KEY</span> AUTOINCREMENT,
</span></span><span style="display:flex;"><span>    associatedHabitId INTEGER <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>    positionX INTEGER <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>    positionY INTEGER <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>    note TEXT <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p>Since its a simple entity it might look workable but think of adding indexes, relations and other complicated relations it can quickly get complicated and prone errors. That being said this is just to show you how room is doing most of the heavy lifting for you, lets move on to customizing our entities.</p>
<hr>
<h2 id="-customizing-entities">🔄 Customizing Entities</h2>
<p>Room provides several ways to customize entities to fit your data structure requirements. Let&rsquo;s explore these!</p>
<h3 id="1-custom-table-and-column-names">1️⃣ Custom Table and Column Names</h3>
<p>By default, Room uses the <strong>class name as the table name</strong> and <strong>variable names as column names</strong>. You can override this using annotations:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Entity</span>(tableName = <span style="color:#e6db74">&#34;habit_tracker&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">data</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">LocalHabitTracker</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@PrimaryKey</span>(autoGenerate = <span style="color:#66d9ef">true</span>) <span style="color:#66d9ef">val</span> id: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@ColumnInfo</span>(name = <span style="color:#e6db74">&#34;habit_id&#34;</span>) <span style="color:#66d9ef">val</span> associatedHabitId: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@ColumnInfo</span>(name = <span style="color:#e6db74">&#34;pos_x&#34;</span>) <span style="color:#66d9ef">val</span> positionX: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@ColumnInfo</span>(name = <span style="color:#e6db74">&#34;pos_y&#34;</span>) <span style="color:#66d9ef">val</span> positionY: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> note: String
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><ul>
<li>tableName changes the SQL table name while keeping the DataClass name as per your requiremnt.</li>
<li>@ColumnInfo(name = &ldquo;custom_name&rdquo;) changes the column name in the table.</li>
</ul>
<p>⚠️ <strong>These customizations are for the DataBase Tables which means when writing the query you must use the customized name for the query to work properly.</strong></p>
<p>This is extremely useful when you want to Name your tables and columns differently, in kotlin we use camelcase but on sql its common to use underscore to split worlds like <strong>habit_tracker</strong> instead of <strong>LocalHabitTracker</strong> it simplifies the query and expedites debugging process.</p>
<p>Using custom name for table and column is optional so if you are comfortable with the existing name you can use just that, next up lets see how can we ensure the entities can be indexed faster.</p>
<h3 id="2-indexing-for-faster-queries">2️⃣ Indexing for Faster Queries</h3>
<p>Indexes speed up query performance, especially for large datasets. Use @Index for frequently queried columns, for example here the <code>associated_habit_id</code> will be used most frequently by me in different queries so i am adding <code>indices</code> property on the <code>@Table</code> annotation, here is an example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Entity</span>(
</span></span><span style="display:flex;"><span>    tableName = <span style="color:#e6db74">&#34;habit_tracker&#34;</span>,
</span></span><span style="display:flex;"><span>    indices = [Index(<span style="color:#66d9ef">value</span> = [<span style="color:#e6db74">&#34;associated_habit_id&#34;</span>])]
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">data</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">LocalHabitTracker</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@PrimaryKey</span>(autoGenerate = <span style="color:#66d9ef">true</span>) <span style="color:#66d9ef">val</span> id: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@ColumnInfo</span>(name = <span style="color:#e6db74">&#34;associated_habit_id&#34;</span>)<span style="color:#66d9ef">val</span> associatedHabitId: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> positionX: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> positionY: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> note: String
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><ul>
<li>Indexing <code>habit_id</code> speeds up lookup queries on this column.</li>
</ul>
<h4 id="when-should-you-use-an-index">When Should You Use an Index?</h4>
<p><strong>✅ Use indexing if:</strong></p>
<ul>
<li>The column is used frequently in WHERE, JOIN, or ORDER BY.</li>
<li>The column is a foreign key linking to another table.</li>
</ul>
<p><strong>🚫 Avoid indexing if:</strong></p>
<ul>
<li>The table is small (indexing overhead isn’t worth it).</li>
<li>The column has many unique values (like id, which is already indexed as PRIMARY KEY).</li>
</ul>
<h4 id="what-actually-happens">What actually happens?</h4>
<p>When you add an index to a column in Room, Room <strong>creates a separate data structure called a B-tree index</strong>. This makes queries that search for specific values in the indexed column much faster.</p>
<p>If you want to know more about <code>B-tree Index</code>: check this out <a href="https://en.wikipedia.org/wiki/B-tree">B-Tree Index</a></p>
<p>Now we have learned about indexing lets checkout how to ensure the columns stays unique</p>
<h3 id="3-unique-constraints">3️⃣ Unique Constraints</h3>
<p>Preventing duplicate values on an indexed column is sometimes necessary and we can achieve that by setting the property <code>unique = true</code> if the Index class, lets checkout an example to see how to apply this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Entity</span>(
</span></span><span style="display:flex;"><span>    tableName = <span style="color:#e6db74">&#34;habit_tracker&#34;</span>,
</span></span><span style="display:flex;"><span>    indices = [
</span></span><span style="display:flex;"><span>        Index(<span style="color:#66d9ef">value</span> = [<span style="color:#e6db74">&#34;entry_data&#34;</span>], unique = <span style="color:#66d9ef">true</span>),
</span></span><span style="display:flex;"><span>        Index(<span style="color:#66d9ef">value</span> = [<span style="color:#e6db74">&#34;associated_habit_id&#34;</span>])
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">data</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">LocalHabitTracker</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@PrimaryKey</span>(autoGenerate = <span style="color:#66d9ef">true</span>) <span style="color:#66d9ef">val</span> id: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@ColumnInfo</span>(name = <span style="color:#e6db74">&#34;associated_habit_id&#34;</span>)<span style="color:#66d9ef">val</span> associatedHabitId: Long,  
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> positionX: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> positionY: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> note: String,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@ColumnInfo</span>(name = <span style="color:#e6db74">&#34;entry_data&#34;</span>)<span style="color:#66d9ef">val</span> entryDate: String
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><ul>
<li>Ensures <code>habit_id</code> remains unique across all rows.</li>
</ul>
<p>The <code>unique = true</code> ensures there is no duplicate value in that particular indexed row, this can be extremely helpful in some cases in our case we are building a habit tracking app where users log their habits daily. We want to ensure that a user cannot log the same habit more than once per day so this property help us to achieve that blissfully.</p>
<p>Lets move on to Keys and Relationships,i meant between the Entities 😜</p>
<h3 id="4-foreign-keys-for-relationships-foreignkey">4️⃣ Foreign Keys for Relationships (@ForeignKey)</h3>
<p>Define relationships between tables using <code>@ForeignKey</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Entity</span>(
</span></span><span style="display:flex;"><span>    tableName = <span style="color:#e6db74">&#34;habit_tracker&#34;</span>,
</span></span><span style="display:flex;"><span>    foreignKeys = [
</span></span><span style="display:flex;"><span>        ForeignKey(
</span></span><span style="display:flex;"><span>            entity = Habit<span style="color:#f92672">::</span><span style="color:#66d9ef">class</span>,
</span></span><span style="display:flex;"><span>            parentColumns = [<span style="color:#e6db74">&#34;id&#34;</span>],
</span></span><span style="display:flex;"><span>            childColumns = [<span style="color:#e6db74">&#34;habit_id&#34;</span>],
</span></span><span style="display:flex;"><span>            onDelete = <span style="color:#a6e22e">ForeignKey</span>.CASCADE
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">data</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">LocalHabitTracker</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@PrimaryKey</span>(autoGenerate = <span style="color:#66d9ef">true</span>) <span style="color:#66d9ef">val</span> id: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> habit_id: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> positionX: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> positionY: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> note: String
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>🔷 habit_id references id from the Habit table.
🔷 onDelete = CASCADE ensures that deleting a Habit deletes all related LocalHabitTracker records.</p>
<p>The <code>@ForeignKey</code> is a primary key of an associated entity in the context of current entity the associated entity is referred as parent entity, using <code>@ForeignKey</code> help us to create relations and trigger changes depending on the event triggered, most commonly used trigger event is <code>onDelete</code> the other one is <code>onUpdate</code> the actions supported are as follows:</p>
<h4 id="supported-actions"><strong>Supported Actions:</strong></h4>
<table>
  <thead>
      <tr>
          <th>Action</th>
          <th>Behavior on Delete/Update</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CASCADE</td>
          <td>Deletes/updates child rows automatically when the parent row is deleted/updated.</td>
      </tr>
      <tr>
          <td>SET NULL</td>
          <td>Sets the foreign key column in child rows to NULL when the parent row is deleted/updated.</td>
      </tr>
      <tr>
          <td>SET DEFAULT</td>
          <td>Sets the foreign key column in child rows to its default value when the parent row is deleted/updated.</td>
      </tr>
      <tr>
          <td>RESTRICT</td>
          <td>Prevents deletion or update of the parent row if child rows exist (throws an error).</td>
      </tr>
      <tr>
          <td>NO ACTION</td>
          <td>Similar to RESTRICT, but the check happens after the statement executes.</td>
      </tr>
  </tbody>
</table>
<p>This table should have given you a clear picture of what each action&rsquo;s behavior and next lets checkout how to select them and when to use them effectively:</p>
<h4 id="when-to-use-each-action"><strong>When to Use Each Action</strong></h4>
<table>
  <thead>
      <tr>
          <th>Action</th>
          <th>When to Use?</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CASCADE</td>
          <td>When child records must be removed/updated along with the parent.</td>
      </tr>
      <tr>
          <td>SET NULL</td>
          <td>When child records should remain but lose their reference to the parent.</td>
      </tr>
      <tr>
          <td>SET DEFAULT</td>
          <td>When child records should be assigned a default foreign key value (useful for fallback behaviors).</td>
      </tr>
      <tr>
          <td>RESTRICT</td>
          <td>When you want to prevent deletion or modification of a parent that still has child records.</td>
      </tr>
      <tr>
          <td>NO ACTION</td>
          <td>Similar to RESTRICT, but checks only after execution (rarely used).</td>
      </tr>
  </tbody>
</table>
<p>You can add any number of foreign keys so make use of it for simplifying and automate your preferred action when the parent entity is modified.</p>
<h3 id="5-embedded-objects-embedded">5️⃣ Embedded Objects (@Embedded)</h3>
<p>Instead of creating separate tables, you can embed objects inside an entity, this does not mean that the table will hold your embedded object as is, it basically flattens the properties into the Entity, first lets see how to use <code>@Embedded</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#66d9ef">data</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Position</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> x: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> y: Int
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@Entity</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">data</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">LocalHabitTracker</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@PrimaryKey</span>(autoGenerate = <span style="color:#66d9ef">true</span>) <span style="color:#66d9ef">val</span> id: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> associatedHabitId: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@Embedded</span> <span style="color:#66d9ef">val</span> position: Position,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> note: String
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><ul>
<li>The Position object is embedded as separate columns (x, y) in LocalHabitTracker,</li>
</ul>
<p>This helps with <strong>structuring data efficiently without creating a separate table for nested objects</strong>, It is primarily used to flatten objects into separate columns within the same table this helps us to avoid using <code>TypeConverters</code>, extra Table, faster queries as we don&rsquo;t have to deal with JOINS and other complications.</p>
<p><strong>When to Use @Embedded:</strong></p>
<ol>
<li>You have small nested objects (e.g., Position, Address, Metadata).</li>
<li>You don’t need a separate table for the object.</li>
<li>You want to avoid TypeConverters for simple objects.</li>
<li>The embedded object is always used with the parent (it has no independent existence).</li>
</ol>
<p><strong>When to Avoid it:</strong></p>
<ol>
<li>The embedded object is large or frequently updated → Use a separate table.</li>
<li>The object has relationships with other entities → Use @Relation instead.</li>
<li>The object needs to be referenced from multiple entities → Use Foreign Keys.</li>
</ol>
<p>I hope this made sense, simply put the <code>@Embedded</code> is only a output structure, while creating the SQL query room will flatten the properties into the entity table, Now if you want to store complex objects like lets say List<String> event that is possible, lets see how to achieve it.</p>
<hr>
<h3 id="6-storing-objects-as-is-typeconverters">6️⃣ Storing Objects as is (TypeConverters)</h3>
<p>Room only supports primitive data types (Int, Long, String, Boolean, etc.) by default. If you want to store complex data types (e.g., List, Date, Enum), you need Type Converters to convert them into a format Room understands ie String, lets look at an example of @TypeConverter:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> androidx.room.TypeConverter
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> com.google.gson.Gson
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> com.google.gson.reflect.TypeToken
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> java.util.Date
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Converters</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Convert Date -&gt; Long (for storage)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@TypeConverter</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">fromDate</span>(date: Date?): Long? {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> date<span style="color:#f92672">?.</span>time
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Convert Long -&gt; Date (for retrieval)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@TypeConverter</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">toDate</span>(timestamp: Long?): Date? {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> timestamp<span style="color:#f92672">?.</span>let { Date(<span style="color:#66d9ef">it</span>) }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Convert List&lt;String&gt; -&gt; JSON String (for storage)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@TypeConverter</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">fromStringList</span>(list: List&lt;String&gt;?): String {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> Gson().toJson(list)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Convert JSON String -&gt; List&lt;String&gt; (for retrieval)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@TypeConverter</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">toStringList</span>(json: String?): List&lt;String&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> Gson().fromJson(json, <span style="color:#66d9ef">object</span> <span style="color:#960050;background-color:#1e0010">: </span><span style="color:#a6e22e">TypeToken</span>&lt;List&lt;String&gt;&gt;() {}.type)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Once a converter is defined it must be added to the DataBase class,here is an example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Database</span>(entities = [User<span style="color:#f92672">::</span><span style="color:#66d9ef">class</span>], version = <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@TypeConverters</span>(Converters<span style="color:#f92672">::</span><span style="color:#66d9ef">class</span>)  <span style="color:#75715e">// Register converters
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">abstract</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">AppDatabase</span> : RoomDatabase() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">abstract</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">userDao</span>(): UserDao
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>now you can add it to the entity by annotating it with the the <code>@TypeConverter</code>, here is an example for it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Entity</span>(tableName = <span style="color:#e6db74">&#34;users&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">data</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">User</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@PrimaryKey</span>(autoGenerate = <span style="color:#66d9ef">true</span>) <span style="color:#66d9ef">val</span> id: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> name: String,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@TypeConverters</span>(Converters<span style="color:#f92672">::</span><span style="color:#66d9ef">class</span>) <span style="color:#75715e">// Apply TypeConverter here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">val</span> hobbies: List&lt;String&gt;
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>The best use-case for this is when you want to store Enums, Dates and List of items, do note the values in the object will be stored but cannot be queried which means you can&rsquo;t search, sort or filter the values so ensure you use them only when absolutely need to like for Storing simple objects (e.g., <code>Address</code>, <code>Date</code>, <code>Enum</code>)</p>
<p>These cover the most important aspects of the <code>@Entity</code> and its properties and how to use them effectively, if you think i have missed any please feel free to add comment and ill definitely add them here so it can be helpful for me and others, before you go lets check the best practices.</p>
<hr>
<h2 id="-best-practices">🔍 Best Practices</h2>
<p>✅ Use <code>autoGenerate = true</code> for <code>@PrimaryKey</code> to avoid conflicts if you don&rsquo;t have a server that provides it.<br>
✅ Optimize query performance with <code>@Index</code>.<br>
✅ Define <code>@ForeignKey</code> relationships to maintain integrity but optional for complex cases.<br>
✅ Use <code>@Ignore</code> for transient fields that shouldn&rsquo;t be stored in the database.<br>
✅ Keep entity classes small and focused—avoid unnecessary logic inside them.<br>
✅ Ensure Proper Indexing: Index frequently queried columns to improve performance. <br>
✅ Use Primitive Types: Room doesn’t support custom objects directly. Use <code>@TypeConverter</code> if needed.</p>
<hr>
<h2 id="-conclusion">🚀 Conclusion</h2>
<p>Room Entities form the foundation of Android&rsquo;s database layer, allowing structured and efficient data management. By following best practices, you ensure scalable and maintainable database architectures.</p>
<p><strong>Next we will explore Data Access Objects</strong>, Stay tuned for the next article in this series! 🚀</p>
<h2 id="final-thoughts"><strong>Final Thoughts</strong></h2>
<p>This is my journey in <strong>building an offline-first app</strong>. I’d love to hear your feedback, suggestions, or questions!</p>
<p>Feel free to connect with me on:<br>
📩 <strong><a href="mailto:mail@eknath.dev">Email</a></strong><br>
🌍 <strong><a href="https://eknath.dev">Website</a></strong><br>
💫 <strong><a href="https://www.linkedin.com/posts/eganathan_offlinefirstandroid-offlinefirst-android-activity-7294912159627546624-TG77?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAABYcOpgBgvDfy-0uUjfX0HTNqzzLfKZQAQU">LinkedIn-Post for comments and feedbacks</a></strong></p>
<p>🔖 <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-3/">Previous Article in this Series</a>
🚀 <strong>Stay tuned for Part 5!</strong> 🚀</p>
<p>🔖 <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-5/">Next Article in this Series</a></p>
]]></content:encoded></item><item><title>Key Components of Room &amp; Manually Creating A Database Instance(#OF03)</title><link>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-3/</link><pubDate>Sat, 22 Feb 2025 11:23:17 +0530</pubDate><guid>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-3/</guid><description>&lt;h2 id="-intro">👋 Intro&lt;/h2>
&lt;p>On the &lt;a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-2/">previous article&lt;/a> we have set-up the Room dependencies and plugins, Now lets get into the primary key components of a room library.&lt;/p>
&lt;p>There are three important components: &lt;strong>Entity&lt;/strong>, &lt;strong>DAO&lt;/strong>&amp;rsquo;s, and &lt;strong>Database&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Entity&lt;/strong> represents a single row of a table. (Table structure)&lt;/li>
&lt;li>&lt;strong>DAO&lt;/strong>&amp;rsquo;s (Data Access Objects) are interfaces to write queries and define operations.&lt;/li>
&lt;li>&lt;strong>Database&lt;/strong> is where you associate entities and include the DAO&amp;rsquo;s you&amp;rsquo;d like to access from outside.&lt;/li>
&lt;/ul>
&lt;p>Let&amp;rsquo;s check it out one by one! 🔍&lt;/p></description><content:encoded><![CDATA[<h2 id="-intro">👋 Intro</h2>
<p>On the <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-2/">previous article</a> we have set-up the Room dependencies and plugins, Now lets get into the primary key components of a room library.</p>
<p>There are three important components: <strong>Entity</strong>, <strong>DAO</strong>&rsquo;s, and <strong>Database</strong>.</p>
<ul>
<li><strong>Entity</strong> represents a single row of a table. (Table structure)</li>
<li><strong>DAO</strong>&rsquo;s (Data Access Objects) are interfaces to write queries and define operations.</li>
<li><strong>Database</strong> is where you associate entities and include the DAO&rsquo;s you&rsquo;d like to access from outside.</li>
</ul>
<p>Let&rsquo;s check it out one by one! 🔍</p>
<hr>
<h2 id="-entities-defining-your-data-structure">🏗️ Entities: Defining Your Data Structure</h2>
<p>Any class annotated with <code>@Entity</code> is called a entity, it represents a table in a database, while designing the entity ensure you follow <a href="https://www.geeksforgeeks.org/normal-forms-in-dbms/">Normalizations Rules</a> to keep it simple and future proof. Let&rsquo;s look checkout a sample entity in a Habit Tracker app.</p>
<p><strong>Example:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Entity</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">data</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">LocalHabitTracker</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@PrimaryKey</span>(autoGenerate = <span style="color:#66d9ef">true</span>) <span style="color:#66d9ef">val</span> id: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> associatedHabitId: Long,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> positionX: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> positionY: Int,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> note: String
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p><strong>Key Points:</strong></p>
<p>✅ An entity <strong>must have</strong> at least one parameter annotated with @PrimaryKey. <br>
✅ The <strong>primary key</strong> must be unique the <code>primaryKey</code> column can&rsquo;t have a duplicate key. <br>
✅ If you want Room to <strong>auto-generate</strong> the primary key, set autoGenerate = true otherwise to false. <br>
✅ Stick to <strong>primitive types</strong> like Long or Int as PrimaryKey.</p>
<p>The entity can be further customized with <strong>table names, indexing, custom column names, keys and more</strong>, which we’ll cover in next article. 🎯</p>
<h2 id="-daos-your-data-gateway">🔗 DAOs: Your Data Gateway</h2>
<p>DAOs or Data Access Objects serve as the interface between your app and the database, providing an abstraction layer. This is a bridge between you the developer and the Database all your interactions you intend to have table must be defined here later at compile time the compiler will generate the concrete classes for these at compile time, lets see an example how to define these interactions.</p>
<p><strong>✍️ Example:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Dao</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">HabitTrackerDao</span> {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Query to get all habit trackers
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@Query</span>(<span style="color:#e6db74">&#34;SELECT * FROM LocalHabitTracker&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">getAll</span>(): List&lt;LocalHabitTracker&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Query to get a habit tracker by ID
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@Query</span>(<span style="color:#e6db74">&#34;SELECT * FROM LocalHabitTracker WHERE id = :id&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">getById</span>(id: Long): LocalHabitTracker
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Insert a new row into the table (fails if duplicate)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@Insert</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">insert</span>(habitTracker: LocalHabitTracker)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Update an existing row
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@Update</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">update</span>(habitTracker: LocalHabitTracker)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Upsert: If exists, update; otherwise, insert
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@Upsert</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">upsert</span>(habitTracker: LocalHabitTracker)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Delete a row from the table
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">@Delete</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">suspend</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">delete</span>(habitTracker: LocalHabitTracker)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Best Practices:</strong></p>
<p>✅ <strong>One entity per DAO:</strong> It’s best to separate interaction with each table into its own DAO for maintainability.  <br>
✅ <strong>Keep queries optimized</strong> to avoid performance issues as your app scales.  <br>
✅ <strong>Inheritance</strong> if some of the interactions are common to other Dao&rsquo;s create a new Dao with <code>Common</code> as prefix for example<code>CommonHabitTrackerDao</code>.</p>
<p>We’ll cover complex cases like @RawQuery, Junctions, and TableViews in later articles as to not over-complicate this! 🔮</p>
<h2 id="-database-the-central-hub">🏛️ Database: The Central Hub</h2>
<p>Now that we have defined the entities and DAOs, we need to create a database to integrate thee entity and Dao&rsquo;s into its domain. Now the room can effectively generate its contents when instantiated along with this the database also manages the integrity validation, migrations and works as a central hub of control for our interactions with this database and its entities.</p>
<p>Here is an <strong>example</strong> of a database class:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Database</span>(
</span></span><span style="display:flex;"><span>    entities = [LocalHabitTracker<span style="color:#f92672">::</span><span style="color:#66d9ef">class</span>], <span style="color:#75715e">// Add all your entities here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    version = <span style="color:#ae81ff">1</span> <span style="color:#75715e">// Update this when modifying the schema
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">abstract</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">HabitTrackerDataBase</span> : RoomDatabase() {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Room will auto-implement this function and return the DAO implementation
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">abstract</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">habitTrackerDao</span>(): HabitTrackerDao
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Important Notes:</strong></p>
<p>✅ Always increase the version when modifying entities, it enables room to validate the integrity of tables and run migrations effectively. <br>
✅ Room doesn’t know how to handle schema changes unless you define a migration strategy. <br>
✅ Multiple databases can exist in an app, so this class serves as an entry point of the particular database.</p>
<p>Migrations and schema updates are crucial for real-world apps to prevent data loss. We’ll cover on later articles. 🔄</p>
<h2 id="-manually-creating-and-using-the-database">🎛️ Manually Creating and Using the Database</h2>
<p>Now, let’s see how to manually create and interact with the database in our app.</p>
<p><strong>✍️ Example:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">MainActivity</span> : AppCompatActivity() {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">override</span> <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">onCreate</span>(savedInstanceState: Bundle?) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">super</span>.onCreate(savedInstanceState)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Creating the database manually
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">val</span> habitTrackerDB = <span style="color:#a6e22e">Room</span>.databaseBuilder(
</span></span><span style="display:flex;"><span>            context = <span style="color:#66d9ef">this</span>, <span style="color:#75715e">// ApplicationContext
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            klass = HabitTrackerDataBase<span style="color:#f92672">::</span><span style="color:#66d9ef">class</span>.java, <span style="color:#75715e">// Database abstract class
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            name = <span style="color:#e6db74">&#34;habbit_tracker&#34;</span> <span style="color:#75715e">// Custom database name
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        ).build() 
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Get the DAO implementation
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">val</span> habitTrackerDao = habitTrackerDB.habitTrackerDao()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        setContent {
</span></span><span style="display:flex;"><span>            AppTheme {
</span></span><span style="display:flex;"><span>                AppNav(activity = <span style="color:#66d9ef">this</span>, habitLocalService = habitTrackerDao)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>⚡ Pro Tips:</strong></p>
<p>✅ Don’t use the UI thread for database operations; always use coroutines or background threads. <br>
✅ In real-world apps, use Dependency Injection (DI) for managing database instances efficiently.  <br>
✅ Avoid creating multiple database instances, as it can lead to memory leaks and performance issues.</p>
<p>Mostly this is enough for creating simple CRUD apps, though it looks simple the entities will be converted into a query to create tables, A concrete classes will be created for each Dao&rsquo;s and other essential tasks will be carried by room it self easing our development and debug process.</p>
<h2 id="-whats-next">🚀 What’s Next?</h2>
<p>This was a quick run through of some of the key components of room, on the next article we will dive a little deeper into <strong>@Entity</strong> its keys and customizations,as we have a larger purpose to explore them each in detail.</p>
<h2 id="final-thoughts"><strong>Final Thoughts</strong></h2>
<p>This is my journey in <strong>building an offline-first app</strong>. I’d love to hear your feedback, suggestions, or questions!</p>
<p>Feel free to connect with me on:<br>
📩 <strong><a href="mailto:mail@eknath.dev">Email</a></strong><br>
🌍 <strong><a href="https://eknath.dev">Website</a></strong>
💫 <strong><a href="https://www.linkedin.com/posts/eganathan_offlinefirstandroid-offlinefirst-android-activity-7294912159627546624-TG77?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAABYcOpgBgvDfy-0uUjfX0HTNqzzLfKZQAQU">LinkedIn-Post for comments and feedbacks</a></strong></p>
<p>🔖 <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-2/">Previous Article in this Series</a></p>
<p>🔖 <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-4/">Next Article in this Series</a></p>
]]></content:encoded></item><item><title>Setting-up Room Library and its dependencies(#OF02)</title><link>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-2/</link><pubDate>Mon, 10 Feb 2025 10:02:29 +0530</pubDate><guid>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-2/</guid><description>&lt;p>Before we proceed with the setup, let’s quickly recap &lt;strong>why&lt;/strong> we chose Room:&lt;/p>
&lt;p>✅ &lt;strong>Compile-time SQL Validation&lt;/strong> – Catches errors early by verifying SQL queries at compile time.&lt;br>
✅ &lt;strong>Kotlin-first Approach&lt;/strong> – Supports coroutines, Flow, and LiveData natively.&lt;br>
✅ &lt;strong>Less Boilerplate Code&lt;/strong> – Simplifies database interactions while maintaining SQLite’s power.&lt;br>
✅ &lt;strong>Seamless Migration Handling&lt;/strong> – Built-in support for database migrations.&lt;/p>
&lt;p>For a more detailed explanation, refer to the &lt;a href="https://developer.android.com/training/data-storage/room">official Android documentation&lt;/a>.&lt;/p></description><content:encoded><![CDATA[<p>Before we proceed with the setup, let’s quickly recap <strong>why</strong> we chose Room:</p>
<p>✅ <strong>Compile-time SQL Validation</strong> – Catches errors early by verifying SQL queries at compile time.<br>
✅ <strong>Kotlin-first Approach</strong> – Supports coroutines, Flow, and LiveData natively.<br>
✅ <strong>Less Boilerplate Code</strong> – Simplifies database interactions while maintaining SQLite’s power.<br>
✅ <strong>Seamless Migration Handling</strong> – Built-in support for database migrations.</p>
<p>For a more detailed explanation, refer to the <a href="https://developer.android.com/training/data-storage/room">official Android documentation</a>.</p>
<hr>
<h2 id="official-documentation">Official Documentation</h2>
<p>Google keeps its documentation up to date with a simple setup guide. However, you might wonder why we need this article if the documentation is available.</p>
<p>Google’s documentation caters to <strong>both Java and Kotlin</strong> developers, but since most of us have migrated to <strong>Kotlin</strong>, we don’t need Java-specific instructions. This article focuses on a <strong>Kotlin-first approach</strong> to setting up Room.</p>
<p>📖 <a href="https://developer.android.com/jetpack/androidx/releases/room">Official Guide on Setting up Room Dependencies and Plugins</a>.</p>
<hr>
<h2 id="setting-up-dependencies">Setting up Dependencies</h2>
<h3 id="1-configure-project-level-buildgradlekts">1️⃣ Configure Project-Level <code>build.gradle.kts</code></h3>
<p>Ensure your project-level <code>build.gradle.kts</code> file includes <strong>Google’s Maven repository</strong> and <strong>Maven Central</strong>, as these are where Room dependencies are hosted.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span>buildscript {
</span></span><span style="display:flex;"><span>    repositories {
</span></span><span style="display:flex;"><span>        google()
</span></span><span style="display:flex;"><span>        mavenCentral()
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="2-add-room-dependencies">2️⃣ Add Room Dependencies</h3>
<p>Head to your module-level build.gradle.kts file and add only the required Room dependencies inside the dependencies block:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span>dependencies {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Other dependencies 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Room Dependencies
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">val</span> room_version = <span style="color:#e6db74">&#34;2.6.1&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Room Database Core
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    implementation(<span style="color:#e6db74">&#34;androidx.room:room-runtime:</span><span style="color:#e6db74">$room</span><span style="color:#e6db74">_version&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Annotation Processor (KSP)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    ksp(<span style="color:#e6db74">&#34;androidx.room:room-compiler:</span><span style="color:#e6db74">$room</span><span style="color:#e6db74">_version&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Kotlin Extensions and Coroutines support for Room (Optional)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    implementation(<span style="color:#e6db74">&#34;androidx.room:room-ktx:</span><span style="color:#e6db74">$room</span><span style="color:#e6db74">_version&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>🔎 Check for Latest Versions
Always use the latest stable version of Room and KSP:</p>
<p>📌 <a href="https://mvnrepository.com/artifact/androidx.room/room-runtime">Maven Central - Room Runtime</a>
📌 <a href="https://github.com/google/ksp/releases">kotlin-ksp-releases</a></p>
<h3 id="3-add-the-room-plugin">3️⃣ Add the Room Plugin</h3>
<p><strong>Project-Level build.gradle.kts</strong>
Add the Room plugin reference to the project-level build.gradle.kts file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span>plugins {
</span></span><span style="display:flex;"><span>    id(<span style="color:#e6db74">&#34;androidx.room&#34;</span>) version <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">$room</span><span style="color:#e6db74">_version&#34;</span> apply <span style="color:#66d9ef">false</span> 
</span></span><span style="display:flex;"><span>    id(<span style="color:#e6db74">&#34;com.google.devtools.ksp&#34;</span>) version <span style="color:#e6db74">&#34;2.0.21-1.0.27&#34;</span> apply <span style="color:#66d9ef">false</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Other plugins...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p><strong>Module-Level build.gradle.kts</strong>
Add this to the module-level <code>build.gradle</code> file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span>plugins {
</span></span><span style="display:flex;"><span>    id(<span style="color:#e6db74">&#34;androidx.room&#34;</span>)
</span></span><span style="display:flex;"><span>    id(<span style="color:#e6db74">&#34;com.google.devtools.ksp&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="4-configure-schema-directory">4️⃣ Configure Schema Directory</h3>
<p>Room allows schema export for better version control and database migrations. Let’s specify a schema directory inside the android {} block:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span>android {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">..</span>.
</span></span><span style="display:flex;"><span>    room {
</span></span><span style="display:flex;"><span>        schemaDirectory(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">$projectDir</span><span style="color:#e6db74">/schemas&#34;</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="sync--verify-installation">Sync &amp; Verify Installation</h3>
<p>Once you’ve added the dependencies and plugin, sync your Gradle project. If everything is set up correctly, you should have Room ready for use in our project! 🎉</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./gradlew build 
</span></span></code></pre></div><hr>
<h3 id="sample-code-for-a-quick-reference">Sample Code for a quick reference</h3>
<p>I hope you had no issues setting up the dependencies, if you need a sample app to compare the change-set feel free to checkout this <a href="https://github.com/Eganathan/jotters-space-android/commit/92613b0b3f1e08709b94752a5276d6031466e16a">commit</a>.</p>
<hr>
<h2 id="whats-next"><strong>What’s Next?</strong></h2>
<p>This short article we have learned how to set-up the room dependencies to our projects and hopefully this was helpful:
📌 <strong>In the next part of this series, we’ll dive into:</strong></p>
<p>1️⃣ <strong>Key Components of Room</strong></p>
<ul>
<li><strong>@Entity</strong>: Defines how <strong>data is stored</strong> in the table.</li>
<li><strong>@Dao</strong>: Specifies <strong>how to interact</strong> with the table (CRUD operations).</li>
<li><strong>@Database</strong>: Configures the database and <strong>associates Entities and DAOs</strong>.</li>
</ul>
<p>2️⃣ <strong>How to create, access and interact with a simple database</strong>.</p>
<hr>
<h2 id="final-thoughts"><strong>Final Thoughts</strong></h2>
<p>This is my journey in <strong>building an offline-first app</strong>. I’d love to hear your feedback, suggestions, or questions!</p>
<p>Feel free to connect with me on:<br>
📩 <strong><a href="mailto:mail@eknath.dev">Email</a></strong><br>
🌍 <strong><a href="https://eknath.dev">Website</a></strong>    <br>
💫 <strong><a href="https://www.linkedin.com/posts/eganathan_offlinefirstandroid-offlinefirst-android-activity-7294912159627546624-TG77?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAABYcOpgBgvDfy-0uUjfX0HTNqzzLfKZQAQU">LinkedIn-Post for comments and feedbacks</a></strong></p>
<p>🔖 <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-1/">Previous Article in this Series</a></p>
<p>🔖 <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-3/">Next Article in this Series</a></p>
]]></content:encoded></item><item><title>Upgrading Your App to Offline First With Room (#OF01)</title><link>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-1/</link><pubDate>Sun, 09 Feb 2025 09:23:57 +0530</pubDate><guid>https://md.eknath.dev/posts/offline-first-series/upgrading-your-app-to-offline-first-with-room-part-1/</guid><description>&lt;h2 id="why-should-you-care">Why Should You Care?&lt;/h2>
&lt;p>Making your app &lt;strong>offline-first&lt;/strong> is essential if you want to provide the &lt;strong>best user experience&lt;/strong>. It makes your app significantly &lt;strong>faster&lt;/strong> by reducing the number of network calls, which in turn also &lt;strong>reduces server costs&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="what-does-offline-first-mean">What does Offline-First mean?&lt;/h2>
&lt;p>The core idea is to &lt;strong>persist/store remote-fetched data on the device&lt;/strong>. This allows users to access the data &lt;strong>instantly&lt;/strong>, skipping unnecessary network requests—until the data expires or is invalidated.&lt;/p></description><content:encoded><![CDATA[<h2 id="why-should-you-care">Why Should You Care?</h2>
<p>Making your app <strong>offline-first</strong> is essential if you want to provide the <strong>best user experience</strong>. It makes your app significantly <strong>faster</strong> by reducing the number of network calls, which in turn also <strong>reduces server costs</strong>.</p>
<hr>
<h2 id="what-does-offline-first-mean">What does Offline-First mean?</h2>
<p>The core idea is to <strong>persist/store remote-fetched data on the device</strong>. This allows users to access the data <strong>instantly</strong>, skipping unnecessary network requests—until the data expires or is invalidated.</p>
<p>If your app has this feature then your app is offline-first if it does not then lets make it one.</p>
<hr>
<p>While offline-first apps come with some complexities, the benefits far outweigh the challenges. but before we start a small disclaimer.</p>
<h2 id="a-quick-disclaimer">A Quick Disclaimer</h2>
<p>There are <strong>multiple ways</strong> to approach offline-first implementation. What I discuss here is <strong>my approach</strong>, tailored to my requirements. There might be <strong>better methods</strong> for different scenarios so take these as <strong>suggestions and not rules</strong>.</p>
<p>If you have suggestions, feedback, or alternative approaches, I’d love to discuss them! Let’s learn and refine this together. As I explore further, I will <strong>actively update</strong> these articles with new insights/approaches/strategies.</p>
<p>Now, let’s get started. 🚀</p>
<hr>
<h2 id="offline-strategies-partial-vs-full-offline-mode">Offline Strategies: Partial vs. Full Offline Mode</h2>
<p>There are <strong>two main strategies</strong> for implementing offline-first functionality:</p>
<ol>
<li><strong>Fully Offline Mode</strong></li>
<li><strong>Partial Offline Mode</strong></li>
</ol>
<p>The <strong>main difference</strong> between them is whether the user can modify their data offline.</p>
<h3 id="1-fully-offline-mode-crud-operations-offline">1️⃣ Fully Offline Mode (CRUD Operations Offline)</h3>
<p>In this approach, users can:<br>
✅ Create, Read, Update, and Delete (CRUD) <strong>without an internet connection</strong>.<br>
✅ Sync data to the cloud <strong>when online</strong>.</p>
<p>🔴 <strong>Major Challenge</strong>:</p>
<ul>
<li>If a user <strong>forgets to sync</strong> data from one device (e.g., a tablet) and later accesses their account from another device (e.g., a phone), they might think the data is lost.</li>
<li>This can <strong>frustrate users</strong>, leading to complaints or abandoning our app.</li>
</ul>
<p>🔵 <strong>Idea Use case</strong>:</p>
<p>Best option for apps that does not support multi user and has single device login, but ⚠️ The data loss % is still significantly high if user forgets to connect online after the initial login and loses his device etc, but thats a tradeoff you have to live with.</p>
<p>While this approach offers <strong>true offline functionality</strong>, it introduces <strong>complex synchronization issues</strong>, making it harder to maintain <strong>data consistency across devices</strong>.</p>
<h3 id="2-partial-offline-mode-read-only-when-offline">2️⃣ Partial Offline Mode (Read-Only When Offline)</h3>
<p>In this approach, users can:<br>
✅ <strong>View/Read data</strong> offline.<br>
❌ <strong>Modify data (Create, Update, Delete) only when online</strong>.</p>
<p>🔹 <strong>Why We Chose This Approach</strong>:</p>
<ul>
<li><strong>Multi-device users won’t face sync issues</strong> since data is always available in cloud.</li>
<li><strong>Less complexity &amp; better error handling &amp; almost no possibility of data loss</strong>.</li>
<li><strong>Data remains clean and consistent</strong> with the server.</li>
</ul>
<p>🔵 <strong>Idea Use case</strong>:</p>
<p>Best option for most use-cases simple or complex or extremely complex data set apps, with or without <strong>multi-user</strong> or <strong>multiple-device-logins</strong> and what not.</p>
<p>This is most preferred due to its <strong>data-consistency</strong> point and much simpler to implement, Now checkout the next one.</p>
<h3 id="3-hybrid-offline-mode-partially-restricted-write-operations">3️⃣ Hybrid Offline Mode (Partially-restricted Write operations)</h3>
<p>The Hybrid mode is using both discussed strategies depending on the use cases, for example you may allow the user to update their profile details or personal notes etc but not the public contents that can cause issue with other uses on the same org/team.</p>
<p>✅ <strong>View/Read data</strong> offline. <br>
✅ <strong>Modify some data even if offline</strong>.<br>
❌ <strong>Modify most data only if online</strong>.</p>
<p>🔵 <strong>Idea Use case:</strong></p>
<p>Best option for most simple or complex apps, with or without multi-user but <strong>strictly allow only single-device-login</strong>, this enables the user to do write-operations to user specific data while restricting the same for other parts of the app, there are tradeoffs here as well but only a particular user will be affected in the org/team.</p>
<p>For me the <strong>partial offline mode</strong> stands out as best option and would suggest the same for most use-cases, we will be sticking with this through out this journey.</p>
<p>Now, let’s dive into <strong>how we fetch and synchronize data</strong>.</p>
<hr>
<h2 id="data-synchronization-strategies"><strong>Data Synchronization Strategies</strong></h2>
<p>Once we are decided on <strong>partial offline mode</strong>, the next question is:<br>
👉 <strong>How do we fetch data from the server and keep it updated?</strong></p>
<p>There are two main synchronization approaches:</p>
<h3 id="1-on-demand-pull-based-synchronization">1️⃣ On-Demand (Pull-Based Synchronization)</h3>
<ul>
<li><strong>Data is fetched only when the user requests it.</strong></li>
<li>Data is <strong>invalidated</strong> when it <strong>expires</strong> or is <strong>manually deleted</strong>.</li>
<li><strong>Lightweight &amp; user-centric</strong>—fetches only relevant data.</li>
</ul>
<h3 id="2-clone-push-based-synchronization">2️⃣ Clone (Push-Based Synchronization)</h3>
<ul>
<li>The local database <strong>mirrors the entire remote database</strong>.</li>
<li><strong>Auto-updates</strong> when the server sends a flag indicating it was modified.</li>
<li><strong>Heavy &amp; resource-intensive</strong>, as it may download <strong>unnecessary</strong> data.</li>
</ul>
<p>🔹 <strong>Which One Did We Pick?</strong><br>
We chose <strong>On-Demand Synchronization</strong> since it’s:<br>
✅ <strong>Efficient</strong> (fetches only what’s needed).<br>
✅ <strong>Faster &amp; lightweight</strong> (minimizes unnecessary data transfers).</p>
<p>However, we use mox of both lets call it a <strong>hybrid approach</strong>, we loaded some data that user will need on the navigating screen to ensure a better user experience.  After that, everything is <strong>strictly on-demand</strong> the data is synced only based on user.</p>
<hr>
<h2 id="which-local-database-library-should-you-choose"><strong>Which Local Database Library Should You Choose?</strong></h2>
<p>There are several <strong>local database solutions</strong> available, but we chose <strong>Google’s Room Persistence Library</strong>. Here’s why:</p>
<p>✅ <strong>Built on SQLite</strong> – but with a modern, developer-friendly API.<br>
✅ <strong>Works seamlessly with Kotlin</strong> (supports data classes).<br>
✅ <strong>Compile-time SQL query verification</strong> (reduces errors).<br>
✅ <strong>Built-in support for LiveData, Flow, and Paging</strong>.<br>
✅ <strong>Faster development</strong> (less boilerplate, easy migrations).</p>
<p>Using <strong>Room</strong> significantly <strong>accelerates development</strong> while maintaining a structured and reliable database. It’s my <strong>go-to solution</strong>, and I highly recommend it for <strong>Android and Kotlin Multiplatform (KMP) projects</strong>.</p>
<hr>
<h2 id="whats-next"><strong>What’s Next?</strong></h2>
<p>This article laid the foundation for an <strong>offline-first architecture</strong> and explained <strong>why we chose partial offline mode &amp; on-demand synchronization</strong>.</p>
<p>📌 <strong>In the next part of this series, we’ll dive into:</strong></p>
<ul>
<li><strong>Setting up Room in an Android</strong>.</li>
<li><strong>Exploring Key Components of Room</strong>.</li>
</ul>
<hr>
<h2 id="final-thoughts"><strong>Final Thoughts</strong></h2>
<p>This is my journey in <strong>building an offline-first app</strong>. I’d love to hear your feedback, suggestions, or questions!</p>
<p>Feel free to connect with me on:<br>
📩 <strong><a href="mailto:mail@eknath.dev">Email</a></strong>   <br>
🌍 <strong><a href="https://eknath.dev">Website</a></strong>     <br>
💫 <strong><a href="https://www.linkedin.com/posts/eganathan_offlinefirstandroid-offlinefirst-android-activity-7294912159627546624-TG77?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAABYcOpgBgvDfy-0uUjfX0HTNqzzLfKZQAQU">LinkedIn-Post for comments and feedbacks</a></strong></p>
<p>🔖 <a href="https://md.eknath.dev/posts/upgrading-your-app-to-offline-first-with-room-part-2/">Next Article in this Series</a></p>
]]></content:encoded></item></channel></rss>