<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[mohamedebrahim96]]></title><description><![CDATA[I'm a software engineer who is passionate about building android applications with Kotlin and Java.
Creating technology to elevate people, and building communit]]></description><link>https://mohamedebrahim96.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1664052499997/sDBntLGuD.png</url><title>mohamedebrahim96</title><link>https://mohamedebrahim96.com</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 06 Oct 2026 19:15:59 GMT</lastBuildDate><atom:link href="https://mohamedebrahim96.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Google Just Quietly Changed How Android Open Source Works , And Almost Nobody Noticed]]></title><description><![CDATA[Sometime in the past few months, a small banner appeared at the top of source.android.com. No press release, no Google I/O keynote slide, no tweet thread. Just a few sentences sitting above the fold o]]></description><link>https://mohamedebrahim96.com/google-just-quietly-changed-how-android-open-source-works-and-almost-nobody-noticed</link><guid isPermaLink="true">https://mohamedebrahim96.com/google-just-quietly-changed-how-android-open-source-works-and-almost-nobody-noticed</guid><dc:creator><![CDATA[Mohamed Ebrahim]]></dc:creator><pubDate>Fri, 25 Sep 2026 02:34:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/62ae3d450924134a80488692/39e781b6-b4c1-4000-8596-b84c22a56d60.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sometime in the past few months, a small banner appeared at the top of source.android.com. No press release, no Google I/O keynote slide, no tweet thread. Just a few sentences sitting above the fold on a documentation site most people never visit unless they're building a custom ROM or bringing up a new device.</p>
<p>Here's what it says, in plain terms: starting in 2026, Google will only publish AOSP source code twice a year — in Q2 and Q4. And developers pulling the platform source should stop using the branch they've relied on for over a decade.</p>
<p>If you've spent any real time inside AOSP, this is a bigger deal than it looks.</p>
<h2>What Actually Changed</h2>
<p>Two concrete things:</p>
<p><strong>1. Source drops are now biannual, not continuous.</strong>
For most of Android's history, <code>aosp-main</code> behaved like a genuinely living branch — Google's internal platform work eventually surfaced there, and anyone syncing the repo could watch the operating system evolve in something close to real time (with obvious gaps for unreleased features). That's over. Public source pushes now happen twice a year, timed to Q2 and Q4.</p>
<p><strong>2. <code>aosp-main</code> is being retired in favor of <code>android-latest-release</code>.</strong>
The new manifest branch doesn't track ongoing development at all — it always points to whatever the most recent official release is (something like an <code>android16-qprX-release</code> tag), full stop. If you're still writing setup docs or CI configs that reference <code>aosp-main</code>, they're now pointing at a branch Google is walking away from.</p>
<pre><code class="language-bash"># The old way (deprecated going forward)
repo init -u https://android.googlesource.com/platform/manifest -b aosp-main
 
# What Google now recommends
repo init -u https://android.googlesource.com/platform/manifest -b android-latest-release
</code></pre>
<p>One thing that <em>isn't</em> changing: monthly security patches. Those keep shipping on their existing <code>android-security</code> branches, on the existing monthly cadence, independent of the new biannual source releases.</p>
<h2>This Didn't Come Out of Nowhere</h2>
<p>If you've only skimmed Android news, this looks like a random infrastructure tweak. If you've been paying attention to Android platform engineering over the last two years, it's the next step in a pattern:</p>
<ul>
<li>Google has been rolling out <strong>Trunk Stable</strong>, an internal development model that replaces long-lived feature branches with everything landing directly in one internal main branch, gated by feature flags instead of branch merges. It's part of why Android 16 shipped ahead of Android's usual yearly cadence.</li>
<li>Around 2025, Google stopped folding Pixel-specific changes into AOSP altogether, shifting toward developing new releases entirely inside closed internal branches before pushing a finished snapshot out.</li>
<li>Components that used to get meaningful public development in the open — the Bluetooth stack, the build system, the update engine, parts of the virtualization framework — have been quietly pulled back behind the same wall.
Put together, the story isn't "AOSP publishes less often." It's "the era of watching Android get built, live, in public, is basically over." What you get now is a finished release, dropped twice a year, rather than a running commit history you can follow.</li>
</ul>
<h2>Why This Actually Matters (Even If You'll Never Clone AOSP)</h2>
<p>Google's own framing is that this is developer infrastructure — nothing a regular Android user will ever notice. That's true for app developers targeting the SDK. It's not true for a decent-sized chunk of the ecosystem:</p>
<ul>
<li><strong>Custom ROM projects</strong> (LineageOS, GrapheneOS, and the dozens of smaller device-specific projects) rebase their work on top of AOSP. Two source drops a year instead of a rolling stream means longer gaps between the moment Google fixes or breaks something and the moment ROM maintainers can even see the diff.</li>
<li><strong>OEMs and chipset vendors</strong> who fork AOSP for their own device lines lose the ability to track platform changes incrementally, which makes it harder to catch integration issues early instead of all at once, twice a year.</li>
<li><strong>Students, researchers, and engineers learning AOSP</strong> — historically one of the best ways to actually understand how Android works under the hood — now learn from a periodic snapshot instead of a living project with visible commit history and rationale.
None of this breaks anything overnight. It just quietly narrows who gets to watch Android being built, and turns AOSP from something closer to "an open project" into something closer to "a release artifact with source code attached."</li>
</ul>
<h2>What To Actually Do About It</h2>
<p>If you touch AOSP in any professional capacity, the action items are small but worth doing now, before your tooling breaks on the next drop:</p>
<ol>
<li><strong>Swap <code>aosp-main</code> for <code>android-latest-release</code></strong> in any repo manifests, CI pipelines, or onboarding docs your team maintains.</li>
<li><strong>Stop expecting incremental platform commits between releases.</strong> Plan integration and testing work around two windows a year (Q2, Q4) instead of continuous syncing.</li>
<li><strong>Keep security patching on its own track</strong> — <code>android-security</code> branches are unaffected, so don't conflate your platform-tracking strategy with your patch strategy.</li>
<li>If you maintain a fork or a custom build, budget more review time right after each release drop — you'll be absorbing roughly six months of platform change in one sync instead of catching it incrementally.</li>
</ol>
<h2>The Bigger Question</h2>
<p>None of this makes Android closed source — the code is still released, still under the same license, still buildable by anyone. But "open source" was never just about the license. Part of what made AOSP genuinely useful to the wider ecosystem was that you could watch it happen — see the commits, understand the reasoning, catch problems early.</p>
<p>That part is what's quietly going away. Google didn't announce it at I/O. It showed up as a banner on a docs site, worded like a routine infrastructure update. Maybe that's exactly what it is. Or maybe it's the clearest signal yet of how much of Android's actual development has already moved behind closed doors — and how comfortable Google now is admitting it in a single paragraph nobody was meant to read twice.</p>
<p><em>Are you affected by this change — building a ROM, maintaining a fork, or just learning AOSP? I'd like to hear how teams are adjusting their workflows for the new release cadence.</em></p>
<hr />
<p><strong>Sources:</strong> <a href="https://source.android.com/">source.android.com</a> · <a href="https://source.android.com/docs/automotive/start/releases">AOSP Automotive release notes</a></p>
]]></content:encoded></item><item><title><![CDATA[Building Jetpack Compose Components the Right Way]]></title><description><![CDATA[If you've spent any time in a Compose codebase past the "hello world" stage, you've probably run into a composable that does everything: fetches data, holds business logic, manages its own state, and ]]></description><link>https://mohamedebrahim96.com/building-jetpack-compose-components-the-right-way</link><guid isPermaLink="true">https://mohamedebrahim96.com/building-jetpack-compose-components-the-right-way</guid><dc:creator><![CDATA[Mohamed Ebrahim]]></dc:creator><pubDate>Fri, 25 Sep 2026 02:15:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/62ae3d450924134a80488692/f7405b82-d9d9-4165-9c78-8b9c9687760a.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you've spent any time in a Compose codebase past the "hello world" stage, you've probably run into a composable that does <em>everything</em>: fetches data, holds business logic, manages its own state, and renders five different UI variations depending on a dozen boolean flags. It compiles. It even works. But nobody wants to touch it, and every new feature request makes it worse.</p>
<p>After years building large, multi-module Android apps, I've found the difference between a Compose codebase that scales and one that turns into spaghetti almost always comes down to a handful of component design habits. Here's what actually holds up in production.</p>
<h2>1. Stateless composables are your default, not your exception</h2>
<p>The single highest-leverage habit in Compose is <strong>state hoisting</strong>: a composable shouldn't own state it doesn't need to own. Push state up to the caller, and let the composable just describe how to render it.</p>
<pre><code class="language-kotlin">// Avoid: the composable owns state it shouldn't
@Composable
fun SearchBar() {
    var query by remember { mutableStateOf("") }
    TextField(value = query, onValueChange = { query = it })
}
 
// Prefer: state is hoisted, composable is stateless
@Composable
fun SearchBar(
    query: String,
    onQueryChange: (String) -&gt; Unit,
    modifier: Modifier = Modifier
) {
    TextField(value = query, onValueChange = onQueryChange, modifier = modifier)
}
</code></pre>
<p>The stateless version is trivially testable, previewable with any input, and reusable anywhere — a settings screen, a dialog, a full-screen search — without dragging along assumptions about where its state lives. Keep a thin stateful wrapper at the screen level if you need one, and let everything below it be dumb and predictable.</p>
<h2>2. Design with slots, not booleans</h2>
<p>A common anti-pattern is a component that grows a new <code>Boolean</code> or nullable parameter every time a designer asks for a variant:</p>
<pre><code class="language-kotlin">@Composable
fun InfoCard(
    title: String,
    showIcon: Boolean,
    iconRes: Int? = null,
    showAction: Boolean = false,
    actionText: String? = null,
    onActionClick: (() -&gt; Unit)? = null
)
</code></pre>
<p>Six months in, this function has fifteen parameters and half of them are mutually exclusive in ways nobody's documented. A <strong>slot API</strong> — composable lambda parameters instead of flags — scales much better:</p>
<pre><code class="language-kotlin">@Composable
fun InfoCard(
    modifier: Modifier = Modifier,
    leadingIcon: @Composable (() -&gt; Unit)? = null,
    title: @Composable () -&gt; Unit,
    trailingAction: @Composable (() -&gt; Unit)? = null
) {
    Card(modifier) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            leadingIcon?.invoke()
            Box(Modifier.weight(1f)) { title() }
            trailingAction?.invoke()
        }
    }
}
</code></pre>
<p>Now callers compose whatever content they need — an icon, a badge, a button, nothing — without the component itself needing to know what "action" or "icon" means. This is the same pattern <code>Scaffold</code>, <code>TopAppBar</code>, and <code>Card</code> use internally, and it's worth copying.</p>
<h2>3. Every composable should accept — and pass through — a <code>Modifier</code></h2>
<p>This one's simple but gets skipped constantly: every public composable should take a <code>Modifier = Modifier</code> parameter, apply it to its outermost layout node, and <em>not</em> have a default that fights the caller (like a hardcoded <code>padding</code> the caller can't remove). Skipping this is the single biggest reason people end up wrapping your component in an extra <code>Box</code> just to add margin.</p>
<pre><code class="language-kotlin">@Composable
fun UserAvatar(
    imageUrl: String,
    modifier: Modifier = Modifier // always present, always applied first
) {
    AsyncImage(
        model = imageUrl,
        contentDescription = null,
        modifier = modifier.clip(CircleShape)
    )
}
</code></pre>
<h2>4. Keep composables small enough to preview meaningfully</h2>
<p>If you can't write a useful <code>@Preview</code> for a composable without mocking half your app's dependencies, it's doing too much. A good litmus test: can you preview it with hardcoded sample data and no ViewModel, repository, or navigation controller in sight?</p>
<pre><code class="language-kotlin">@Preview
@Composable
private fun InfoCardPreview() {
    InfoCard(
        leadingIcon = { Icon(Icons.Default.Info, null) },
        title = { Text("Battery optimization available") },
        trailingAction = { TextButton(onClick = {}) { Text("View") } }
    )
}
</code></pre>
<p>Composables that need real data sources to render are composables that need to be split — pull the data-fetching and business logic up into a stateful wrapper, and leave the presentation piece pure.</p>
<h2>5. Avoid duplicate sources of truth</h2>
<p>A subtle bug pattern: a composable receives a value via parameter, then also keeps a local <code>remember</code> copy of "the same" value, and the two drift out of sync after a recomposition or process death.</p>
<pre><code class="language-kotlin">// Bug waiting to happen
@Composable
fun VolumeSlider(initialVolume: Float) {
    var volume by remember { mutableStateOf(initialVolume) }
    // if initialVolume changes upstream, this composable never finds out
}
</code></pre>
<p>If a value can change from outside, it needs to be a parameter that's read fresh on every recomposition — not seeded into local state once. Reach for <code>remember(key)</code> if you genuinely need to reset local state when an identity changes, but treat that as the exception, not the default.</p>
<h2>6. Let stability do the performance work for you</h2>
<p>Compose skips recomposition for composables whose inputs are stable and unchanged — but only if the compiler can actually prove that. Data classes with <code>var</code> properties, or classes holding <code>List</code>/<code>Map</code> (which the compiler treats as unstable by default), silently opt your composable out of that optimization. In practice this means:</p>
<ul>
<li>Prefer <code>val</code> and immutable collections (<code>ImmutableList</code> from <code>kotlinx.collections.immutable</code>, or plain <code>List</code> treated as read-only by convention) in state holders that back Compose UI.</li>
<li>Run the Compose compiler's stability report during code review on performance-sensitive screens — it's often surprising which classes are silently unstable.</li>
<li>Use <code>@Immutable</code> or <code>@Stable</code> annotations deliberately, not as a blanket fix, once you've confirmed the class actually behaves that way.
None of this matters for a static settings screen. It matters a lot for a list of hundreds of IoT devices updating live sensor readings.</li>
</ul>
<h2>The underlying principle</h2>
<p>Every one of these rules is really one idea wearing different clothes: <strong>a well-designed composable is a pure function of its inputs, and every design decision should make that easier to see, not harder.</strong> Hoist the state, expose slots instead of flags, pass the modifier through, keep the function small enough to preview in isolation. Do that consistently across a codebase and the fifteen-parameter God Composable stops happening — not because anyone bans it, but because the alternative is just easier to write.</p>
]]></content:encoded></item><item><title><![CDATA[A safer way to collect flows from Android UIs]]></title><description><![CDATA[In an Android app, Kotlin flows are typically collected from the UI layer to display data updates on the screen. However, you want to collect these flows making sure you’re not doing more work than necessary, wasting resources (both CPU and memory) o...]]></description><link>https://mohamedebrahim96.com/a-safer-way-to-collect-flows-from-android-uis</link><guid isPermaLink="true">https://mohamedebrahim96.com/a-safer-way-to-collect-flows-from-android-uis</guid><dc:creator><![CDATA[Mohamed Ebrahim]]></dc:creator><pubDate>Sat, 24 Sep 2022 22:07:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1664057086852/JoMiBJCtl.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In an Android app, Kotlin flows are typically collected from the UI layer to display data updates on the screen. However, you want to collect these flows making sure you’re not doing more work than necessary, wasting resources (both CPU and memory) or leaking data when the view goes to the background.</p>
<p>In this article, you’ll learn how the Lifecycle.repeatOnLifecycle, and Flow.flowWithLifecycle APIs protect you from wasting resources and why they’re a good default to use for flow collection in the UI layer.</p>
<p>Wasting resources
It’s recommended to expose the Flow API from lower layers of your app hierarchy regardless of the flow producer implementation details. However, you should also collect them safely.</p>
<p>A cold flow backed by a channel or using operators with buffers such as buffer, conflate, flowOn, or shareIn is not safe to collect with some of the existing APIs such as CoroutineScope.launch, Flow.launchIn, or LifecycleCoroutineScope.launchWhenX, unless you manually cancel the Job that started the coroutine when the activity goes to the background. These APIs will keep the underlying flow producer active while emitting items into the buffer in the background, and thus wasting resources.</p>
<h3 id="heading-note-a-cold-flow-is-a-type-of-flow-that-executes-the-producer-block-of-code-on-demand-when-a-new-subscriber-collects">Note: A cold flow is a type of flow that executes the producer block of code on-demand when a new subscriber collects.</h3>
<pre><code><span class="hljs-comment">// Implementation of a cold flow backed by a Channel that sends Location updates</span>
fun FusedLocationProviderClient.locationFlow() = callbackFlow&lt;Location&gt; {
    val callback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult?) {
            result ?: <span class="hljs-keyword">return</span>
            <span class="hljs-keyword">try</span> { offer(result.lastLocation) } <span class="hljs-keyword">catch</span>(e: Exception) {}
        }
    }
    requestLocationUpdates(createLocationRequest(), callback, Looper.getMainLooper())
        .addOnFailureListener { e -&gt;
            close(e) <span class="hljs-comment">// in case of exception, close the Flow</span>
        }
    <span class="hljs-comment">// clean up when Flow collection ends</span>
    awaitClose {
        removeLocationUpdates(callback)
    }
}
</code></pre><h1 id="heading-visual-diagram">Visual diagram</h1>
<p>Circling back to the beginning, collecting locationFlow directly from a coroutine started with lifecycleScope.launch was dangerous since the collection keeps happening even when the View is in the background.</p>
<p>repeatOnLifecycle prevents you from wasting resources and app crashes because it stops and restarts the flow collection when the lifecycle moves in and out of the target state.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664057213438/TyhdjuIB4.png" alt="1_fmQRBPMPpnO7NAO2bg0GKw.png" /></p>
<p>Comparison with LiveData
You might’ve noticed that this API behaves similarly to LiveData, and that’s true! LiveData is aware of Lifecycle, and its restarting behavior makes it ideal for observing streams of data from the UI. And that’s also the case for the Lifecycle.repeatOnLifecycle, and Flow.flowWithLifecycle APIs!</p>
<p>Collecting flows using these APIs is a natural replacement for LiveData in Kotlin-only apps. If you use these APIs for flow collection, LiveData doesn’t offer any benefits over coroutines and flow. Even more, flows are more flexible since they can be collected from any Dispatcher and they can be powered with all its operators. As opposed to LiveData, which has limited operators available and whose values are always observed from the UI thread.</p>
<p>StateFlow support in data binding
On a different note, one of the reasons you might be using LiveData is because it’s supported by data binding. Well, so is StateFlow! For more information about StateFlow support in data binding, check out the official documentation.</p>
<p>Use the Lifecycle.repeatOnLifecycle or Flow.flowWithLifecycle APIs to safely collect flows from the UI layer in Android.</p>
]]></content:encoded></item><item><title><![CDATA[Hilt is stable! Easier dependency injection on Android]]></title><description><![CDATA[Hilt, Jetpack’s recommended dependency injection (DI) solution for Android apps, is already stable! That means Hilt is fully ready to be used in production. Hilt is simpler than Dagger, enables you to write less boilerplate code, it’s designed for An...]]></description><link>https://mohamedebrahim96.com/hilt-is-stable-easier-dependency-injection-on-android</link><guid isPermaLink="true">https://mohamedebrahim96.com/hilt-is-stable-easier-dependency-injection-on-android</guid><dc:creator><![CDATA[Mohamed Ebrahim]]></dc:creator><pubDate>Sat, 24 Sep 2022 22:02:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1664056871524/ivSoG1BTb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hilt, Jetpack’s recommended dependency injection (DI) solution for Android apps, is already stable! That means Hilt is fully ready to be used in production. Hilt is simpler than Dagger, enables you to write less boilerplate code, it’s designed for Android and has integration with multiple Jetpack libraries. Several companies have already started taking advantage of Hilt in their apps.</p>
<p>Hilt was first released as alpha in June 2020 with the mission of defining a standard way to do DI in your Android apps and since then, we’ve received tons of feedback from developers. That not only improved the library, but also, it let us know that we’re working on the right problems.</p>
<p>Instead of creating dependency graphs by hand, and manually injecting and passing around types where needed, Hilt automatically generates all that code for you at compile time by means of annotations. Hilt can help you get the most out of DI best practices in your app by doing the hard work and generating all that boilerplate you would’ve needed to write otherwise. Also, as it’s fully integrated with Android, Hilt manages the lifecycle of the dependency graphs associated with the Android framework classes automatically for you.</p>
<p>Let’s see Hilt in action with a quick example! After setting Hilt up, using it in your project from scratch to inject a ViewModel in an Activity is as easy as adding few annotations to your code as follows:</p>
<pre><code>@HiltAndroidApp <span class="hljs-comment">// Setup Hilt in your app</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApplication</span> : <span class="hljs-title">Application</span>() </span>{ ... }

<span class="hljs-comment">// Make Hilt aware of this ViewModel</span>
@HiltViewModel
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginViewModel</span> @<span class="hljs-title">Inject</span> <span class="hljs-title">constructor</span>(
    <span class="hljs-title">private</span> <span class="hljs-title">val</span> <span class="hljs-title">savedStateHandle</span>: <span class="hljs-title">SavedStateHandle</span>,
    /* ... <span class="hljs-title">Other</span> <span class="hljs-title">dependencies</span> <span class="hljs-title">Hilt</span> <span class="hljs-title">takes</span> <span class="hljs-title">care</span> <span class="hljs-title">of</span> ... */
) : <span class="hljs-title">ViewModel</span>() </span>{ ... }


<span class="hljs-comment">// Make the activity use the right ViewModel factory and</span>
<span class="hljs-comment">// inject other dependencies</span>
@AndroidEntryPoint 
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginActivity</span> : <span class="hljs-title">AppCompatActivity</span>() </span>{

    private val loginViewModel: LoginViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        <span class="hljs-built_in">super</span>.onCreate(savedInstanceState)
        <span class="hljs-comment">// loginViewModel is ready to be used</span>
    }
}
</code></pre><p>Apart from what’s mentioned above, why should you use Hilt in your Android app?</p>
<p>Simpler than Dagger
Hilt is built on top of the popular DI library Dagger so benefits from the compile time correctness, runtime performance, scalability, and Android Studio support that Dagger provides. Some Dagger annotations, such as @Inject to tell Dagger and Hilt how to provide an instance of a type, are often used in Hilt. But Hilt is simpler than Dagger!</p>
<p>“I highly recommend leveraging Dagger for dependency injection in Android applications. However, pure vanilla Dagger can lead to too much room for creativity. When that gets mixed with the complexity of the various lifecycle-aware components that are part of Android development, there’s plenty of room for pitfalls such as memory leaks: for example, accidentally passing in Activity-scoped dependencies into ViewModels. Hilt being opinionated and designed specifically for Android helps you avoid some of the pitfalls when using vanilla Dagger.” — Marcelo Hernandez, Staff Software Engineer, Tinder</p>
<p>If you’re already using Dagger in your app and want to migrate to Hilt… fear not! Dagger and Hilt can coexist together, apps can be migrated on an as-needed basis.</p>
<p>Less boilerplate
Hilt is opinionated — this means that it makes decisions for you so that you have less code to write. Hilt defines standard components, or dependency graphs, fully integrated with Android framework classes such as Application, activities, fragments, and views. As well as scope annotations to scope instances of types to those components.</p>
<p>“Hilt automatically generates the test application and test component via @HiltAndroidTest. We were able to remove between 20% and 40% of boilerplate wire up test code after migrating to Hilt!” — Jusun Lee, Software Engineer, YouTube</p>
<p>“We are only scratching the surface in terms of migrating to Hilt. However, in one of the modules we migrated to Hilt, we saw +78/-182 in terms of lines changed for this library.” — Marcelo Hernandez, Staff Software Engineer, Tinder</p>
<p>Designed for Android
As opposed to Dagger that is a dependency injection solution for the Java programming language applications, Hilt solely works in Android apps. Some annotations such as</p>
<p>@HiltAndroidApp, @AndroidEntryPoint, or @HiltViewModel are specific to Hilt and define an opinionated way to do DI on Android.</p>
<p>“Hilt finally offers built-in Android lifecycle aware Dagger components. With Hilt, we can solely focus on Dagger @Modules without having to worry about Components, Subcomponents, the Component provider pattern, and so on.” — Marcelo Hernandez, Staff Software Engineer, Tinder</p>
<p>Standardization of components and bindings
For those that know about Dagger, Hilt simplifies the dependency graph by using a monolithic component system to generate less code at compile time.</p>
<p>“With Hilt’s monolithic component system, binding definitions are provided once and shared across all classes that use that component. This is a big win as previously, YouTube used a polylithic component system where modules were manually wired-up into custom components and there were many duplicate binding definitions.” — Jusun Lee, Software Engineer, YouTube</p>
<p>“As our Gradle modules separation allows for feature development in isolation, it became easy to be too creative when using Dagger. We’ve found that migrating those modules over to Hilt has actually exposed flaws in which we were inadvertently violating the separation of concerns.” — Marcelo Hernandez, Staff Software Engineer, Tinder</p>
]]></content:encoded></item><item><title><![CDATA[Migrating Architecture Blueprints to Jetpack Compose]]></title><description><![CDATA[As part of our effort to modernize the app architecture guidance, we want to experiment with different UI patterns to see what works best, find similarities and differences between the alternatives, and ultimately consolidate those learnings as best ...]]></description><link>https://mohamedebrahim96.com/migrating-architecture-blueprints-to-jetpack-compose</link><guid isPermaLink="true">https://mohamedebrahim96.com/migrating-architecture-blueprints-to-jetpack-compose</guid><dc:creator><![CDATA[Mohamed Ebrahim]]></dc:creator><pubDate>Sat, 24 Sep 2022 18:12:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1664042987624/pKQodMCDu.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As part of our effort to modernize the app architecture guidance, we want to experiment with different UI patterns to see what works best, find similarities and differences between the alternatives, and ultimately consolidate those learnings as best practices.</p>
<p>To make our findings as easy to follow as possible, we needed a sample that has a familiar business case and isn’t too complicated. And… who doesn’t know about TODO apps? We chose Architecture Blueprints! Historically, Blueprints served as an experimental playground for architecture choices. What a great fit for this as well!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664043032489/cs-UOCcjW.gif" alt="2022-04-11-blueprints-migration-compose-1.gif" /></p>
<p>The patterns we want to experiment with are obviously affected by the different APIs available nowadays. The new kids in town are Jetpack Compose’s State APIs! As Compose seamlessly works with any Unidirectional Data Flow pattern, we’ll use Compose to render UI to make a fair comparison.</p>
<p>This blog post tells the story of how the team migrated Architecture Blueprints to Jetpack Compose. As LiveData is also considered an alternative in our experiments, we left the sample as it was at the time of the migration. In this refactoring, the ViewModel classes and the data layer were left untouched.</p>
<p>⚠️ The architecture used in this LiveData-based codebase doesn’t fully follow the latest architecture best practices. In particular, LiveData shouldn’t be used in the data or domain layers — Flows and coroutines should be used instead.</p>
<p>Now that the context is clear, let’s dive in on how we approached refactoring Blueprints to Jetpack Compose. You can check out the full code on the main branch.</p>
<p>The goal of a gradual migration is to ease code reviews and maintain a shippable product throughout the migration. The migration plan involved three steps:</p>
<p>Migrate the content of each screen to Compose. Each screen could be individually migrated to Compose, including their UI tests. Fragments then become the container/host of each migrated screen.</p>
<p>Migrate the app to Navigation Compose — which removes all Fragments from the project — and migrate the Activity UI logic to root composables. End-to-end tests are also migrated at this point.</p>
<p>Remove View system dependencies.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664043105056/ByhAxmZSm.png" alt="2022-04-11-blueprints-migration-compose-2.png" /></p>
]]></content:encoded></item></channel></rss>