<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aalaa Fahiem </title>
    <description>The latest articles on DEV Community by Aalaa Fahiem  (@itsaalaa7).</description>
    <link>https://dev.to/itsaalaa7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3859289%2Fae7b77d0-955c-4c2f-b8d1-43ddb3012a83.png</url>
      <title>DEV Community: Aalaa Fahiem </title>
      <link>https://dev.to/itsaalaa7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/itsaalaa7"/>
    <language>en</language>
    <item>
      <title>I Thought Coroutines Were Enough. (Flow, StateFlow, MutableStateFlow)</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Tue, 23 Jun 2026 12:56:05 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/i-thought-coroutines-were-enough-flow-stateflow-mutablestateflow-39f</link>
      <guid>https://dev.to/itsaalaa7/i-thought-coroutines-were-enough-flow-stateflow-mutablestateflow-39f</guid>
      <description>&lt;p&gt;I had coroutines working. The data loaded. The screen showed something. I felt like I finally understood async in Android.&lt;/p&gt;

&lt;p&gt;Then I added a search bar that should filter results as you type.&lt;/p&gt;

&lt;p&gt;I called my suspend function on every keystroke. Every single character triggered a fresh network call. The results flickered. Old responses arrived after newer ones. The UI was a mess.&lt;/p&gt;

&lt;p&gt;That's when I realized — coroutines give you one value, once. But some data doesn't work that way. Some data is alive. It changes. It &lt;strong&gt;flows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's when I learned Flow. And then StateFlow. And the way data actually moves through an Android app finally made sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With &lt;code&gt;suspend&lt;/code&gt; for Live Data
&lt;/h2&gt;

&lt;p&gt;A suspend function is like ordering food. You place the order, you wait, you get your food. Done. One request, one result.&lt;/p&gt;

&lt;p&gt;But what if you're tracking a live score? What if you want to react every time the database changes? What if the user is typing and you want to filter results in real time?&lt;/p&gt;

&lt;p&gt;You don't want one answer. You want a &lt;strong&gt;stream&lt;/strong&gt; of answers over time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// One value, one time — then silence&lt;/span&gt;
&lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Pokemon&lt;/span&gt;

&lt;span class="c1"&gt;// A new value every time something changes&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemonUpdates&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the difference. &lt;code&gt;suspend&lt;/code&gt; is a bottle of water. &lt;code&gt;Flow&lt;/code&gt; is a pipe.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is a Flow?
&lt;/h2&gt;

&lt;p&gt;A Flow is a stream of values that arrive over time. You create it, you collect it, and every time a new value is emitted — your code runs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;countDown&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;flow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;emit()&lt;/code&gt; is how you push a value into the stream. Each emit is one item. The collector receives them one by one.&lt;/p&gt;

&lt;p&gt;And here's the important part — a Flow does nothing until someone collects it. It's &lt;strong&gt;lazy&lt;/strong&gt;. It just sits there, waiting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;countDown&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 3... 2... 1...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The moment you call &lt;code&gt;collect&lt;/code&gt;, the flow starts running.&lt;/p&gt;




&lt;h2&gt;
  
  
  Flow Operators — Where It Gets Useful
&lt;/h2&gt;

&lt;p&gt;This is the part I wish I'd paid attention to earlier.&lt;/p&gt;

&lt;p&gt;You don't have to collect raw values. You can transform the stream before it ever reaches your UI — filter it, map it, reshape it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPokemonList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"fire"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uppercase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reads like a sentence. Each operator takes the stream, does something to it, and passes it along.&lt;/p&gt;

&lt;p&gt;The ones you'll use most:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;map&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transform each value into something else&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;filter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only let values through that match a condition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onEach&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Do something with each value without changing it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;take(n)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop after n values&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Problem With Regular Flow in the UI
&lt;/h2&gt;

&lt;p&gt;Here's something I did wrong for longer than I'd like to admit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In ViewModel&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// In Activity&lt;/span&gt;
&lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pokemon&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks fine. It's not.&lt;/p&gt;

&lt;p&gt;Every time the Activity collects this flow — a new one starts from scratch. New network call. New everything. And when the phone rotates? The collection stops, the flow restarts, and the last value is gone. The screen flashes.&lt;/p&gt;

&lt;p&gt;You need something that holds the current value and gives it immediately to anyone who starts watching — even if they started watching late.&lt;/p&gt;

&lt;p&gt;That's StateFlow.&lt;/p&gt;




&lt;h2&gt;
  
  
  StateFlow — Flow That Remembers
&lt;/h2&gt;

&lt;p&gt;StateFlow is a flow with one extra superpower: it always holds the latest value in memory.&lt;/p&gt;

&lt;p&gt;Think of a scoreboard at a football match. Whether you look at it the moment a goal is scored or ten minutes later — you see the current score. You don't wait for the next goal to know what's happening.&lt;/p&gt;

&lt;p&gt;That's StateFlow. It always knows what the current value is, and it tells you immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PokemonViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;_pokemon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_pokemon&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;loadPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;_pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice — no &lt;code&gt;emit()&lt;/code&gt;. StateFlow uses &lt;code&gt;.value =&lt;/code&gt; to update. And it lives in the ViewModel, so it survives rotation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Private/Public Pattern (Please Don't Skip This)
&lt;/h2&gt;

&lt;p&gt;You'll see this in every professional Android project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;_pokemon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// you write to this&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_pokemon&lt;/span&gt;              &lt;span class="c1"&gt;// UI reads from this&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The underscore one is private and mutable — only the ViewModel can change it. The public one is read-only — the UI can observe it but never modify it directly.&lt;/p&gt;

&lt;p&gt;This isn't just style. It's a rule. The UI should never control the state. It just reacts to it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Collecting StateFlow in the UI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;repeatOnLifecycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Lifecycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;STARTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pokemon&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;pokemon&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;repeatOnLifecycle(STARTED)&lt;/code&gt; is the piece most tutorials skip. It automatically pauses collection when the app goes to background and resumes when it comes back. Without it, your flow keeps running even when the user can't see the screen — wasting battery, risking crashes.&lt;/p&gt;

&lt;p&gt;Always use it when collecting in an Activity or Fragment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Flow vs StateFlow — The Simple Version
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Flow&lt;/th&gt;
&lt;th&gt;StateFlow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Holds last value?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Starts automatically?&lt;/td&gt;
&lt;td&gt;No — needs a collector&lt;/td&gt;
&lt;td&gt;Yes — always running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where it lives&lt;/td&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;ViewModel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use it for&lt;/td&gt;
&lt;td&gt;DB queries, data streams&lt;/td&gt;
&lt;td&gt;UI state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rule I follow: data comes from the repository as &lt;code&gt;Flow&lt;/code&gt;. Data goes to the UI as &lt;code&gt;StateFlow&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Repository — emits a stream&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemonList&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// ViewModel — holds the current state for the UI&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_list&lt;/span&gt;

&lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPokemonList&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;_list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Full Picture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repository
   └── Flow&amp;lt;T&amp;gt;               ← stream from data source, lazy

ViewModel
   └── MutableStateFlow&amp;lt;T&amp;gt;   ← holds current value, survives rotation
   └── StateFlow&amp;lt;T&amp;gt;          ← read-only, exposed to UI

UI
   └── collect { }           ← reacts to every change
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I drew this out, everything clicked. The data flows in one direction — from the source, through the ViewModel, to the screen. Each layer has one job.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Wish I Knew Earlier
&lt;/h2&gt;

&lt;p&gt;Flow and StateFlow aren't two separate tools that compete. They're two parts of the same pipeline.&lt;/p&gt;

&lt;p&gt;Flow is for the journey. StateFlow is for the destination.&lt;/p&gt;

&lt;p&gt;Once you think of it that way — your Repository emitting changes, your ViewModel holding the latest version of truth, your UI just reacting — the code writes itself.&lt;/p&gt;

&lt;p&gt;And that flickering search bar I mentioned at the start? Fixed. One Flow, debounced, collected properly. No more race conditions. No more flashing.&lt;/p&gt;

</description>
      <category>android</category>
      <category>beginners</category>
      <category>flow</category>
      <category>stateflow</category>
    </item>
    <item>
      <title>My App Froze Every Time It Loaded Data. That's When I Finally Understood Coroutines.</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Mon, 22 Jun 2026 09:56:15 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/my-app-froze-every-time-it-loaded-data-thats-when-i-finally-understood-coroutines-1ah5</link>
      <guid>https://dev.to/itsaalaa7/my-app-froze-every-time-it-loaded-data-thats-when-i-finally-understood-coroutines-1ah5</guid>
      <description>&lt;p&gt;I didn't know my app was broken.&lt;/p&gt;

&lt;p&gt;It worked. The data showed up. The UI updated. I thought I was doing great.&lt;/p&gt;

&lt;p&gt;Then someone ran it on a slow connection and sent me a screen recording. The app was completely frozen for 3 seconds. Nothing moved. The button didn't respond. It looked dead.&lt;/p&gt;

&lt;p&gt;I was doing network calls on the Main Thread. I had no idea that was even a thing I could do wrong.&lt;/p&gt;

&lt;p&gt;That's when I started actually learning coroutines — not copy-pasting them, &lt;strong&gt;learning&lt;/strong&gt; them.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, Why Does the Main Thread Even Matter?
&lt;/h2&gt;

&lt;p&gt;Your Android app runs on one main thread. That thread does everything you see — drawing buttons, responding to taps, updating text. It's the only thread that can touch the UI.&lt;/p&gt;

&lt;p&gt;If you give it slow work to do — like waiting for an API response — it can't do anything else. It just stands there. Waiting. While your users stare at a frozen screen.&lt;/p&gt;

&lt;p&gt;Think of it like a waiter who goes to the kitchen, stands there until the food is ready, and refuses to take any other orders in the meantime. The whole restaurant stops.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Main Thread: [waiting for network response.........] [show result]
                    ☠️ App is completely frozen here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the problem coroutines solve.&lt;/p&gt;




&lt;h2&gt;
  
  
  So What Even Is a Coroutine?
&lt;/h2&gt;

&lt;p&gt;A coroutine is a piece of work that can pause itself &lt;strong&gt;without blocking the thread&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The waiter analogy again — but this time, the waiter puts in the order, goes back to serving other tables, and comes back when the food is ready. The restaurant keeps running. Nobody is waiting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Coroutine:    [start] → [pause: waiting for network] → [resume] → [done]
Main Thread:                  ↑ free to handle UI here ↑
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The keyword that makes this possible is &lt;code&gt;suspend&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;suspend&lt;/code&gt; — The One Keyword That Changes Everything
&lt;/h2&gt;

&lt;p&gt;A suspend function is a function that can pause and resume. That's the whole thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Pokemon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pikachu"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// pauses here, frees the thread&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks exactly like a normal function. It returns a value like a normal function. The difference is what happens underneath — instead of blocking the thread, it pauses and lets the thread go do other things.&lt;/p&gt;

&lt;p&gt;Two rules to remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can only call a suspend function from another suspend function, or from inside a coroutine.&lt;/li&gt;
&lt;li&gt;That's it. That's the rule.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Starting a Coroutine — &lt;code&gt;launch&lt;/code&gt; vs &lt;code&gt;async&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You can't just call a suspend function from anywhere. You need to launch a coroutine first. Think of it as opening a room where suspend functions are allowed to live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;launch&lt;/code&gt; — when you don't need a result back&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// suspend call — pauses here&lt;/span&gt;
    &lt;span class="n"&gt;_uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pokemon&lt;/span&gt;   &lt;span class="c1"&gt;// resumes here when done&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;async&lt;/code&gt; — when you need a result&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemonDeferred&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pokemonDeferred&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;await&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// wait for the result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the time you'll use &lt;code&gt;launch&lt;/code&gt;. Use &lt;code&gt;async&lt;/code&gt; when you want to run two things at the same time and wait for both.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dispatchers — Who Does the Work?
&lt;/h2&gt;

&lt;p&gt;Just because you're in a coroutine doesn't mean the slow work goes to the right thread automatically. You have to tell it where to run. That's what Dispatchers are for.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dispatcher&lt;/th&gt;
&lt;th&gt;Use it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Dispatchers.Main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UI updates — text, buttons, anything visible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Dispatchers.IO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Network calls, database reads, file access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Dispatchers.Default&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Heavy CPU work — sorting, parsing large data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// network work on IO&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pikachu"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;withContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;      &lt;span class="c1"&gt;// switch to Main for UI&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;withContext&lt;/code&gt; is how you switch dispatchers mid-coroutine. It's clean. It's readable. You'll use it constantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scopes — The Lifetime of a Coroutine
&lt;/h2&gt;

&lt;p&gt;Every coroutine lives inside a scope. When the scope dies, all its coroutines die too. This is how Android prevents memory leaks automatically.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Dies when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;viewModelScope&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The ViewModel is cleared&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lifecycleScope&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The Activity or Fragment is destroyed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GlobalScope&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Never — avoid this one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As a junior dev, you'll mostly use &lt;code&gt;viewModelScope&lt;/code&gt;. It's the safe default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PokemonViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;loadPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// auto-cancelled when ViewModel dies&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;_pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pokemon&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You didn't write any cleanup code. You didn't manage any threads manually. The scope handled all of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Error Handling
&lt;/h2&gt;

&lt;p&gt;One more thing before you go build something — handle your errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;_uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Something went wrong"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Network calls fail. Always wrap them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model
&lt;/h2&gt;

&lt;p&gt;This is the whole thing in one picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt;  &lt;span class="err"&gt;→&lt;/span&gt;  &lt;span class="nf"&gt;coroutine&lt;/span&gt; &lt;span class="nc"&gt;PAUSES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="n"&gt;goes&lt;/span&gt; &lt;span class="n"&gt;free&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="n"&gt;arrives&lt;/span&gt;  &lt;span class="err"&gt;→&lt;/span&gt;  &lt;span class="n"&gt;coroutine&lt;/span&gt; &lt;span class="nc"&gt;RESUMES&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="nf"&gt;withContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="nc"&gt;UI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scope&lt;/strong&gt; controls the lifetime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dispatcher&lt;/strong&gt; controls the thread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;suspend&lt;/code&gt;&lt;/strong&gt; controls the pause.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I Wish I Knew Earlier
&lt;/h2&gt;

&lt;p&gt;Coroutines aren't magic. They're a structured way to write async code that looks synchronous. No callbacks. No nested hell. Just top-to-bottom code that knows when to pause and when to come back.&lt;/p&gt;

&lt;p&gt;Once that clicked for me, a lot of the "why does this feel wrong" in my code started making sense.&lt;/p&gt;

&lt;p&gt;If your app ever froze and you didn't know why — now you do.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>android</category>
      <category>mobile</category>
      <category>coroutines</category>
    </item>
    <item>
      <title>As a Junior Android Dev Should I Memorize All Retrofit Code or Just Ask AI?(Interview Questions)</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Wed, 17 Jun 2026 09:24:34 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/as-a-junior-android-dev-should-i-memorize-all-retrofit-code-or-just-ask-aiinterview-questions-2104</link>
      <guid>https://dev.to/itsaalaa7/as-a-junior-android-dev-should-i-memorize-all-retrofit-code-or-just-ask-aiinterview-questions-2104</guid>
      <description>&lt;p&gt;I was learning Retrofit and staring at a wall of boilerplate code thinking:&lt;br&gt;
"Am I supposed to have all of this memorized?"&lt;/p&gt;
&lt;h2&gt;
  
  
  So I asked — and the honest answer surprised me. Most juniors are afraid to raise this question. Here's what I found out.
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Should You Write It All From Memory?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No. And yes. It depends on what part.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of it like learning to drive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don't need to know how to build a car engine from scratch&lt;/li&gt;
&lt;li&gt;But you &lt;strong&gt;do&lt;/strong&gt; need to know how to drive, and what to do when something
breaks
There are &lt;strong&gt;3 zones&lt;/strong&gt; — and once you understand them, the whole learning process feels way less overwhelming.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Zone 1 — Let AI Write It
&lt;/h2&gt;

&lt;p&gt;Boilerplate. Nobody memorizes this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This setup in AppModule.kt? AI writes it. Every time.&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;okHttpClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectionSpecs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ConnectionSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CLEARTEXT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HTTP_1_1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;Dns&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InetAddress&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Dns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SYSTEM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Inet4Address&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ifEmpty&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Dns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SYSTEM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SECONDS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nobody writes this from memory. Senior devs copy-paste it from their last project. Use AI or docs — zero shame.&lt;/p&gt;




&lt;p&gt;Zone 2 — You MUST Know This Cold&lt;br&gt;
This will be tested in interviews. Learn it by heart.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Writing the API interface yourself
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;PokeApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemon"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemonList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;PokemonList&lt;/span&gt;

    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemon/{name}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemonInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Pokemon&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It's only ~10 lines, and it's the heart of Retrofit. Write it without help.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. The basic Retrofit builder
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://pokeapi.co/api/v2/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConverterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GsonConverterFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PokeApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;4 lines. Know these by heart.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. The data flow in your head
&lt;/h3&gt;

&lt;p&gt;Be able to explain this out loud without hesitation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI → ViewModel → Repository → API interface → Retrofit builds URL → OkHttp sends it → Gson parses response → back up the chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. What the annotations do
&lt;/h3&gt;

&lt;p&gt;-&lt;code&gt;@GET&lt;/code&gt; — Sends a GET request to the given endpoint&lt;br&gt;
-&lt;code&gt;@POST&lt;/code&gt; — Sends a POST request&lt;br&gt;
-&lt;code&gt;@Query&lt;/code&gt; — Adds ?key=value to the end of the URL&lt;br&gt;
-&lt;code&gt;@Path&lt;/code&gt; — Fills a {slot} inside the URL&lt;br&gt;
-&lt;code&gt;@Body&lt;/code&gt; — Sends an object as the request body (JSON)&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Why &lt;code&gt;suspend&lt;/code&gt;?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"Network calls block the thread. &lt;code&gt;suspend&lt;/code&gt; runs them in a coroutine so the UI doesn't freeze."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Key points to remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;suspend&lt;/code&gt; means the function can pause while waiting (for network, DB, etc.) and resume later without blocking the app&lt;/li&gt;
&lt;li&gt;It does &lt;strong&gt;not&lt;/strong&gt; create a new thread — it just allows pausing and resuming&lt;/li&gt;
&lt;li&gt;You call it from a &lt;strong&gt;coroutine scope&lt;/strong&gt; like &lt;code&gt;viewModelScope.launch { }&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Retrofit API calls are &lt;code&gt;suspend&lt;/code&gt; because network requests take time&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Zone 3 — Understand It, Don't Memorize It
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Resource wrapper — understand WHY it exists&lt;/span&gt;
&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You should be able to explain:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"It wraps the result so the ViewModel can handle success, error, and loading states cleanly."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You don't need to write it from memory — but if an interviewer shows it to you, you need to explain it confidently.&lt;/p&gt;

&lt;p&gt;The mental model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;suspend              → gets data safely from slow operations
Resource             → tells the app what happened with the data
ViewModel + StateFlow → sends the new state to the UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Real Interview Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🟢 Easy — Answer instantly
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;"What is Retrofit?"&lt;/strong&gt;&lt;br&gt;
A type-safe HTTP client that turns your interface methods into network calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What is &lt;code&gt;@GET&lt;/code&gt;?"&lt;/strong&gt;&lt;br&gt;
Annotation that makes the function send a GET request to that endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Difference between &lt;code&gt;@Query&lt;/code&gt; and &lt;code&gt;@Path&lt;/code&gt;?"&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;@Path&lt;/code&gt; fills a &lt;code&gt;{slot}&lt;/code&gt; in the URL, &lt;code&gt;@Query&lt;/code&gt; adds &lt;code&gt;?key=value&lt;/code&gt; at the end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What is Gson converter?"&lt;/strong&gt;&lt;br&gt;
Converts JSON from the server into Kotlin/Java objects automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Why &lt;code&gt;suspend&lt;/code&gt;?"&lt;/strong&gt;&lt;br&gt;
Network calls are slow — suspend runs them off the main thread so the UI doesn't freeze.&lt;/p&gt;


&lt;h3&gt;
  
  
  🟡 Medium — Think 5 seconds, then answer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;"What is OkHttp and how does it relate to Retrofit?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Retrofit sits &lt;em&gt;on top of&lt;/em&gt; OkHttp. Retrofit handles the interface, annotations, and conversion. OkHttp is the actual engine that opens the socket and sends/receives bytes. You can customize OkHttp (timeouts, interceptors, headers) and plug it into Retrofit.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;"What is an Interceptor?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A piece of code that runs on every request before it's sent (or every response before it arrives). Used for adding auth headers, logging, modifying requests.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;"What is the Repository pattern and why use it?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A middle layer between the ViewModel and the data source (API/database). The ViewModel doesn't care whether data comes from the internet or a local DB — it just asks the Repository. Makes testing easier and keeps code clean.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;"What does @SerializedName do?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tells Gson to map a specific JSON key to a Kotlin field, even if the names are different. Example: JSON has &lt;code&gt;"base_experience"&lt;/code&gt; but Kotlin uses &lt;code&gt;baseExperience&lt;/code&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  🔴 Hard — Bonus points if you nail these
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;"What happens if you don't add &lt;code&gt;suspend&lt;/code&gt; to a Retrofit function?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before Retrofit 2.6, you used &lt;code&gt;Call&amp;lt;T&amp;gt;&lt;/code&gt;. With &lt;code&gt;suspend&lt;/code&gt;, Retrofit uses coroutines natively and runs on a background thread automatically. Without &lt;code&gt;suspend&lt;/code&gt; in a coroutine-based project, you'd get a crash or have to handle &lt;code&gt;Call&amp;lt;T&amp;gt;&lt;/code&gt; manually.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;"How would you add an auth token to every request?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an OkHttp Interceptor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addInterceptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;newBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bearer $token"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"What is &lt;a class="mentioned-user" href="https://dev.to/singleton"&gt;@singleton&lt;/a&gt; in Hilt and why does Retrofit need it?"&lt;/p&gt;

&lt;p&gt;Creating a Retrofit instance is expensive — it sets up connection pools, parsers, and configuration. &lt;a class="mentioned-user" href="https://dev.to/singleton"&gt;@singleton&lt;/a&gt; tells Hilt to create it once and reuse the same instance everywhere.&lt;/p&gt;

&lt;p&gt;Key points:&lt;/p&gt;

&lt;p&gt;Without &lt;a class="mentioned-user" href="https://dev.to/singleton"&gt;@singleton&lt;/a&gt;, Hilt may create multiple Retrofit objects — wasteful and slow&lt;/p&gt;

&lt;h2&gt;
  
  
  In Android apps, we keep one Retrofit instance for the whole app and inject it wherever needed
&lt;/h2&gt;

&lt;p&gt;TL;DR&lt;br&gt;
Zone 1 — Boilerplate → Let AI write it. Always.&lt;br&gt;
Zone 2 — Core concepts → Memorize it. Interviews will ask.&lt;br&gt;
Zone 3 — Patterns → Understand the why, not the exact syntax.&lt;br&gt;
The goal isn't to be a human compiler. It's to understand the system well enough to drive it, debug it, and explain it — even when you don't know every line by heart.&lt;/p&gt;

&lt;p&gt;Still learning Kotlin and Android as a junior dev — writing about it as I go. If this helped, I'd love to hear what you're building! 👇&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>beginners</category>
      <category>retrofit</category>
      <category>android</category>
    </item>
    <item>
      <title>The Complete Retrofit Lifecycle in a Real Android App</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:34:39 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/the-complete-retrofit-lifecycle-in-a-real-android-app-4je2</link>
      <guid>https://dev.to/itsaalaa7/the-complete-retrofit-lifecycle-in-a-real-android-app-4je2</guid>
      <description>&lt;p&gt;Most Retrofit tutorials show you isolated code snippets. This article traces the &lt;strong&gt;entire lifecycle&lt;/strong&gt; of a Retrofit call — from app startup to data appearing on screen — using real code from a Pokedex app built with Kotlin, Hilt, Coroutines, and Jetpack Compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  By the end, you'll understand every link in the chain!
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What Is Retrofit?
&lt;/h2&gt;

&lt;p&gt;Retrofit is a type-safe HTTP client for Android and Java. Instead of writing raw HTTP requests, you describe your API as a Kotlin &lt;strong&gt;interface&lt;/strong&gt;, and Retrofit generates the networking code for you behind the scenes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;PokeApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemon"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemonList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;PokemonList&lt;/span&gt;

    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemon/{name}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemonInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Pokemon&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No manual &lt;code&gt;HttpURLConnection&lt;/code&gt;, no manual JSON parsing. Retrofit handles building the request and Gson (or Moshi) handles converting the JSON response into your Kotlin objects.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 3 Building Blocks
&lt;/h2&gt;

&lt;p&gt;Every Retrofit setup needs exactly three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Interface&lt;/strong&gt; — what requests can be made&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Builder&lt;/strong&gt; — where to send them and how to parse responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Response model&lt;/strong&gt; — what shape the data comes back in
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retrofit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://pokeapi.co/api/v2/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConverterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GsonConverterFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PokeApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Full Lifecycle, Step by Step
&lt;/h2&gt;

&lt;p&gt;Here's the entire request lifecycle traced through a real app architecture: &lt;strong&gt;Compose UI → ViewModel → Repository → Retrofit → OkHttp → Server → Gson → back up the chain.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — App Starts, Hilt Wakes Up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@HiltAndroidApp&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PokedexApplication&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nc"&gt;Timber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Timber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DebugTree&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@HiltAndroidApp&lt;/code&gt; tells Hilt to start managing dependency injection across the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Hilt Builds Retrofit Once (Singleton)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Module&lt;/span&gt;
&lt;span class="nd"&gt;@InstallIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SingletonComponent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;providePokeApi&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;PokeApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;okHttpClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SECONDS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SECONDS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retryOnConnectionFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addInterceptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;newBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Mozilla/5.0 ..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;okHttpClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConverterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GsonConverterFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PokeApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;providePokemonRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PokeApi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PokemonRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building a Retrofit instance is expensive — connection pools, thread pools, parsers. &lt;code&gt;@Singleton&lt;/code&gt; ensures it's built &lt;strong&gt;once&lt;/strong&gt; and reused everywhere, instead of recreated per screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — ViewModel Is Created, Requests Data Immediately
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@HiltViewModel&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PokemonListViewModel&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PokemonRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;pokemonList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PokedexListEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;loadPokemonPaginated&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPokemonList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curPage&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;PAGE_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// handle result below&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hilt injects the &lt;code&gt;PokemonRepository&lt;/code&gt; automatically — no manual instantiation needed. &lt;code&gt;viewModelScope.launch&lt;/code&gt; starts a coroutine so the network call doesn't block the main thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — Repository Calls the API and Handles Failure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Singleton&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PokemonRepository&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PokeApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemonList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PokemonList&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPokemonList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Couldn't load Pokemon"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Repository pattern decouples the ViewModel from the data source. The ViewModel doesn't know — or care — whether data comes from a network call, a cache, or a database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 — Retrofit Builds the URL and OkHttp Sends It
&lt;/h3&gt;

&lt;p&gt;Given:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemon"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPokemonList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;PokemonList&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;getPokemonList(20, 0)&lt;/code&gt; produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://pokeapi.co/api/v2/pokemon?limit=20&amp;amp;offset=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OkHttp (the engine underneath Retrofit) opens the connection, attaches any interceptor headers, and sends the request over the wire.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 — Server Responds With JSON
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pokeapi.co/api/v2/pokemon?offset=20&amp;amp;limit=20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"previous"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bulbasaur"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pokeapi.co/api/v2/pokemon/1/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7 — Gson Converts JSON Into Kotlin Objects
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;PokemonList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@SerializedName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerializedName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"next"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerializedName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"previous"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerializedName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@SerializedName&lt;/code&gt; maps a JSON key to a Kotlin property — essential when the server uses &lt;code&gt;snake_case&lt;/code&gt; and your code uses &lt;code&gt;camelCase&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8 — Result Flows Back Up, Wrapped in a Sealed Class
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wrapper lets the ViewModel handle success, error, and loading as explicit states rather than nullable guesswork.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9 — ViewModel Updates State, Compose Redraws
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Success&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;pokemonList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* map to UI model */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;loadError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;
        &lt;span class="n"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;pokemonList&lt;/code&gt; is a Compose &lt;code&gt;mutableStateOf&lt;/code&gt;, the moment it changes, any composable reading it recomposes automatically. No manual UI refresh needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full Chain, Visualized
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App starts → Hilt builds Retrofit + Repository (once)
    ↓
ViewModel created → Repository injected → init{} fires
    ↓
viewModelScope.launch → repository.getPokemonList()
    ↓
api.getPokemonList() → Retrofit builds the URL
    ↓
OkHttp attaches headers, sends the HTTP request
    ↓
Server responds with JSON
    ↓
Gson converts JSON → Kotlin data class
    ↓
Repository wraps it in Resource.Success / Resource.Error
    ↓
ViewModel updates mutableStateOf
    ↓
Compose recomposes → user sees the data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

</description>
      <category>retrofit</category>
      <category>mobile</category>
      <category>android</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Jetpack Compose Navigation(Interview Prep)</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Wed, 10 Jun 2026 17:58:39 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/jetpack-compose-navigationinterview-prep-2bc7</link>
      <guid>https://dev.to/itsaalaa7/jetpack-compose-navigationinterview-prep-2bc7</guid>
      <description>&lt;p&gt;About this article: This is based on hands-on review of a real project. The goal is understanding over memorization — because interviewers can always tell the difference.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Screen.Details.route vs Screen.Details.createRoute(name) — What's the Difference?
This trips up a lot of people because both look like they're "the route." They're not.
Screen.Details.route is a template — it contains a placeholder:
"details/{pokemonName}"
You use this when registering the destination inside NavHost, so the navigation system knows the shape of the route and which argument to extract.
Screen.Details.createRoute(name) produces a real URL like:
"details/Pikachu"
You use this when navigating — navController.navigate(...) needs an actual address with real data embedded.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;⚠️ What breaks if you use .route in both places?&lt;br&gt;
NavHost would still match the route, but the extracted argument would be the literal string "{pokemonName}" instead of the actual Pokémon name. No crash — just silent wrong data.&lt;/p&gt;

&lt;p&gt;Key Rule → Template for registration. Real URL for navigation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Defining a Route in NavConstants Doesn't Register It
If NavConstants.kt defines Search and Settings but they never appear in AppNavigation, what happens when you call navController.navigate(Screen.Search.route)?
The app crashes at runtime:
IllegalArgumentException:
"navigation destination 'search' is unknown to this NavController"
Here's why this confuses people:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;NavConstants.kt is just a dictionary of strings — defining a route there costs nothing and guarantees nothing.&lt;br&gt;
AppNavigation.kt is where destinations actually exist — a route only becomes real when it has a matching composable() registered inside NavHost.&lt;/p&gt;

&lt;p&gt;NavConstants protects you from typos. It cannot protect you from forgetting to register the destination. Both mistakes crash the app.&lt;br&gt;
Key Rule → You need both — the string AND the composable() registration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;nullable = false and if (name != null) Are Not Contradicting Each Other&lt;br&gt;
You might see navArgument("pokemonName") { nullable = false } declared, and then right below it, the code still does ?. and if (name != null). That looks contradictory. It's not.&lt;br&gt;
They operate at completely different layers:&lt;br&gt;
nullable = falseif (name != null)LayerNavigation systemKotlin type systemWhen it actsRuntime navigationCompile timeWhat it doesCrashes if arg is missingSatisfies the compiler&lt;br&gt;
Bundle.getString() always returns String? regardless of your navigation contract — the Kotlin compiler has no idea about navArgument rules. The null check is there to satisfy the compiler, not because you genuinely expect null at runtime.&lt;br&gt;
Key Rule → Navigation contract and Kotlin types are separate systems that don't talk to each other.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;popBackStack() vs navigate(Home) — Never Substitute One for the Other&lt;br&gt;
Both can result in the user seeing the Home screen. But what they do to the back stack is completely different.&lt;br&gt;
popBackStack() removes the current screen and reveals what was beneath it:&lt;br&gt;
Before: [Home, Details]&lt;br&gt;
After:  [Home]           ← Details removed, Home revealed&lt;br&gt;
navigate(Screen.Home.route) pushes a new Home screen on top:&lt;br&gt;
Before: [Home, Details]&lt;br&gt;
After:  [Home, Details, Home]  ← new Home added&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;⚠️ Why it matters: After navigate(), pressing Back lands the user on Details — a screen they thought they left. Press again, they're on the original Home. The back stack grows silently and creates a broken experience.&lt;/p&gt;

&lt;p&gt;Key Rule → Use popBackStack() to go back. Use navigate() to go forward. Never substitute.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When Do You Actually Need backStackEntry?&lt;br&gt;
Inside NavHost, the lambda for composable() can optionally name its parameter backStackEntry. Some composables do, some don't. Why?&lt;br&gt;
backStackEntry gives access to arguments embedded in the route. Screens with static routes like "home" or "profile" have no arguments — there's nothing to extract, so backStackEntry is ignored and not named.&lt;br&gt;
Details has {pokemonName} in its route. When navController.navigate("details/Pikachu") was called, the navigation system stored "Pikachu" in the backStackEntry's argument bundle. backStackEntry.arguments?.getString("pokemonName") pulls it back out.&lt;br&gt;
Key Rule → You only need backStackEntry when the route carries arguments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Three Layers of Compose Navigation&lt;br&gt;
Understanding these three layers separately makes everything else click.&lt;br&gt;
🔵 Navigation System Layer&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rules enforced at runtime by NavHost/NavController&lt;br&gt;
Every route navigated to must be registered with composable() — otherwise crash&lt;br&gt;
nullable = false is a contract here — violate it, it throws&lt;br&gt;
Doesn't know Kotlin types&lt;/p&gt;

&lt;p&gt;🟣 Kotlin Type System Layer&lt;/p&gt;

&lt;p&gt;Rules enforced at compile time by the compiler&lt;br&gt;
Bundle.getString() returns String? — compiler forces null handling&lt;br&gt;
Has no awareness of navigation contracts or navArgument rules&lt;br&gt;
Doesn't know your back stack state&lt;/p&gt;

&lt;p&gt;🟢 Back Stack Layer&lt;/p&gt;

&lt;p&gt;Runtime state — the history of where the user has been&lt;br&gt;
navigate() always pushes a new destination&lt;br&gt;
popBackStack() always removes the top destination&lt;br&gt;
System Back button also pops it&lt;br&gt;
Keeps growing until something pops it&lt;/p&gt;

&lt;p&gt;A quick comparison between the two that are most often confused:&lt;br&gt;
Kotlin Type SystemBack StackNatureCompile-time rulesRuntime stateCares aboutNullability, typesNavigation historyExamplegetString() returns String?[Home, Details] grows with navigate()&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigation Hoisting — Screens Get Lambdas, Not NavController
Navigation hoisting means screen composables receive navigation actions as lambda parameters instead of having direct access to navController.
kotlin// AppNavigation owns navController, passes actions as lambdas
HomeScreen(
onNavigateToProfile = { navController.navigate(Screen.Profile.route) },
onPokemonClick = { name -&amp;gt; navController.navigate(Screen.Details.createRoute(name)) }
)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;// HomeScreen knows nothing about navController&lt;br&gt;
@Composable&lt;br&gt;
fun HomeScreen(&lt;br&gt;
    onNavigateToProfile: () -&amp;gt; Unit,&lt;br&gt;
    onPokemonClick: (String) -&amp;gt; Unit&lt;br&gt;
)&lt;br&gt;
Why this pattern exists:&lt;/p&gt;

&lt;p&gt;Separates UI logic from navigation logic&lt;br&gt;
HomeScreen becomes testable without a real NavController&lt;br&gt;
Screens stay reusable — they don't care how navigation is implemented&lt;/p&gt;

&lt;p&gt;Notice the subtle difference between the two lambdas above:&lt;/p&gt;

&lt;p&gt;onNavigateToProfile: () -&amp;gt; Unit — no data needed; Profile renders itself without input&lt;br&gt;
onPokemonClick: (String) -&amp;gt; Unit — carries the Pokémon name; Details needs it to know what to show&lt;/p&gt;

&lt;p&gt;Key Rule → Screens get lambdas, not navController.&lt;/p&gt;

&lt;p&gt;Quick Reference Card&lt;br&gt;
ConceptRule.route vs createRoute()Template to register, real URL to navigateUnregistered routeCrashes at runtime with IllegalArgumentExceptionnullable = falseNavigation contract — not Kotlin null safetypopBackStack() vs navigate()Go back vs go forward — never substitutebackStackEntryOnly needed when the route carries argumentsNavigation hoistingScreens get lambdas, not navControllerNavConstantsPrevents typos, not missing registrations&lt;/p&gt;

&lt;p&gt;Found this useful? These notes came from reviewing a real Compose project end-to-end — the kind of hands-on work that makes interview answers actually stick.&lt;/p&gt;

</description>
      <category>interview</category>
      <category>android</category>
      <category>navigation</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Thought Passing Data Between Screens Would Be Easy. It Was Not.</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Tue, 09 Jun 2026 18:10:50 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/i-thought-passing-data-between-screens-would-be-easy-it-was-not-2ek4</link>
      <guid>https://dev.to/itsaalaa7/i-thought-passing-data-between-screens-would-be-easy-it-was-not-2ek4</guid>
      <description>&lt;p&gt;Let me tell you exactly how my week went.&lt;br&gt;
Day one: I tried to pass a full Product object through navigation instead of just the ID.&lt;br&gt;
The app compiled fine. Then it crashed the moment I tapped a product. No helpful message. Just gone.&lt;br&gt;
Day two: I passed the ID correctly this time — I'd learned from the last article — but after navigating to the detail screen, I had this weird feeling: where is the data actually living right now? I couldn't explain it. I just trusted that backStackEntry.arguments would have it, and sometimes it did, and sometimes I got null, and I had no idea why.&lt;br&gt;
Day three: I figured out how to send data forward fine. But then I needed to send data back — back to the previous screen after the user made a selection. And I stared at the screen for a very long time because nothing in what I'd learned prepared me for that direction.&lt;br&gt;
All three of these happened to me within the same week on the same project. And they all came from the same root problem: I was guessing at how navigation handles data instead of actually understanding it.&lt;br&gt;
This article is me stopping the guessing — for both of us.&lt;br&gt;
We're continuing with the e-commerce app from the last article: Product List → Product Detail → Cart. If you haven't read that one, the short version is: we have a sealed class for routes, a NavHost, and a NavController. That setup stays exactly the same. What we're doing today is understanding everything that happens when data moves between those screens.&lt;/p&gt;

&lt;p&gt;Why You Can't Just Pass a Full Object&lt;br&gt;
This is where most people start — and where most people hit their first wall.&lt;br&gt;
You have a Product. The detail screen needs a Product. Why not just... pass it?&lt;br&gt;
kotlin// This feels logical. Don't do it.&lt;br&gt;
navController.navigate(Screen.ProductDetail.createRoute(product))&lt;br&gt;
Here's why it doesn't work.&lt;br&gt;
Navigation routes are strings. That's the core of the system. When you call navController.navigate(...), you're navigating to a string address — like a URL. "product_detail/1" is a valid address. "product_detail/Product(id=1, name=Air Max 90, price=120.0)" is not.&lt;br&gt;
The system wasn't built to serialize and deserialize objects through a URL. And even if you found a workaround — JSON encoding, for example — you'd be fighting the architecture instead of using it.&lt;br&gt;
There's also a deeper reason. Screens in a navigation stack can be recreated from their route alone. If Android kills your process and the user comes back, it needs to be able to rebuild the back stack. A string ID can do that. A full object floating in memory cannot.&lt;br&gt;
So the rule is simple: pass the minimum. Pass the ID.&lt;br&gt;
kotlin// This is the right mental model.&lt;br&gt;
// The ID is just an address. The screen goes and fetches the full data.&lt;br&gt;
navController.navigate(Screen.ProductDetail.createRoute(product.id))&lt;/p&gt;

&lt;p&gt;Primitives: What's Actually Happening&lt;br&gt;
In the last article, we passed a productId: Int and moved on. Let's slow down and actually read what's happening, because this is where the null mystery comes from.&lt;br&gt;
Here's the route definition:&lt;br&gt;
kotlinobject ProductDetail : Screen("product_detail/{productId}") {&lt;br&gt;
    fun createRoute(productId: Int) = "product_detail/$productId"&lt;br&gt;
}&lt;br&gt;
The {productId} in the route template is a placeholder. It's saying: "when this screen is registered, expect something called productId to be embedded in the URL."&lt;br&gt;
When you navigate with "product_detail/1", the system parses the URL, matches the pattern, and extracts 1 as the value of productId. It holds onto it in a Bundle called arguments.&lt;br&gt;
That Bundle lives inside the NavBackStackEntry — the entry for this specific screen in the back stack. It's tied to that entry's lifecycle. As long as the screen is in the back stack, the arguments are there.&lt;br&gt;
Here's the NavHost registration:&lt;br&gt;
kotlincomposable(&lt;br&gt;
    route = Screen.ProductDetail.route,&lt;br&gt;
    arguments = listOf(&lt;br&gt;
        navArgument("productId") { type = NavType.IntType }&lt;br&gt;
    )&lt;br&gt;
) { backStackEntry -&amp;gt;&lt;br&gt;
    val productId = backStackEntry.arguments?.getInt("productId") ?: 0&lt;br&gt;
    ProductDetailScreen(productId = productId, navController = navController)&lt;br&gt;
}&lt;br&gt;
The navArgument block isn't just decoration. It tells the system the type of the argument. This matters because the URL is a string — "product_detail/1" — and the system needs to know to convert "1" into an Int, not leave it as "1".&lt;br&gt;
If you forget the arguments list, or name the argument differently here than in the route template, the system can't match them. The value doesn't get extracted. backStackEntry.arguments?.getInt("productId") returns null. That's the mystery.&lt;br&gt;
The ?: 0 at the end is a fallback — if for any reason the argument is missing, we default to 0 instead of crashing. In a real app, you'd probably show an error state instead, but the pattern is the same.&lt;/p&gt;

&lt;p&gt;Optional Arguments and Default Values&lt;br&gt;
Here's something the documentation doesn't make obvious: arguments can be optional.&lt;br&gt;
By default, a {placeholder} in a route is required. If it's not there, navigation fails. But sometimes you have a screen that works fine without certain data — a search screen that can open with or without an initial query, for example.&lt;br&gt;
In our e-commerce app, let's say the Cart screen can optionally receive a couponCode that pre-fills a discount field:&lt;br&gt;
kotlinobject Cart : Screen("cart?couponCode={couponCode}") {&lt;br&gt;
    fun createRoute(couponCode: String? = null): String {&lt;br&gt;
        return if (couponCode != null) "cart?couponCode=$couponCode"&lt;br&gt;
        else "cart"&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
Two things changed here. First, the syntax: optional arguments use query parameter format — ?key={value} — instead of path format — /{value}. This is how the system knows an argument is optional.&lt;br&gt;
Second, createRoute() now has two forms: one with a coupon code, one without. Both navigate to "the cart screen" — just with different context.&lt;br&gt;
The NavHost registration:&lt;br&gt;
kotlincomposable(&lt;br&gt;
    route = Screen.Cart.route,&lt;br&gt;
    arguments = listOf(&lt;br&gt;
        navArgument("couponCode") {&lt;br&gt;
            type = NavType.StringType&lt;br&gt;
            nullable = true&lt;br&gt;
            defaultValue = null&lt;br&gt;
        }&lt;br&gt;
    )&lt;br&gt;
) { backStackEntry -&amp;gt;&lt;br&gt;
    val couponCode = backStackEntry.arguments?.getString("couponCode")&lt;br&gt;
    CartScreen(couponCode = couponCode, navController = navController)&lt;br&gt;
}&lt;br&gt;
The nullable = true and defaultValue = null are what make this argument optional. Without them, the system would require the argument to be present.&lt;br&gt;
Now you can navigate to the cart two ways:&lt;br&gt;
kotlin// From ProductDetail — no coupon&lt;br&gt;
navController.navigate(Screen.Cart.createRoute())&lt;/p&gt;

&lt;p&gt;// From a promotional banner — with a coupon pre-filled&lt;br&gt;
navController.navigate(Screen.Cart.createRoute(couponCode = "SAVE20"))&lt;br&gt;
Both work. The cart screen handles both cases based on whether couponCode is null or not.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>android</category>
      <category>beginners</category>
      <category>navigation</category>
    </item>
    <item>
      <title>The Junior Dev Who Never Had to Google Anything — Is That a Superpower or a Problem?</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Tue, 09 Jun 2026 17:54:30 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/the-junior-dev-who-never-had-to-google-anything-is-that-a-superpower-or-a-problem-1hf3</link>
      <guid>https://dev.to/itsaalaa7/the-junior-dev-who-never-had-to-google-anything-is-that-a-superpower-or-a-problem-1hf3</guid>
      <description>&lt;p&gt;Let me tell you four things that happened to me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One.&lt;/strong&gt; I sat in a mock interview. The question was basic — embarrassingly basic for someone who'd been coding for months. And I went completely blank. Not because I didn't know the concept. I did. I'd used it dozens of times. But every single time I'd used it, I'd asked AI first. I never had to hold the answer in my own head. So when someone asked me to pull it out of my head on the spot, there was nothing there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two.&lt;/strong&gt; I looked at a file in my own project — code I wrote, in a project I built — and I couldn't explain what one of the functions was doing. I remembered asking AI for it. I remembered copying it. I remembered it working. But the understanding? I never actually got it. I just got the output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three.&lt;/strong&gt; A friend of mine — also a junior — lost internet access for two hours during a coding session. He sent me a message that I still think about: &lt;em&gt;"I don't know how to do anything without it."&lt;/em&gt; He wasn't joking. He sat there until the connection came back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Four.&lt;/strong&gt; And me. All three of those stories are me.&lt;/p&gt;




&lt;p&gt;I want to be honest with you about something that nobody in the dev community is saying loudly enough:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI tools are making some of us faster. And quietly making others of us hollow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the terrifying part is — you can't always tell which one you are until it's too late.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Superpower Feeling Is Real
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend it isn't.&lt;/p&gt;

&lt;p&gt;The first time I used AI to help me with code, it felt like cheating in the best possible way. I described a problem in plain English. It gave me working code in seconds. I integrated it, it worked, I moved on.&lt;/p&gt;

&lt;p&gt;That speed is real. That efficiency is real. If you're a junior dev and you're not using AI tools at all, you're probably moving slower than you need to.&lt;/p&gt;

&lt;p&gt;But here's the thing about superpowers in movies — they always come with a cost the hero doesn't notice until a critical moment. And for a lot of junior devs right now, that cost is silently compounding in the background.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Actually Happening When You Over-Rely on AI
&lt;/h2&gt;

&lt;p&gt;When you ask AI to write your code, explain your error, and structure your logic — every single time, without stopping to struggle first — you're skipping something that has no shortcut.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're skipping the part where your brain builds the map.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Learning to code isn't just about collecting solutions. It's about building a mental model of how things connect. Why this pattern exists. What breaks when you do it differently. Why the error says what it says.&lt;/p&gt;

&lt;p&gt;That mental model only gets built through friction. Through sitting with a problem long enough that your brain has to construct a path. Through being wrong, running it, seeing it fail, and figuring out why.&lt;/p&gt;

&lt;p&gt;When AI removes all the friction, it also removes all the map-building.&lt;/p&gt;

&lt;p&gt;You end up with a project full of code that works. And a brain full of gaps that don't show up until someone asks you to think without a safety net.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Test That Reveals Everything
&lt;/h2&gt;

&lt;p&gt;There's a simple test I think every junior dev should run on themselves right now.&lt;/p&gt;

&lt;p&gt;Close the AI. Close Stack Overflow. Open a blank file.&lt;/p&gt;

&lt;p&gt;Now build the simplest thing you built last week. From memory. Without help.&lt;/p&gt;

&lt;p&gt;Can you do it?&lt;/p&gt;

&lt;p&gt;If the answer is yes — great. You're using AI as a tool and you're still the one doing the thinking.&lt;/p&gt;

&lt;p&gt;If the answer is "I don't even know where to start" — that's not a skill level problem. That's a dependency problem. And it's worth taking seriously.&lt;/p&gt;

&lt;p&gt;I ran this test on myself after that interview. The results were uncomfortable. I could describe what the code should do. I couldn't write it without reaching for help every three minutes.&lt;/p&gt;

&lt;p&gt;That's not a junior dev using AI well. That's a junior dev who learned to prompt AI well. Those are not the same thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Nobody Talks About This
&lt;/h2&gt;

&lt;p&gt;Part of why this conversation doesn't happen enough is that the output looks identical.&lt;/p&gt;

&lt;p&gt;Your code works. Your project runs. Your GitHub is active. From the outside — and honestly, from the inside too — everything looks fine.&lt;/p&gt;

&lt;p&gt;The gap only becomes visible in high-pressure moments. An interview. A pair programming session. A production bug at 2am when the AI gives you three different conflicting answers and you need to actually &lt;em&gt;understand&lt;/em&gt; what's happening to fix it.&lt;/p&gt;

&lt;p&gt;That's when you find out what you actually know versus what you only knew how to ask for.&lt;/p&gt;




&lt;h2&gt;
  
  
  I'm Not Saying Stop Using AI
&lt;/h2&gt;

&lt;p&gt;That would be a ridiculous take and I won't make it.&lt;/p&gt;

&lt;p&gt;AI is here. It's useful. The developers who learn to work &lt;em&gt;with&lt;/em&gt; it effectively will have a real edge. That's just true.&lt;/p&gt;

&lt;p&gt;But there's a version of using AI that makes you better — and a version that quietly replaces your thinking without you noticing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The version that makes you better looks like this:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You try first. You sit with the problem for at least fifteen minutes. You form a hypothesis. You write something, even if it's wrong. &lt;em&gt;Then&lt;/em&gt; you ask AI — not for the answer, but to check your reasoning, fill a specific gap, or explain why your approach broke.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The version that hollows you out looks like this:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You see the problem. You open AI. You paste the output. You move on.&lt;/p&gt;

&lt;p&gt;One of those builds a developer. The other builds someone who can only code when the internet is on.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Uncomfortable Question
&lt;/h2&gt;

&lt;p&gt;If you're a junior dev reading this — and I'm talking to the version of you that's being honest right now, not the version that's performing confidence online — ask yourself this:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;When was the last time you solved something hard completely on your own?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Not with AI. Not with Stack Overflow. Not with a YouTube tutorial open in the background.&lt;/p&gt;

&lt;p&gt;Just you, the problem, and enough patience to sit with the discomfort until something clicked.&lt;/p&gt;

&lt;p&gt;If you can remember that moment clearly, you're probably okay.&lt;/p&gt;

&lt;p&gt;If you're struggling to remember the last time it happened — that's the answer.&lt;/p&gt;




&lt;p&gt;The junior dev who never has to Google anything isn't a 10x developer.&lt;/p&gt;

&lt;p&gt;They might just be someone who got very good at delegating their thinking.&lt;/p&gt;

&lt;p&gt;And that's worth examining — before an interview, a job, or a real production problem does it for you.&lt;/p&gt;




&lt;p&gt;Drop your honest answer in the comments. When was the last time you solved something without AI? I'm genuinely asking — not to judge, because I had to think hard about my own answer too.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>discuss</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AI Is the GPS That Made Me Forget How to Read a Map" — you can still get anywhere, but you couldn't explain how</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Tue, 02 Jun 2026 17:53:24 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/ai-is-the-gps-that-made-me-forget-how-to-read-a-map-you-can-still-get-anywhere-but-you-couldnt-3p0b</link>
      <guid>https://dev.to/itsaalaa7/ai-is-the-gps-that-made-me-forget-how-to-read-a-map-you-can-still-get-anywhere-but-you-couldnt-3p0b</guid>
      <description>&lt;h1&gt;
  
  
  AI Is the GPS That Made Me Forget How to Read a Map
&lt;/h1&gt;

&lt;p&gt;There's a moment I keep having.&lt;/p&gt;

&lt;p&gt;I open a problem. A bug, a blank component, a feature I need to figure out. And before I've even read it fully — before my brain has had five seconds to actually &lt;em&gt;think&lt;/em&gt; — my hand is already moving.&lt;/p&gt;

&lt;p&gt;Tab. New chat. Paste.&lt;/p&gt;

&lt;p&gt;And the worst part? I don't even notice I'm doing it anymore.&lt;/p&gt;

&lt;p&gt;It happened again last week. I was staring at a layout bug. Nothing serious. The kind of thing I've fixed a hundred times. But something felt uncomfortable about just... sitting with it. About not knowing the answer immediately. So I opened the prompt box. Typed the problem. Got the fix.&lt;/p&gt;

&lt;p&gt;It worked. I moved on.&lt;/p&gt;

&lt;p&gt;But later, I kept thinking: did I just solve that? Or did I just &lt;em&gt;collect&lt;/em&gt; the solution?&lt;/p&gt;

&lt;p&gt;Because those two things used to feel the same. Now I'm not sure they are.&lt;/p&gt;




&lt;h2&gt;
  
  
  The GPS Thing
&lt;/h2&gt;

&lt;p&gt;I want to talk about GPS for a second. Because I think it's the most honest comparison I've found for what's happening to us.&lt;/p&gt;

&lt;p&gt;Before GPS, getting somewhere new was an experience. You looked at a map. You planned a route. You made a wrong turn, figured out where you went wrong, corrected it. By the time you arrived, you &lt;em&gt;knew&lt;/em&gt; how you got there. You could describe it. You could do it again without help.&lt;/p&gt;

&lt;p&gt;Now? I can get to almost any address in my city. But if you took my phone away and asked me to explain the route, I genuinely couldn't. I know the destination. I have no idea about the journey.&lt;/p&gt;

&lt;p&gt;GPS didn't make me a worse driver. I'm probably a faster one. But it quietly outsourced the part of navigation that builds spatial memory. And I didn't notice it happening until the moment I needed that memory and it wasn't there.&lt;/p&gt;

&lt;p&gt;AI did the same thing to my thinking. And I let it.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Did the Discomfort Start Feeling Wrong?
&lt;/h2&gt;

&lt;p&gt;Here's what I've been sitting with.&lt;/p&gt;

&lt;p&gt;There used to be a feeling at the start of a hard problem. A kind of productive discomfort. Not panic — just that low hum of &lt;em&gt;I don't know yet, but I'm going to figure this out.&lt;/em&gt; That feeling was the start of actual thinking. Of following a thread. Of making mistakes and learning something real from them.&lt;/p&gt;

&lt;p&gt;I don't feel that as often anymore.&lt;/p&gt;

&lt;p&gt;Now the discomfort arrives and almost immediately I'm looking for somewhere to put it. Somewhere that will take it away fast. And the prompt box is always right there. Patient. Instant. Never annoyed that I asked again.&lt;/p&gt;

&lt;p&gt;The problem isn't that I use AI. The problem is that I've started treating uncertainty like a bug to be fixed instead of a process to be experienced.&lt;/p&gt;

&lt;p&gt;And that shift happened so slowly I didn't see it until I was already on the other side of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part That Actually Scared Me
&lt;/h2&gt;

&lt;p&gt;A few months ago, a friend asked me to explain how something worked. Not to build it. Just to explain it.&lt;/p&gt;

&lt;p&gt;Something I'd used in three projects. Something I'd shipped to production. Something I thought I understood.&lt;/p&gt;

&lt;p&gt;And I opened my mouth, and... the explanation wasn't there. I knew it &lt;em&gt;worked&lt;/em&gt;. I'd seen it work. But I'd never actually built the understanding. I'd just collected outputs and moved on.&lt;/p&gt;

&lt;p&gt;I went home and asked AI to explain it to me. It did. Clearly. In about forty seconds.&lt;/p&gt;

&lt;p&gt;And I thought: I've been using this thing for months, and this is the first time I've actually understood it.&lt;/p&gt;

&lt;p&gt;That was uncomfortable in a different way. Not the productive kind.&lt;/p&gt;




&lt;h2&gt;
  
  
  Are We Using AI, or Just Renting Thinking?
&lt;/h2&gt;

&lt;p&gt;This is the question I keep coming back to.&lt;/p&gt;

&lt;p&gt;Because there are two very different things you can do with AI.&lt;/p&gt;

&lt;p&gt;You can use it as a tool. Something you reach for &lt;em&gt;after&lt;/em&gt; you've thought, when you're stuck, when you need a second opinion, when you want to move faster on something you already understand. That version of AI is genuinely powerful. It amplifies what you already have.&lt;/p&gt;

&lt;p&gt;Or you can use it as a shortcut past the thinking entirely. Skip the discomfort. Go straight to the answer. Never sit with not-knowing long enough for anything to stick.&lt;/p&gt;

&lt;p&gt;That version is seductive because it &lt;em&gt;works&lt;/em&gt;. You still ship. You still get the answer. The output is the same.&lt;/p&gt;

&lt;p&gt;But something is different on the inside. And over time, that difference adds up.&lt;/p&gt;

&lt;p&gt;The GPS gets you to the destination either way. But only one version of the journey leaves you knowing where you are.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Anxiety Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Here's the thing I haven't seen many people admit.&lt;/p&gt;

&lt;p&gt;There's a specific anxiety that comes with not having the prompt box open. A restlessness. A sense of exposure. Like you're working without a safety net and something could go wrong at any moment.&lt;/p&gt;

&lt;p&gt;That feeling is new. I didn't have it three years ago. Back then, not knowing something immediately just meant you didn't know it yet. It was a normal part of thinking.&lt;/p&gt;

&lt;p&gt;Now not-knowing feels like a problem to be solved in seconds. And the seconds are ticking.&lt;/p&gt;

&lt;p&gt;I think that anxiety is worth paying attention to. Not because AI is bad. But because that feeling is information. It's telling you something about what you've quietly outsourced — and whether you're okay with that.&lt;/p&gt;

&lt;p&gt;I'm still figuring out my own answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Question Worth Asking Yourself
&lt;/h2&gt;

&lt;p&gt;Not "should I use AI less?" That's not a useful question. AI isn't going anywhere and neither is its usefulness.&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you close the prompt box and sit with a problem alone — how long before the discomfort becomes unbearable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ten minutes? Two? Thirty seconds?&lt;/p&gt;

&lt;p&gt;Because that window — the time between opening a problem and reaching for AI — that's where your actual thinking lives. That's where you build the spatial memory. That's where the understanding that &lt;em&gt;sticks&lt;/em&gt; gets made.&lt;/p&gt;

&lt;p&gt;If that window is shrinking, it's worth noticing.&lt;/p&gt;

&lt;p&gt;That's all. Just noticing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Things I'm Trying (Not Advice, Just Honest)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I set a "think first" timer.&lt;/strong&gt; Ten minutes. Problem open. No AI. Just me and the discomfort. Sometimes I solve it. Sometimes I don't. But every time, I show up to the AI with a much better question — and I actually understand the answer when it comes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I ask AI to explain, not just solve.&lt;/strong&gt; Instead of "fix this," I've started asking "explain why this is broken." It takes longer. The answer is less clean. But something actually transfers.&lt;/p&gt;

&lt;p&gt;Neither of these is a cure. I still reach for the prompt box too fast sometimes. Probably today.&lt;/p&gt;

&lt;p&gt;But at least now I notice when I do it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;So here's the open question I'll leave you with:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Are you using AI to go further than you could alone? Or are you using it to avoid the part of the journey where the real learning happens?&lt;/p&gt;

&lt;p&gt;Both are honest answers. Neither is comfortable.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drop yours in the comments. I'm genuinely curious which one you'll admit to. 👇&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>That Weird `{ }` After Button? I Had No Idea What I Was Actually Writing{Lambda function}</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Tue, 19 May 2026 13:31:43 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/that-weird-after-button-i-had-no-idea-what-i-was-actually-writinglambda-function-cjp</link>
      <guid>https://dev.to/itsaalaa7/that-weird-after-button-i-had-no-idea-what-i-was-actually-writinglambda-function-cjp</guid>
      <description>&lt;p&gt;I remember the first time I looked at a &lt;code&gt;Button&lt;/code&gt; in Jetpack Compose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Click me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I typed it. It worked. I moved on.&lt;/p&gt;

&lt;p&gt;But I had a question I was too embarrassed to ask out loud: &lt;em&gt;why are there two sets of curly braces here?&lt;/em&gt; What is that second block doing floating there after the closing parenthesis? Is that even valid Kotlin?&lt;/p&gt;

&lt;p&gt;I copy-pasted it from the docs, trusted it, and kept going.&lt;/p&gt;

&lt;p&gt;Then one day I tried to write my own composable that accepted a click listener. I wrote it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;MyButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I called it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;MyButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It worked. But then a teammate showed me I could also call it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;MyButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Submit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I stared at that for a long time. Where did &lt;code&gt;onClick =&lt;/code&gt; go? Why did the lambda just... float outside? Was this some kind of shortcut? Was I missing something?&lt;/p&gt;

&lt;p&gt;That was the day I stopped ignoring lambdas and actually learned them.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, What Even Is a Lambda?
&lt;/h2&gt;

&lt;p&gt;Before Compose, before callbacks — let's go back to basics.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;lambda&lt;/strong&gt; is simply a function that has no name. That's it. Instead of declaring a full function with &lt;code&gt;fun&lt;/code&gt;, you write the logic inline, right where you need it.&lt;/p&gt;

&lt;p&gt;Here's a normal function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello, $name"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the exact same thing as a lambda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;greet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Hello, $name"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same logic. No name declaration. No &lt;code&gt;return&lt;/code&gt; keyword — the last expression is returned automatically. You store it in a variable, or you pass it directly somewhere else.&lt;/p&gt;

&lt;p&gt;That's a lambda.&lt;/p&gt;




&lt;h2&gt;
  
  
  Function Types: What You're Actually Passing Around
&lt;/h2&gt;

&lt;p&gt;When you write &lt;code&gt;() -&amp;gt; Unit&lt;/code&gt;, you're not writing magic. You're writing a &lt;strong&gt;type&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Just like &lt;code&gt;String&lt;/code&gt; is a type, or &lt;code&gt;Int&lt;/code&gt; is a type — &lt;code&gt;() -&amp;gt; Unit&lt;/code&gt; is the type of &lt;em&gt;a function that takes no parameters and returns nothing&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let's break the pattern down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(parameters) -&amp;gt; ReturnType
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;() -&amp;gt; Unit&lt;/code&gt; — takes nothing, returns nothing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(String) -&amp;gt; Unit&lt;/code&gt; — takes a String, returns nothing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(Int) -&amp;gt; Boolean&lt;/code&gt; — takes an Int, returns a Boolean&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(String, Int) -&amp;gt; String&lt;/code&gt; — takes a String and an Int, returns a String&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Compose, you'll see &lt;code&gt;() -&amp;gt; Unit&lt;/code&gt; constantly. Every &lt;code&gt;onClick&lt;/code&gt;, every &lt;code&gt;onDismiss&lt;/code&gt;, every &lt;code&gt;onNavigate&lt;/code&gt; — they all have this type because they just &lt;em&gt;do something&lt;/em&gt; when triggered, they don't return a value.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Trailing Lambda — The Thing That Confused Me
&lt;/h2&gt;

&lt;p&gt;Here's the rule that explains everything:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In Kotlin, if the &lt;strong&gt;last parameter&lt;/strong&gt; of a function is a lambda, you can move it &lt;strong&gt;outside&lt;/strong&gt; the parentheses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. That's the whole thing.&lt;/p&gt;

&lt;p&gt;So this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Click me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can be written as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Click me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;content&lt;/code&gt; is the last parameter, Kotlin lets you pull it outside. And if there's &lt;strong&gt;only one&lt;/strong&gt; lambda parameter and it's the last one, you can even drop the parentheses entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is called a &lt;strong&gt;trailing lambda&lt;/strong&gt;. It's not a different feature — it's just syntax sugar that Kotlin gives you for cleaner-looking code. And once you know it exists, you'll see it absolutely everywhere in Compose.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lambdas as Event Callbacks: Starting Simple
&lt;/h2&gt;

&lt;p&gt;Now let's see this in a real Compose context.&lt;/p&gt;

&lt;p&gt;The simplest example is &lt;code&gt;Button&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;MyScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TAG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Button clicked!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Click me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;onClick&lt;/code&gt; parameter has the type &lt;code&gt;() -&amp;gt; Unit&lt;/code&gt;. You're passing a lambda — an anonymous function — directly inline. When the button is tapped, Compose calls your lambda.&lt;/p&gt;

&lt;p&gt;Now what if you want to do something more meaningful when the button is clicked — like navigate or update state? You lift the logic out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;MyScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onNavigate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;onNavigate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Go to next screen"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the screen doesn't decide what happens on click. It just says "when clicked, call whatever you gave me." The caller decides the behavior. That's the entire power of passing functions as parameters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Leveling Up: Custom Composables with Callbacks
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting.&lt;/p&gt;

&lt;p&gt;Imagine you're building a list of items. Each item has a name, and when the user taps one, you want to do something with it. Here's how that composable might look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;PokemonItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;onItemClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clickable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;onItemClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;onItemClick: (String) -&amp;gt; Unit&lt;/code&gt;. This lambda takes a &lt;code&gt;String&lt;/code&gt; and returns nothing. When the card is tapped, it calls the lambda with the Pokémon's name.&lt;/p&gt;

&lt;p&gt;Now the screen that uses this composable decides what to do with that name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;PokemonListScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onNavigateToDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemonList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pikachu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Charizard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bulbasaur"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nc"&gt;LazyColumn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pokemonList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pokemon&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nc"&gt;PokemonItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;onItemClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;selectedName&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="nf"&gt;onNavigateToDetail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or using trailing lambda syntax, even shorter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;PokemonItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;selectedName&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;onNavigateToDetail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same result. The &lt;code&gt;PokemonItem&lt;/code&gt; composable knows nothing about navigation. It just knows it should call &lt;code&gt;onItemClick&lt;/code&gt; when tapped. What happens next is someone else's responsibility.&lt;/p&gt;

&lt;p&gt;That separation is not just clean — it makes your composables &lt;strong&gt;reusable anywhere&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  One More Thing: Lambdas and Recomposition
&lt;/h2&gt;

&lt;p&gt;Here's something most beginner articles skip — and it actually matters.&lt;/p&gt;

&lt;p&gt;Every time Compose recomposes a screen, it re-executes the composable function. That means every lambda you write inline gets &lt;strong&gt;recreated&lt;/strong&gt; on every recomposition.&lt;/p&gt;

&lt;p&gt;For most cases, this is totally fine. But once you start passing lambdas deep into a component tree, or using them inside performance-sensitive composables, this can cause unnecessary recompositions in child components.&lt;/p&gt;

&lt;p&gt;The solution Compose gives you is &lt;code&gt;remember&lt;/code&gt; with lambdas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;onClickAction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* do something */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or for lambdas that depend on state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;onItemClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onPokemonSelected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Compose: "don't recreate this lambda unless &lt;code&gt;viewModel&lt;/code&gt; changes."&lt;/p&gt;

&lt;p&gt;You don't need to do this everywhere from day one. But knowing it exists means when you eventually run into a recomposition issue, you'll know where to look.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model That Tied It All Together
&lt;/h2&gt;

&lt;p&gt;When I finally understood lambdas in Compose, I stopped seeing them as confusing syntax and started seeing them as &lt;strong&gt;the language of events&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every tap, every input change, every navigation action — they're all just functions being passed around. Your composables don't handle what happens. They just say: "when &lt;em&gt;this&lt;/em&gt; occurs, call &lt;em&gt;whatever you gave me&lt;/em&gt;."&lt;/p&gt;

&lt;p&gt;That's the real reason Compose is designed this way. It keeps each composable focused on one job: rendering. The rest is handled by whoever calls it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What It Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lambda&lt;/td&gt;
&lt;td&gt;A function with no name, written inline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;() -&amp;gt; Unit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A function type: takes nothing, returns nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(String) -&amp;gt; Unit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A function type: takes a String, returns nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trailing lambda&lt;/td&gt;
&lt;td&gt;When the last param is a lambda, move it outside &lt;code&gt;()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Callback pattern&lt;/td&gt;
&lt;td&gt;Passing lambdas down so composables can report events up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remember { lambda }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prevents unnecessary recomposition from lambda recreation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;If any part of this clicked for you, or if you've been silently confused about the same thing — drop a comment. I'm still learning too, and honestly, the best thing about writing these articles is finding out I wasn't the only one who didn't get it at first.&lt;/p&gt;

</description>
      <category>android</category>
      <category>beginners</category>
      <category>kotlin</category>
      <category>mobile</category>
    </item>
    <item>
      <title>I Had No Idea How My Screens Were Talking to Each Other. Then I Learned Compose Navigation.</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Mon, 18 May 2026 14:49:52 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/i-had-no-idea-how-my-screens-were-talking-to-each-other-then-i-learned-compose-navigation-300a</link>
      <guid>https://dev.to/itsaalaa7/i-had-no-idea-how-my-screens-were-talking-to-each-other-then-i-learned-compose-navigation-300a</guid>
      <description>&lt;p&gt;Let me be honest with you.&lt;/p&gt;

&lt;p&gt;The first time I tried to navigate between two screens in Jetpack Compose, I copy-pasted code from Claude, it worked, and I moved on. I had zero idea what &lt;code&gt;NavHost&lt;/code&gt;, &lt;code&gt;NavController&lt;/code&gt;, or &lt;code&gt;BackStack&lt;/code&gt; actually meant.&lt;/p&gt;

&lt;p&gt;Then my app had 5 screens. Then 8. Then I needed to pass data between them. Then everything broke, and I finally had to sit down and &lt;em&gt;actually&lt;/em&gt; learn this.&lt;/p&gt;

&lt;p&gt;This article is what I wish someone had explained to me back then.&lt;/p&gt;

&lt;p&gt;We're building a simple e-commerce app — &lt;strong&gt;Product List → Product Detail → Cart&lt;/strong&gt; — and we're going to understand every single line of navigation code we write. No copy-pasting in the dark this time.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, What Does "Navigation" Even Mean?
&lt;/h2&gt;

&lt;p&gt;Before we write a single line of code, let's make sure we actually understand what's happening.&lt;/p&gt;

&lt;p&gt;Imagine your phone screen is a stack of physical cards on a table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You open the app → the first card (Product List) is placed on the table.&lt;/li&gt;
&lt;li&gt;You tap a product → a new card (Product Detail) is placed &lt;em&gt;on top&lt;/em&gt; of the first one.&lt;/li&gt;
&lt;li&gt;You press back → that top card is &lt;em&gt;removed&lt;/em&gt;, and you see the Product List again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That pile of cards? That's your &lt;strong&gt;Back Stack&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Android manages this stack automatically. Your job is just to tell it: &lt;em&gt;"add this screen"&lt;/em&gt; or &lt;em&gt;"remove the top one."&lt;/em&gt; That's navigation.&lt;/p&gt;

&lt;p&gt;Simple. Let's build it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Setup
&lt;/h2&gt;

&lt;p&gt;Add this dependency to your &lt;code&gt;build.gradle.kts&lt;/code&gt; (app level):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.navigation:navigation-compose:2.9.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sync and you're ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  Meet the 3 Players
&lt;/h2&gt;

&lt;p&gt;Compose Navigation has three key pieces. Think of them like a team with very specific roles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Who&lt;/th&gt;
&lt;th&gt;What They Do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NavController&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;pilot&lt;/strong&gt; — decides where you go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NavHost&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;airport&lt;/strong&gt; — the container that holds all destinations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;composable()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;gate&lt;/strong&gt; — registers each screen as a destination&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's see them in action.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;rememberNavController()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This creates your NavController and keeps it alive across recompositions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberNavController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of this as creating the remote control for your whole app's navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;NavHost&lt;/code&gt; + &lt;code&gt;composable()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is where you declare every screen your app has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;NavHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;startDestination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"product_list"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"product_list"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;ProductListScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"product_detail"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;ProductDetailScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cart"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;CartScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;startDestination&lt;/code&gt; is just the screen that shows up first when the app launches. Everything else gets added when the user navigates there.&lt;/p&gt;

&lt;p&gt;That's the whole system. Now let's make it production-quality.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stop Hardcoding Strings — Use a Sealed Class
&lt;/h2&gt;

&lt;p&gt;Right now, our routes are raw strings like &lt;code&gt;"product_list"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the problem: you'll use that string in multiple places — the NavHost, the navigate call, the popBackStack call... One typo anywhere? App crashes at runtime with zero help from the compiler.&lt;/p&gt;

&lt;p&gt;The fix is a sealed class that acts as a single source of truth for all your routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ProductList&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"product_list"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Cart&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cart"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Screens with arguments get their own createRoute() function&lt;/span&gt;
    &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ProductDetail&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"product_detail/{productId}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"product_detail/$productId"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if you ever rename a screen, you change it in &lt;strong&gt;one place&lt;/strong&gt; and every reference updates automatically. The compiler is your safety net.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting Up the Full NavHost
&lt;/h2&gt;

&lt;p&gt;With the sealed class in place, your NavHost becomes clean and readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;AppNavigation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberNavController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nc"&gt;NavHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;startDestination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProductList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProductList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;ProductListScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProductDetail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;navArgument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"productId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NavType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IntType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;backStackEntry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backStackEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"productId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="nc"&gt;ProductDetailScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;CartScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how &lt;code&gt;ProductDetail&lt;/code&gt; declares an &lt;code&gt;arguments&lt;/code&gt; list — that's how you tell the NavHost &lt;em&gt;"hey, this screen expects some data to arrive with it."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Navigating to a Screen
&lt;/h2&gt;

&lt;p&gt;Now let's make the user actually &lt;em&gt;go&lt;/em&gt; somewhere. When a product is tapped in the list, we navigate to its detail screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProductListScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NavController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Air Max 90"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;120.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Jordan 1 Retro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;180.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"New Balance 550"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;95.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nc"&gt;LazyColumn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nc"&gt;ProductItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// 👇 This is all navigation is — "go here"&lt;/span&gt;
                    &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProductDetail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;createRoute(product.id)&lt;/code&gt; produces &lt;code&gt;"product_detail/1"&lt;/code&gt; — it fills in the argument in the route template. Clean, no string manipulation in your UI code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Passing Data: The Simple Way (ID)
&lt;/h2&gt;

&lt;p&gt;The golden rule of navigation: &lt;strong&gt;pass the minimum data needed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually that's just an ID. The receiving screen fetches the full data using that ID (from a ViewModel, a repository, wherever). This is the officially recommended approach.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ProductDetailScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NavController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProductViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hiltViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fetch the full product using the ID&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;product&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"Loading..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typography&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headlineMedium&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"$${product?.price}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nc"&gt;Spacer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Add to Cart 🛒"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ID goes in → ViewModel fetches the product → UI shows it. Simple and predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going Back
&lt;/h2&gt;

&lt;p&gt;Three flavors of going back, depending on what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Go back one screen — the most common case&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popBackStack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Go back to a specific screen (keeps it in the stack)&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popBackStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProductList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;inclusive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;  &lt;span class="c1"&gt;// false = keep ProductList on the stack, land on it&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Go back and clear everything including the destination&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popBackStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProductList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;inclusive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;// true = remove ProductList too (useful for login → home flows)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;inclusive&lt;/code&gt; flag is the one that trips everyone up. A simple way to remember it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;inclusive = false&lt;/code&gt; → &lt;em&gt;"go back TO this screen"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;inclusive = true&lt;/code&gt; → &lt;em&gt;"go back PAST this screen"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧩 The Complete Picture
&lt;/h2&gt;

&lt;p&gt;Let's look at everything together. Here's your final &lt;code&gt;AppNavigation.kt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;AppNavigation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberNavController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nc"&gt;NavHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;startDestination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProductList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProductList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;ProductListScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProductDetail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;navArgument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"productId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NavType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IntType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;backStackEntry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backStackEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"productId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="nc"&gt;ProductDetailScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;CartScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And your sealed class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ProductList&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"product_list"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Cart&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cart"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ProductDetail&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"product_detail/{productId}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"product_detail/$productId"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a complete, production-ready navigation setup. No magic. No mystery.&lt;/p&gt;




&lt;p&gt;The next time someone mentions the Back Stack, NavHost, or sealed class routes — you'll know exactly what they mean and why it's set up that way.&lt;/p&gt;

&lt;p&gt;What are you building with Compose Navigation? Drop it in the comments 👇&lt;/p&gt;

&lt;p&gt;And if this saved you from a Claude rabbit hole, share it with the Android dev who needs it right now. 🙌&lt;/p&gt;

</description>
      <category>androiddev</category>
      <category>kotlin</category>
      <category>navigation</category>
    </item>
    <item>
      <title>How Sealed Classes Make Navigation Safer in Jetpack Compose</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Tue, 12 May 2026 15:44:45 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/how-sealed-classes-make-navigation-safer-in-jetpack-compose-1pdd</link>
      <guid>https://dev.to/itsaalaa7/how-sealed-classes-make-navigation-safer-in-jetpack-compose-1pdd</guid>
      <description>&lt;p&gt;I spent forty minutes debugging a screen that refused to open.&lt;/p&gt;

&lt;p&gt;The app compiled fine. No red errors anywhere. I'd tap the button, nothing would happen — no crash, no error, just silence. I checked the click listener. I checked the composable. I checked the NavHost. Everything looked right.&lt;/p&gt;

&lt;p&gt;Then I found it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Where I registered the screen&lt;/span&gt;
&lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemonDetails"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Where I was navigating to&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemondetails"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One lowercase letter. &lt;code&gt;Details&lt;/code&gt; vs &lt;code&gt;details&lt;/code&gt;. Forty minutes.&lt;/p&gt;

&lt;p&gt;And the worst part? The compiler had no idea. It saw two strings and had nothing to say about it. From Kotlin's perspective, both lines were perfectly valid code.&lt;/p&gt;

&lt;p&gt;That was the day I stopped trusting navigation strings — and started learning about sealed classes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Way Everyone Starts: Raw Strings
&lt;/h2&gt;

&lt;p&gt;When you first build navigation in Jetpack Compose, this feels completely natural:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;NavHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;startDestination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"home"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"home"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;HomeScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;SettingsScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And navigating around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works. For a small app with three screens, it's fine. You can see all the strings at a glance, they're short, and refactoring is easy enough.&lt;/p&gt;

&lt;p&gt;But this approach has a silent flaw built into it from day one: &lt;strong&gt;Kotlin has no idea what "profile" means&lt;/strong&gt;. To the compiler, it's just a String. It could be anything. A typo, a different capitalization, a slightly different spelling — the code still compiles and the screen just silently fails to open.&lt;/p&gt;

&lt;p&gt;You won't find out until runtime. Sometimes not until a user reports it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The First Improvement: At Least Use Constants
&lt;/h2&gt;

&lt;p&gt;The first instinct most developers have is to pull the strings into constants. It's the right instinct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Routes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;HOME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"home"&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;PROFILE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"profile"&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;SETTINGS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"settings"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the NavHost looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;NavHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;startDestination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HOME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HOME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;HomeScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PROFILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SETTINGS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;SettingsScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And navigation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PROFILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is already better. Typos in the string value only need to be fixed in one place. Autocomplete helps. Renaming a route is a single change instead of a search-and-replace across the whole project.&lt;/p&gt;

&lt;p&gt;But this still has a real limitation — one that only shows up once your app starts to grow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Constants Start Falling Apart: Arguments
&lt;/h2&gt;

&lt;p&gt;Everything feels manageable until screens need to pass data to each other.&lt;/p&gt;

&lt;p&gt;Say you're building a Pokédex app. You have a list screen and a detail screen. The detail screen needs to know &lt;em&gt;which&lt;/em&gt; Pokémon to show. In Compose Navigation, that means route arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/{pokemonName}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;backStackEntry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backStackEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemonName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;PokemonDetailScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And navigating to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/pikachu"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/charizard"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/$selectedPokemon"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine this pattern across ten screens. Profile with a userId. Post with a postId. Settings with a section parameter. You're manually building strings everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"profile/$userId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"post/$postId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"settings/$section"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of these is a hand-rolled string. Every one of them can be wrong in ways the compiler will never catch. Forget the slash, get the argument name slightly wrong, pass the wrong variable — silent failure every time.&lt;/p&gt;

&lt;p&gt;This is the moment where raw strings, even organized into constants, start genuinely hurting you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Shift in Thinking: Screens Are a Known, Finite Set
&lt;/h2&gt;

&lt;p&gt;Here's the idea that made sealed classes click for me.&lt;/p&gt;

&lt;p&gt;Your app has a specific, known list of screens. It's not infinite. It's not dynamic. At any point in time, the compiler &lt;em&gt;could&lt;/em&gt; know every valid destination — if you gave it a way to represent them.&lt;/p&gt;

&lt;p&gt;That's exactly what a sealed class does.&lt;/p&gt;

&lt;p&gt;A sealed class says: &lt;strong&gt;"only these specific types can exist."&lt;/strong&gt; No others. The compiler enforces it.&lt;/p&gt;

&lt;p&gt;Instead of navigation being a world of arbitrary strings where anything goes, it becomes a closed, controlled system where every valid destination is explicitly defined in one place.&lt;/p&gt;

&lt;p&gt;That's the whole idea. Not syntax. Not Kotlin trivia. A navigation architecture that makes invalid destinations impossible to express.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the Screen Hierarchy
&lt;/h2&gt;

&lt;p&gt;Here's what that looks like in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"home"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Settings&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemonName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/{pokemonName}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createRoute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"details/$pokemonName"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's slow down and read this carefully, because every word is doing something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;sealed class Screen(val route: String)&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
This is the parent. Every screen in your app is a &lt;code&gt;Screen&lt;/code&gt;. The &lt;code&gt;route&lt;/code&gt; property is what gets registered with the NavHost. Because it's a &lt;code&gt;sealed class&lt;/code&gt;, nothing outside this file can create a new subtype — the set of screens is locked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;data object Home : Screen("home")&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;Home&lt;/code&gt; is a screen. It inherits from &lt;code&gt;Screen&lt;/code&gt;, which means it automatically has a &lt;code&gt;route&lt;/code&gt; property — and its value is &lt;code&gt;"home"&lt;/code&gt;. &lt;code&gt;data object&lt;/code&gt; means it's a singleton. There's only ever one &lt;code&gt;Home&lt;/code&gt;. It doesn't need arguments — it's just a destination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;data class Details(...) : Screen("details/{pokemonName}")&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;Details&lt;/code&gt; is also a screen, but it needs data. It takes a &lt;code&gt;pokemonName&lt;/code&gt; and defines a &lt;code&gt;createRoute()&lt;/code&gt; function that builds the actual navigation string safely — in one place, with the correct format, every time.&lt;/p&gt;

&lt;p&gt;The visual hierarchy looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Screen
 ├── Home          (no arguments — just a destination)
 ├── Profile       (no arguments)
 ├── Settings      (no arguments)
 └── Details       (needs a pokemonName)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of them belong to the same family. All of them have a &lt;code&gt;route&lt;/code&gt;. None of them can be misspelled into existence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using It in a Real Compose NavHost
&lt;/h2&gt;

&lt;p&gt;Here's the NavHost with sealed classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;AppNavigation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberNavController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nc"&gt;NavHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;startDestination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;HomeScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;onPokemonClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;createRoute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;SettingsScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// "details/{pokemonName}"&lt;/span&gt;
            &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;navArgument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemonName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NavType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringType&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;backStackEntry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backStackEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemonName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
            &lt;span class="nc"&gt;PokemonDetailScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pokemonName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at what's changed. There's not a single hardcoded string anywhere in this navigation setup. Every route comes from a &lt;code&gt;Screen&lt;/code&gt; object. And when navigating to the detail screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before — fragile, easy to get wrong&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/pikachu"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// After — safe, readable, impossible to misspell&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pikachu"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;createRoute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you rename the screen, you change it in one place — the &lt;code&gt;sealed class&lt;/code&gt;. Everywhere that uses &lt;code&gt;Screen.Details&lt;/code&gt; just works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why &lt;code&gt;createRoute()&lt;/code&gt; Is More Important Than It Looks
&lt;/h2&gt;

&lt;p&gt;It's easy to look at &lt;code&gt;createRoute()&lt;/code&gt; and think it's a tiny convenience method. It's not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemonName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/{pokemonName}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createRoute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"details/$pokemonName"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before this existed, every screen that navigated to &lt;code&gt;Details&lt;/code&gt; was responsible for building the string correctly. Every single one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In HomeScreen&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/$pokemonName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// In SearchScreen&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/$selectedPokemon"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// In FavoritesScreen&lt;/span&gt;
&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/$favoriteName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's three places that need to agree on the exact format of the route. Change the argument name, or add a second argument, and you're hunting through every screen that navigates to &lt;code&gt;Details&lt;/code&gt; to update them all.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;createRoute()&lt;/code&gt;, there's exactly one place where the route for &lt;code&gt;Details&lt;/code&gt; is built. Every screen calls the same function. Change the format once, it's fixed everywhere. That's encapsulation — and it's the real reason sealed classes start feeling powerful instead of just "more organized."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Clean Setup (Copy-Friendly)
&lt;/h2&gt;

&lt;p&gt;Here's everything together — sealed classes, NavHost, and a full navigation example using the latest versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle.kts&lt;/span&gt;
&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;composeBom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.compose:compose-bom:2026.04.01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;composeBom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.compose.material3:material3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.navigation:navigation-compose:2.9.8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Screen.kt&lt;/span&gt;
&lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"home"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Settings&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pokemonName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"details/{pokemonName}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createRoute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"details/$pokemonName"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// NavGraph.kt&lt;/span&gt;
&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;AppNavigation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberNavController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nc"&gt;NavHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;startDestination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;HomeScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;onNavigateToProfile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;onPokemonClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;createRoute&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;ProfileScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;SettingsScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;navArgument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemonName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NavType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringType&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;backStackEntry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backStackEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pokemonName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
            &lt;span class="nc"&gt;PokemonDetailScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pokemonName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>navigation</category>
      <category>sealedclasses</category>
      <category>kotlin</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Plugins</title>
      <dc:creator>Aalaa Fahiem </dc:creator>
      <pubDate>Sat, 25 Apr 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/itsaalaa7/plugins-5dnf</link>
      <guid>https://dev.to/itsaalaa7/plugins-5dnf</guid>
      <description>&lt;p&gt;Every Android project I've ever opened starts the same way. Right at the top of &lt;code&gt;build.gradle&lt;/code&gt;, before any dependencies, before any SDK versions, before anything useful — there's this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"android"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I copy-pasted those two lines into every project I created for months. I never touched them. I didn't know what they did. I was honestly scared that if I looked at them too hard something would break.&lt;/p&gt;

&lt;p&gt;And then one day I created a new project and only saw &lt;em&gt;one&lt;/em&gt; of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;kotlin("android")&lt;/code&gt; line was gone. Just... missing. I panicked. Googled it. Added it back. The build failed. Removed it. The build worked again.&lt;/p&gt;

&lt;p&gt;That was the moment I realized I had absolutely no idea what a Gradle plugin even was.&lt;/p&gt;

&lt;p&gt;If you're in the same spot — let's fix that right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  So What Is a Gradle Plugin, Actually?
&lt;/h2&gt;

&lt;p&gt;Here's the simplest mental model I could come up with:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gradle, on its own, knows how to do general build stuff.&lt;/strong&gt; Compile files. Run tasks. Manage dependencies. It's a general-purpose build tool.&lt;/p&gt;

&lt;p&gt;But Gradle has no idea what an Android app is. It doesn't know what an APK is. It doesn't know about AndroidManifest, or resources, or how to sign an app for the Play Store. Out of the box, it just doesn't speak Android.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;plugin&lt;/strong&gt; is what teaches Gradle a new language.&lt;/p&gt;

&lt;p&gt;When you apply &lt;code&gt;com.android.application&lt;/code&gt;, you're saying: &lt;em&gt;"hey Gradle, load the Android plugin — the one that knows how to build Android apps."&lt;/em&gt; Suddenly Gradle understands &lt;code&gt;android { }&lt;/code&gt; blocks, knows how to produce an APK, understands build variants, knows what a manifest is.&lt;/p&gt;

&lt;p&gt;Without the plugin, the &lt;code&gt;android { }&lt;/code&gt; block in your Gradle file would be gibberish. Gradle would throw an error because it has no idea what that block means.&lt;/p&gt;

&lt;p&gt;Think of it like this: Gradle is a blank canvas. Plugins are the brushes. You can't paint without picking up a brush first.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Two Plugins Every Android App Uses (Or Used To)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;com.android.application&lt;/code&gt; — The Android Brush
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the core Android plugin. It's what makes your module an &lt;em&gt;app&lt;/em&gt; rather than just a bunch of Kotlin files. It adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ability to produce an APK or App Bundle&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;android { }&lt;/code&gt; configuration block&lt;/li&gt;
&lt;li&gt;Build types (debug / release)&lt;/li&gt;
&lt;li&gt;Support for Android resources, manifest merging, signing&lt;/li&gt;
&lt;li&gt;Every Android-specific Gradle task you've ever seen in the Build menu&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This one is non-negotiable. Every Android app module has it. Without it, Gradle doesn't know it's building an Android app at all.&lt;/p&gt;

&lt;p&gt;There's also a close cousin: &lt;code&gt;com.android.library&lt;/code&gt;. Same idea, but for modules that produce a &lt;code&gt;.aar&lt;/code&gt; library instead of an installable app. If you ever split your project into multiple modules, you'll meet it.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;kotlin("android")&lt;/code&gt; — The Kotlin Brush (That You Might Not Need Anymore)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"android"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// this is shorthand for:&lt;/span&gt;
&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.jetbrains.kotlin.android"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one used to teach Gradle how to compile Kotlin code for Android. Without it, Gradle only understood Java. Add it, and suddenly &lt;code&gt;.kt&lt;/code&gt; files become valid source code it knows how to build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the twist — and why I panicked when I saw it missing:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As of Android Gradle Plugin &lt;strong&gt;9.0&lt;/strong&gt; (released January 2026), Kotlin support is now &lt;em&gt;built directly into AGP itself&lt;/em&gt;. You no longer need to apply &lt;code&gt;kotlin("android")&lt;/code&gt; separately. AGP already speaks Kotlin fluently.&lt;/p&gt;

&lt;p&gt;So if you're starting a new project today on AGP &lt;strong&gt;9.1.1&lt;/strong&gt; (the current latest), your plugins block looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// that's it. kotlin support is already included.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not a mistake. That's not a missing line. That's just how it works now.&lt;/p&gt;

&lt;p&gt;But — and this is important — if you open an older project or follow an older tutorial, you'll still see &lt;code&gt;kotlin("android")&lt;/code&gt; there. It still works. AGP 9.x accepts it for backwards compatibility. It just isn't required anymore.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before vs After: What This Actually Looks Like
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ The old way — AGP 8.x and older tutorials
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle.kts (Project level)&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"8.2.0"&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.jetbrains.kotlin.android"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"1.9.0"&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle.kts (Module: app)&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.jetbrains.kotlin.android"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// you had to add this yourself&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;android&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;compileSdk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="nf"&gt;kotlinOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;jvmTarget&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"11"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two plugins. Two separate version declarations at the project level. Both needed.&lt;/p&gt;




&lt;h3&gt;
  
  
  ✅ The new way — AGP 9.x (2026)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle.kts (Project level)&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"9.1.1"&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="c1"&gt;// no kotlin plugin needed here anymore&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle.kts (Module: app)&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// kotlin support is built in&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;android&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;compileSdk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;

    &lt;span class="nf"&gt;defaultConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;applicationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.example.myapp"&lt;/span&gt;
        &lt;span class="n"&gt;minSdk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
        &lt;span class="n"&gt;targetSdk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;
        &lt;span class="n"&gt;versionCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;versionName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;compileOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sourceCompatibility&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JavaVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION_11&lt;/span&gt;
        &lt;span class="n"&gt;targetCompatibility&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JavaVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION_11&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// kotlinOptions block is also optional now in AGP 9.x&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;composeBom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.compose:compose-bom:2026.03.00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;composeBom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.compose.ui:ui"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.compose.material3:material3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.activity:activity-compose:1.10.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.core:core-ktx:1.16.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cleaner. Fewer moving parts. Less to break.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part Nobody Explains: &lt;code&gt;apply false&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You've probably noticed this in the project-level &lt;code&gt;build.gradle&lt;/code&gt; and wondered what it means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Project-level&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"9.1.1"&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;apply false&lt;/code&gt; looks backwards. Why declare a plugin and then say "don't apply it"?&lt;/p&gt;

&lt;p&gt;Here's the reason: the project-level file is a &lt;em&gt;declaration hub&lt;/em&gt;, not an activation point. It's saying: &lt;em&gt;"this plugin exists at version 9.1.1 — I'm registering it for the whole project."&lt;/em&gt; But it's not turning it on yet.&lt;/p&gt;

&lt;p&gt;The actual activation happens in the module-level file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Module: app&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// no version here &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, every module in your project that needs the plugin pulls the &lt;em&gt;same version&lt;/em&gt; — the one declared at the top. No version drift. No conflicts. One source of truth.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project-level&lt;/strong&gt; → &lt;em&gt;"Here are the available plugins and their versions"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module-level&lt;/strong&gt; → &lt;em&gt;"I want to activate this one"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Question That Unlocks Everything
&lt;/h2&gt;

&lt;p&gt;Whenever you see a plugin you don't recognize, ask one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What does this teach Gradle to do?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;com.android.application&lt;/code&gt; → teaches Gradle to build an Android app &lt;code&gt;com.android.library&lt;/code&gt; → teaches Gradle to build an Android library module &lt;code&gt;kotlin("android")&lt;/code&gt; → used to teach Gradle to compile Kotlin (now built into AGP) &lt;code&gt;com.google.devtools.ksp&lt;/code&gt; → teaches Gradle to run KSP (Kotlin Symbol Processing) for annotation processing &lt;code&gt;com.google.dagger.hilt.android&lt;/code&gt; → teaches Gradle about Hilt's code generation&lt;/p&gt;

&lt;p&gt;Every plugin has a job. It extends what Gradle can do. Once you ask that one question about any plugin you encounter, it stops being magic and starts being a tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;Plugins are just extensions. They're not magic, they're not scary, and they're not lines you paste in and pretend don't exist.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;com.android.application&lt;/code&gt; is the single most important line in your entire project. Without it, Gradle doesn't know it's building an Android app. Everything else in your &lt;code&gt;build.gradle&lt;/code&gt; — the SDK versions, the dependencies, the build types — only makes sense &lt;em&gt;because&lt;/em&gt; that plugin was applied first.&lt;/p&gt;

&lt;p&gt;Now when you see that &lt;code&gt;plugins { }&lt;/code&gt; block at the top of a file, you won't just scroll past it. You'll know exactly what each line is doing there.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>beginners</category>
      <category>gradle</category>
    </item>
  </channel>
</rss>
