<?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: Vortana Say</title>
    <description>The latest articles on DEV Community by Vortana Say (@vsay01).</description>
    <link>https://dev.to/vsay01</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F882784%2Fbb946a3d-025a-4cf3-a832-f38155cd910b.jpeg</url>
      <title>DEV Community: Vortana Say</title>
      <link>https://dev.to/vsay01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vsay01"/>
    <language>en</language>
    <item>
      <title>🧱 Breaking the Monolith: A Practical, Step-by-Step Guide to Modularizing Your Android App - Part 1</title>
      <dc:creator>Vortana Say</dc:creator>
      <pubDate>Sat, 20 Sep 2025 02:17:30 +0000</pubDate>
      <link>https://dev.to/vsay01/breaking-the-monolith-a-practical-step-by-step-guide-to-modularizing-your-android-app-part-1-2n0n</link>
      <guid>https://dev.to/vsay01/breaking-the-monolith-a-practical-step-by-step-guide-to-modularizing-your-android-app-part-1-2n0n</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published on Hashnode. You can read it&lt;/em&gt; &lt;a href="https://vsaytech.hashnode.dev/breaking-the-monolith-a-practical-step-by-step-guide-to-modularizing-your-android-app-part-1" rel="noopener noreferrer"&gt;&lt;em&gt;here.&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://medium.com/@sayvortana.itc/untangling-android-navigation-from-simple-flows-to-production-grade-architectures-part-3-851dd80d7a7d" rel="noopener noreferrer"&gt;&lt;em&gt;"Untangling Android Navigation"&lt;/em&gt; series&lt;/a&gt; (&lt;a href="https://github.com/vsay01/PinterestStyleGridDemo/tree/navigation-part-3" rel="noopener noreferrer"&gt;Starter GitHub code&lt;/a&gt;), we built a healthy, single-module app using Jetpack Compose, Hilt, Paging, nested navigation, and deep links. That’s great for learning — but large production apps eventually need &lt;strong&gt;modularization&lt;/strong&gt; to scale builds, teams, and features.&lt;/p&gt;

&lt;p&gt;In part 1 of this article, we are going to focus on&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;blueprint&lt;/strong&gt;: a clear thought process, a high-level plan, and a safe step-by-step migration order so you (and your team) can modularize with confidence&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement convention plugins to manage Gradle build logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement feature_bookmarks module&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Why Modularize?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;⚡ &lt;strong&gt;Build performance&lt;/strong&gt; → Recompile only what changed; faster local builds &amp;amp; CI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;👥 &lt;strong&gt;Team velocity&lt;/strong&gt; → Parallelize work; isolate feature ownership; fewer merge conflicts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🏛 &lt;strong&gt;Architecture enforcement&lt;/strong&gt; → Keep boundaries clean between UI, domain, and data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔁 &lt;strong&gt;Reusability&lt;/strong&gt; → Shared design system, clients, and models become stable libraries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧩 &lt;strong&gt;Future-proofing&lt;/strong&gt; → One step away from Dynamic Feature Delivery.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When not to modularize:&lt;/strong&gt; minimal apps, solo projects, or prototypes where overhead &amp;gt; benefit.&lt;/p&gt;




&lt;h3&gt;
  
  
  Starting Point: The Monolith
&lt;/h3&gt;

&lt;p&gt;Our current single module 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;:app/
├── build.gradle.kts (Module-level Gradle file for :app)
├── src/
│   ├── androidTest/
│   │   └── java/
│   │       └── com/vsay/pintereststylegriddemo/
│   │           └── (Instrumented tests, e.g., for UI or navigation)
│   ├── main/
│   │   ├── AndroidManifest.xml
│   │   ├── java/
│   │   │   └── com/vsay/pintereststylegriddemo/
│   │   │       ├── MainApplication.kt      (If using Hilt, @HiltAndroidApp)
│   │   │       ├── MainActivity.kt         (Entry point, hosts AppWithTopBar)
│   │   │       │
│   │   │       ├── common/                 (Shared utilities, constants, and ALL navigation before modularization)
│   │   │       │   ├── navigation/
│   │   │       │   │   ├── AppRoute.kt
│   │   │       │   │   ├── AppNavHost.kt
│   │   │       │   │   ├── BottomNavItem.kt
│   │   │       │   │   ├── MainAppNavGraph.kt  (Defines Home, Detail composables for NavHost)
│   │   │       │   │   ├── ProfileNavGraph.kt  (Defines Profile, Account Settings composables)
│   │   │       │   │   └── BookmarkNavGraph.kt (Defines Bookmark composables)
│   │   │       │   └── utils/
│   │   │       │       └── (General utility files)
│   │   │       │
│   │   │       ├── di/                     (Dependency Injection - Hilt modules for the whole app)
│   │   │       │   ├── AppModule.kt        (App-level bindings, context, etc.)
│   │   │       │   ├── ViewModelModule.kt  (If providing ViewModels not directly with @HiltViewModel)
│   │   │       │   ├── NetworkModule.kt    (Retrofit, OkHttp, ApiService bindings)
│   │   │       │   └── DatabaseModule.kt   (Room DB, DAO bindings)
│   │   │       │
│   │   │       ├── domain/                 (Core business logic - models, repository interfaces, use cases)
│   │   │       │   ├── model/
│   │   │       │   │   ├── Image.kt
│   │   │       │   │   └── (User.kt - even if simplified for now)
│   │   │       │   ├── repository/
│   │   │       │   │   └── ImageRepository.kt (Interface)
│   │   │       │   └── usecase/
│   │   │       │       └── GetImageDetailsUseCase.kt
│   │   │       │
│   │   │       ├── data/                   (Data sources and repository implementations)
│   │   │       │   ├── remote/
│   │   │       │   │   ├── ApiService.kt
│   │   │       │   │   └── dto/
│   │   │       │   │       └── ImageDto.kt
│   │   │       │   ├── local/              (If using Room for local persistence)
│   │   │       │   │   ├── AppDatabase.kt
│   │   │       │   │   ├── ImageDao.kt
│   │   │       │   │   └── ImageEntity.kt
│   │   │       │   ├── repository/
│   │   │       │   │   └── ImageRepositoryImpl.kt
│   │   │       │   └── mapper/
│   │   │       │       └── (ImageMapper.kt - DTO to Domain)
│   │   │       │
│   │   │       ├── presentation/           (UI logic: ViewModels and Screens for ALL features)
│   │   │       │   ├── app/
│   │   │       │   │   └── AppViewModel.kt   (For global TopAppBarConfig, etc.)
│   │   │       │   │
│   │   │       │   ├── common/             (Shared presentation elements like TopAppBarConfig class)
│   │   │       │   │   ├── TopAppBarConfig.kt
│   │   │       │   │   └── NavigationIconType.kt
│   │   │       │   │
│   │   │       │   ├── home/               (Logically for Home feature)
│   │   │       │   │   ├── viewmodel/
│   │   │       │   │   │   └── HomeViewModel.kt
│   │   │       │   │   └── ui/
│   │   │       │   │       ├── HomeScreen.kt
│   │   │       │   │       └── (HomeScreenUI.kt - if separated)
│   │   │       │   │
│   │   │       │   ├── detail/             (Logically for Detail feature)
│   │   │       │   │   ├── viewmodel/
│   │   │       │   │   │   └── DetailViewModel.kt
│   │   │       │   │   └── ui/
│   │   │       │   │       └── DetailScreen.kt (which contains DetailScreenUI)
│   │   │       │   │
│   │   │       │   ├── profile/            (Logically for Profile feature)
│   │   │       │   │   ├── viewmodel/
│   │   │       │   │   │   └── ProfileViewModel.kt
│   │   │       │   │   └── ui/
│   │   │       │   │       ├── ProfileScreen.kt
│   │   │       │   │       ├── ProfileScreenUI.kt
│   │   │       │   │       ├── AccountSettingsOverviewScreen.kt
│   │   │       │   │       ├── ChangePasswordScreen.kt
│   │   │       │   │       └── (EditProfileScreen.kt, ManageNotificationsScreen.kt)
│   │   │       │   │
│   │   │       │   └── bookmark/           (Logically for Bookmark feature)
│   │   │       │       ├── viewmodel/
│   │   │       │       │   └── BookmarkViewModel.kt
│   │   │       │       └── ui/
│   │   │       │           ├── BookmarkScreen.kt
│   │   │       │           └── (BookmarkScreenUI.kt - if separated)
│   │   │       │
│   │   │       └── ui/                     (Overall App UI structure and Theme)
│   │   │           ├── AppWithTopBar.kt    (Main Scaffold, TopAppBar, BottomNav, NavHost integration)
│   │   │           ├── common/             (Shared UI components not specific to a screen, e.g. GenericLoading.kt)
│   │   │           └── theme/
│   │   │               ├── Color.kt
│   │   │               ├── Theme.kt
│   │   │               ├── Type.kt
│   │   │               └── Shape.kt
│   │   │
│   │   └── res/                          (All resources are currently in :app)
│   │       ├── drawable/
│   │       ├── layout/ (Minimal, maybe activity_main.xml)
│   │       ├── mipmap-xxxhdpi/ (App icons)
│   │       ├── values/
│   │       │   ├── colors.xml
│   │       │   ├── strings.xml (Contains ALL strings for all features)
│   │       │   └── themes.xml
│   │       └── (Other resource folders like font/, raw/)
│   │
│   └── test/
│       └── java/
│           └── com/vsay/pintereststylegriddemo/
│               └── (Unit tests for ViewModels, UseCases, Mappers, etc.)
│
├── .gitignore
├── build.gradle.kts (Project-level)
├── settings.gradle.kts (Declares only ':app' module)
└── gradle.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but everything depends on &lt;code&gt;:app&lt;/code&gt;. A small change in one area tends to rebuild the world.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Target: A Pragmatic Module Map
&lt;/h3&gt;

&lt;p&gt;We’ll aim for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:app                       // entry point (Activity, AppNavHost, bottom bar, @HiltAndroidApp)
:core-domain               // models, use cases, repository interfaces (pure Kotlin)
:core-data                 // Retrofit/Room, DTOs, mappers, repo impls (Hilt modules for data)
:core-ui                   // theme, typography, shared Compose components
:core-navigation           // route contracts, deep link constants
:core-common               // (NEW) generic utils, base classes, non-UI/domain constants

:feature-home              // UI + VM + nav graph definition
:feature-detail            // UI + VM + nav graph definition
:feature-profile           // UI + VM + nav graph definition (incl. nested graphs)
:feature-bookmark          // UI + VM + nav graph definition

// Optional, but highly recommended for larger projects:
:core-testing              // (NEW - for shared test utilities)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Principle: &lt;strong&gt;Features depend inward&lt;/strong&gt; (on &lt;code&gt;:core-*&lt;/code&gt;), never sideways on each other. &lt;code&gt;:app&lt;/code&gt; depends on all features and stitches them together.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DI (Hilt) does not need its own dedicated module.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hilt modules (&lt;a href="http://twitter.com/Module" rel="noopener noreferrer"&gt;@Module&lt;/a&gt; annotated classes) should reside in the Gradle module where the dependencies they provide are most logically located or implemented.&lt;/li&gt;
&lt;li&gt;Creating a separate :core-di module just for Hilt files can sometimes create an extra, somewhat artificial layer and might lead to complex dependency management for that DI module itself. Co-locating Hilt modules with the implementations or logical groupings they configure is usually cleaner.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  🧭 Thought Process Before Touching Code
&lt;/h3&gt;

&lt;p&gt;Before you move a single file, align on &lt;em&gt;how&lt;/em&gt; modularization will work. Rushing into splitting modules without guardrails is the fastest way to chaos. These principles act like a &lt;strong&gt;compass&lt;/strong&gt;: once agreed upon, the actual file moves become mechanical. Without them, modularization can easily spiral into refactors, regressions, or wasted effort.&lt;/p&gt;

&lt;p&gt;Here’s a &lt;strong&gt;decision framework&lt;/strong&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Stabilize the Domain First
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;What it means:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Extract &lt;strong&gt;domain models&lt;/strong&gt; (e.g., &lt;code&gt;User&lt;/code&gt;, &lt;code&gt;Image&lt;/code&gt;) and &lt;strong&gt;repository interfaces&lt;/strong&gt; (&lt;code&gt;ImageRepository&lt;/code&gt;, &lt;code&gt;UserRepository&lt;/code&gt;) into &lt;code&gt;:core-domain&lt;/code&gt;. Keep it &lt;strong&gt;pure Kotlin&lt;/strong&gt; with no Android dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why first?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stable contracts&lt;/strong&gt; → Every feature and data layer depends on domain types. If you change them mid-migration, the ripple breaks everything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build speed&lt;/strong&gt; → Pure Kotlin modules compile fastest. Locking them down means most changes won’t invalidate them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testability&lt;/strong&gt; → With the domain isolated, you can write fast, JVM-only tests for your most critical business rules.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;Reasoning:&lt;/strong&gt; Think of the domain as the &lt;em&gt;“public API” of your app’s business logic&lt;/em&gt;. Stabilizing it first is like pouring the foundation of a house before moving walls around.&lt;/p&gt;
&lt;h4&gt;
  
  
  2. Extract the Most Reused Things Next
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;What it means:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Pull out your &lt;strong&gt;design system&lt;/strong&gt; (colors, typography, theming), reusable &lt;strong&gt;Compose UI components&lt;/strong&gt; (e.g., &lt;code&gt;AppTopBar&lt;/code&gt;, &lt;code&gt;ErrorView&lt;/code&gt;), and &lt;strong&gt;navigation constants&lt;/strong&gt; into &lt;code&gt;:core-ui&lt;/code&gt; and &lt;code&gt;:core-navigation&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why now?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;High reuse&lt;/strong&gt; → Nearly every feature imports them. Leaving them in &lt;code&gt;:app&lt;/code&gt; means features remain coupled to the monolith.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prevents duplication&lt;/strong&gt; → If you modularize features first, you’ll end up copy-pasting components before you extract them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build savings&lt;/strong&gt; → UI components change often; isolating them means recompiles don’t cascade into unrelated modules.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;Reasoning:&lt;/strong&gt; Shared UI and route contracts are like the &lt;em&gt;“shared language”&lt;/em&gt; of the app. Extracting them early gives features a common dictionary to speak.&lt;/p&gt;
&lt;h4&gt;
  
  
  3. Choose a Safe Migration Order
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;What it means:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
 Don’t move everything at once. Instead, peel off features in an order that reduces risk: &lt;strong&gt;Bookmark -&amp;gt; Detail → Profile → Home&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this order?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bookmark&lt;/strong&gt; → Simple dummy composable screen&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Detail&lt;/strong&gt; → A leaf feature (only consumes an ID). Few dependencies, minimal blast radius.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Profile&lt;/strong&gt; → Demonstrates nested graphs (Profile → Settings → EditProfile), useful for testing navigation modularity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Home&lt;/strong&gt; → The hub feature (feeds, notifications, paging). Depends on many things, so save it for last.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;Reasoning:&lt;/strong&gt; Start with &lt;strong&gt;low-risk, low-dependency features&lt;/strong&gt; to validate your setup. Leave “hub” features for last to avoid blocking progress elsewhere.&lt;/p&gt;
&lt;h4&gt;
  
  
  4. Keep Navigation Boundaries Clear
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;What it means:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
 Each feature defines its own &lt;strong&gt;NavGraph&lt;/strong&gt; extension, but route constants live in &lt;code&gt;:core-navigation&lt;/code&gt;. &lt;code&gt;:app&lt;/code&gt; is the only place where everything is stitched together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this way?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Encapsulation&lt;/strong&gt; → Features don’t need to know each other’s internals, only shared contracts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt; → Easier to swap features in/out (e.g., AB testing, dynamic delivery).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Correctness&lt;/strong&gt; → Avoids fragile “stringly typed” navigation mistakes by centralizing route contracts.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;Reasoning:&lt;/strong&gt; Navigation is &lt;em&gt;the glue&lt;/em&gt;, not a feature. Keeping boundaries clear means your app scales like LEGO bricks — each feature is self-contained and reusable.&lt;/p&gt;
&lt;h4&gt;
  
  
  5. Prepare Dependency Injection (DI) Boundaries
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;What it means:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interfaces&lt;/strong&gt; (contracts) in &lt;code&gt;:core-domain&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implementations + Hilt bindings&lt;/strong&gt; in &lt;code&gt;:core-data&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feature modules&lt;/strong&gt; depend only on interfaces.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this pattern?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No circular dependencies&lt;/strong&gt; → &lt;code&gt;:core-domain&lt;/code&gt; doesn’t depend on anything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feature isolation&lt;/strong&gt; → Features inject only what they need (interfaces), not whole implementations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt; → Easy to swap implementations (network vs fake, DB vs in-memory) for testing or experiments.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;Reasoning:&lt;/strong&gt; This is textbook Dependency Inversion. By making features depend on abstractions, you lock down clean boundaries and gain long-term maintainability.&lt;/p&gt;


&lt;h3&gt;
  
  
  🛠 Pre-Work Checklist Before Migration
&lt;/h3&gt;

&lt;p&gt;Before you split a single module, set up these &lt;strong&gt;guardrails&lt;/strong&gt;. Skipping them leads to config drift, resource conflicts, and painful rewrites later. Also, before creating new modules, ensure these are in place in your current single-module project. If they are, great! If not, now is the time to set them up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version Catalog (&lt;/strong&gt;&lt;code&gt;libs.versions.toml&lt;/code&gt;&lt;strong&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Action: Create/update gradle/libs.versions.toml to centralize all dependency versions (Compose, Kotlin, Hilt, Retrofit, Room, etc.).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reason: Centralize dependency versions (Compose BOM, Retrofit, Room) and ensure version consistency across all future modules and simplifying updates. Prevents version conflicts when multiple modules declare the same dependency.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;toml
[versions]
kotlin = "1.9.22"
composeCompiler = "1.5.8"
composeBom = "2024.02.02"
# ... other versions

[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
# ... other libraries

[plugins]
# ... gradle plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Convention Plugins or&lt;/strong&gt; &lt;code&gt;buildSrc&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Extract common Gradle configs (compileSdk, Compose flags, Kotlin options).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensures consistency and reduces boilerplate when adding new modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reason: Reduces boilerplate in each module’s build.gradle.kts, enforces consistency, makes global configuration changes easier, also ensures consistency and reduces boilerplate when adding new modules.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Resource Hygiene&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Prefix shared resources (&lt;code&gt;core_&lt;/code&gt; for colors, strings, drawables).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prevents namespace collisions once you have multiple feature resource folders.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dependency Scoping Rules&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Default to &lt;code&gt;implementation&lt;/code&gt;. Use &lt;code&gt;api&lt;/code&gt; &lt;strong&gt;Only&lt;/strong&gt; if consumers must see the type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enforces proper encapsulation across modules.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Build Performance Optimizations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Prefer &lt;strong&gt;KSP&lt;/strong&gt; over KAPT (e.g., for Room, Moshi).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable Gradle build cache + configuration cache.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Test Strategy Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Decide where tests live (inside each module).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up CI to run only affected modules’ tests on PRs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Do these first → then the migration becomes smooth, mechanical, and predictable.&lt;/p&gt;


&lt;h3&gt;
  
  
  💻⚙️🔧 &lt;strong&gt;Convention Plugins or&lt;/strong&gt; &lt;code&gt;buildSrc&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;There are two options to manage Gradle build logic, convention plugins or buildSrc. We are going to choose Convention plugins due to the reasons below:&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Convention Plugin:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Convention Plugins are the tools that help you manage the build configurations of these modules consistently and efficiently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It’s a piece of code (usually written in Kotlin or Groovy) that applies a set of pre-defined configurations to a Gradle project (a module).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For example, you could have an android-library-convention.gradle.kts plugin that sets up everything a typical Android library module needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Included Build: This is different from buildSrc, which is a special directory Gradle handles automatically. An included build is more explicit and can be more flexible.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Decoupled&lt;/strong&gt;:&lt;code&gt;build-logic&lt;/code&gt; is an &lt;strong&gt;independent build&lt;/strong&gt;, not tied to the main project classpath. Changes don’t trigger full recompiles like &lt;code&gt;buildSrc&lt;/code&gt; does.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Clear Separation of Concerns&lt;/strong&gt;: Build logic is neatly separated into its own convention-plugins build, away from your application/library source code. This is often cleaner than buildSrc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Avoids buildSrc Quirks&lt;/strong&gt;: buildSrc has a special classpath that can sometimes lead to subtle issues or conflicts. A regular included build often has a cleaner, more isolated classpath.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Testability (Advanced)&lt;/strong&gt;: Convention plugins in an included build are easier to test with unit tests than logic directly in buildSrc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Scalable&lt;/strong&gt;: Easy to split into multiple convention plugins (Android app, library, feature, Compose, testing, etc.).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ &lt;strong&gt;Recommended by Google&lt;/strong&gt;: AndroidX and Nowinandroid use convention plugins for large-scale modular projects.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Slightly more setup complexity than &lt;code&gt;buildSrc&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Implement Convention Plugin
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Create the Directory for the Plugins Build. At the root of your PinterestStyleGridDemo project, create a new directory: &lt;strong&gt;convention-plugins&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Initialize the convention-plugins Build&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create convention-plugins/settings.gradle.kts:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rootProject.name = "pinterest-convention-plugins"

dependencyResolutionManagement {
    versionCatalogs {
        create("libs") { // creating a catalog named "libs" for this included build
            from(files("../gradle/libs.versions.toml")) // Pointing to the TOML file in the root project
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Create convention-plugins/build.gradle.kts:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import org.gradle.kotlin.dsl.`kotlin-dsl`

plugins {
    `kotlin-dsl`
}

repositories {
    google()
    mavenCentral()
}

// Access versions from the catalog for these dependencies
dependencies {
    implementation("com.android.tools.build:gradle:${libs.versions.agp.get()}") // agp version is in the catalog
    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${libs.versions.kotlin.get()}") // kotlin version is in the catalog
}

gradlePlugin {
    plugins {
        register("androidLibrary") {
            id = "pinterest.android-library-convention"
            implementationClass = "AndroidLibraryConventionPlugin"
        }
        // Register other plugins here
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; add convention-plugins to root settings.gradle.kts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// other codes
rootProject.name = "PinterestStyleGridDemo"
include(":app")
includeBuild("convention-plugins")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sync the Project with Gradle files, and the build should be successful.&lt;/p&gt;

&lt;h4&gt;
  
  
  Implement Custom Gradle plugin
&lt;/h4&gt;

&lt;p&gt;Think about what most of your Android library modules (that aren’t the main :app module) will need:&lt;/p&gt;

&lt;p&gt;Core Android Library Setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;They’ll all apply the com.android.library plugin.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;They’ll all apply the org.jetbrains.kotlin.android plugin.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;They’ll need a compileSdk, minSdk, and targetSdk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;They’ll need a testInstrumentationRunner.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;They’ll need standard JavaVersion compatibility for compileOptions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common Dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Kotlin standard library (org.jetbrains.kotlin:kotlin-stdlib).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Basic testing libraries (JUnit, AndroidX Test, Espresso).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Perhaps AndroidX Core KTX for useful extension functions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build Optimizations/Configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Maybe specific ProGuard rules for release builds (though often centralized).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lint configurations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code coverage (JaCoCo) setup&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of writing this boilerplate in every single library module’s build.gradle.kts file, you create an AndroidLibraryConventionPlugin that encapsulates all the common setup listed above.&lt;/p&gt;

&lt;p&gt;Why we need it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DRY (Don’t Repeat Yourself): Write the setup once.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistency: Every library module gets the same battle-tested configuration. No accidental variations in minSdk or forgotten test dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maintainability: Need to update compileSdk for all libraries? Change it in ONE place (the convention plugin).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Readability: Your library module’s build.gradle.kts becomes tiny, only declaring plugins and dependencies specific to that module.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to define what and how many custom Gradle plugins need to be created?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are the processes that I found useful and produce good results:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Identify and Isolate a Pilot Feature Module First&lt;/strong&gt;: You need a concrete example of a module to understand its true, common needs. Creating convention plugins in a vacuum can lead to premature abstraction or missing essential configurations. By extracting a real feature, you’ll see exactly what boilerplate you’re repeating.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observe the Boilerplate&lt;/strong&gt; in the new Module’s build.gradle.kt in the feature module: Once we create and move files to feature module then we can see what are the boilerplate code used in this build.gradle.kts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create Convention Plugin&lt;/strong&gt; file(s) for the feature module&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apply the Convention Plugin&lt;/strong&gt; to the Feature Module to see the benefit immediately and verify the plugin works.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Iterate and Expand&lt;/strong&gt;: As you extract more features or common libraries, you’ll refine your existing convention plugins and create new ones.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why this order?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Concrete over Abstract: It’s easier to generalize from a concrete example than to correctly guess all common needs upfront.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Immediate Value: You see the benefit of your convention plugin right away with the first new module.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Less Rework: If you create plugins first, you might find they don’t perfectly fit the needs of your actual modules once you start creating them, leading to more refactoring of the plugins.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Incremental Progress: Modularization can be a large task. This approach allows you to do it piece by piece, validating each step.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  Implement feature_bookmarks module
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Create the :feature-bookmarks Android Library Module in the project&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Folder/Module name → feature-bookmarks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In Gradle configuration, → must prefix with &lt;code&gt;:&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gradle uses &lt;code&gt;:&lt;/code&gt; as a &lt;strong&gt;path separator&lt;/strong&gt; for projects. &lt;code&gt;:&lt;/code&gt; is not part of the module’s &lt;strong&gt;actual name&lt;/strong&gt;, but rather part of the &lt;strong&gt;Gradle project path&lt;/strong&gt;. Examples:&lt;br&gt;&lt;br&gt;
&lt;code&gt;:&lt;/code&gt; = root project&lt;br&gt;&lt;br&gt;
&lt;code&gt;:feature-bookmarks&lt;/code&gt; = module named &lt;code&gt;feature-bookmarks&lt;/code&gt; at the root&lt;br&gt;&lt;br&gt;
&lt;code&gt;:features:bookmarks&lt;/code&gt; = module &lt;code&gt;bookmarks&lt;/code&gt; inside folder/module &lt;code&gt;features&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Move Bookmarks-Specific Code from app module to feature-bookmarks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In BookmarkScreen.kt, it is trying to access code that’s still in the :app module. We can add :app module to the :feature-module, but this creates a circular dependency which we want to avoid. So there are codes we need to refactor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change import R file from com.vsay.pintereststylegriddemo.R to com.vsay.pintereststylegriddemo.feature.bookmarks.R and create string resources used in the compose screen&lt;/li&gt;
&lt;li&gt;Decoupling AppViewModel, TopAppBarConfig, NavigationIconType. The current: the current BookmarkScreen takes appViewModel to configure the top app bar. This creates a dependency on :app. We need to invert this: BookmarkScreen should describe its app bar needs, and the caller (in :app) should fulfill them.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Composable
fun BookmarkScreen() {
    BookmarkScreenUI(modifier = Modifier)
}

@Composable
fun BookmarkScreenUI(modifier: Modifier) {
    Column(
        modifier = modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = androidx.compose.ui.platform.LocalContext.current.getString(R.string.bookmark_screen_title),
            style = MaterialTheme.typography.headlineMedium
        )
        Text(
            text = androidx.compose.ui.platform.LocalContext.current.getString(R.string.bookmark_screen_placeholder_text),
            style = MaterialTheme.typography.bodyLarge
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In BookmarkNavGraph.kt: it previously took appViewModel. It no longer needs to. It just defines the composable for the bookmarks screen route.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Define a route for this feature's graph, if it has multiple screens.
// If it's just one screen, this might not be a nested graph.
const val BOOKMARKS_GRAPH_ROUTE = "bookmarks_graph"
const val BOOKMARK_SCREEN_ROUTE = "bookmark_screen_route"

fun NavGraphBuilder.bookmarkNavGraph() {
    // If bookmarks feature is just one screen, you can use composable directly.
    // If it's a nested graph of multiple bookmark-related screens:
    navigation(
        startDestination = BOOKMARK_SCREEN_ROUTE,
        route = BOOKMARKS_GRAPH_ROUTE
    ) {
        composable(route = BOOKMARK_SCREEN_ROUTE) {
            BookmarkScreen()
        }
        // Add other composables specific to the bookmarks feature here if any
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Observe the Boilerplate in build.gradle.kts under :feature-bookmakrs module&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is our build.gradle.kts(:feature-bookmars)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Suppress("DSL_SCOPE_VIOLATION") // Remove if not needed after plugins are applied
plugins {
    alias(libs.plugins.android.library)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
}

android {
    namespace = "com.vsay.pintereststylegriddemo.feature.bookmarks"
    compileSdk = libs.versions.compileSdk.get().toInt()

    defaultConfig {
        minSdk = libs.versions.minSdk.get().toInt()
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles("consumer-rules.pro")
    }

    buildTypes {
        release {
            isMinifyEnabled = false // Adjust as needed for your project
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = libs.versions.jvmTarget.get()
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
    }
}

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.activity.compose)

    // material
    implementation(libs.material)
    implementation(libs.androidx.material3)

    // Jetpack Compose
    implementation(libs.androidx.compose.ui)
    implementation(libs.androidx.compose.ui.tooling.preview)
    implementation(libs.androidx.navigation.compose) // For NavHostController, composable, etc.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice there are a bunch of configurations in android {…} and dependencies {…}; we know that in other feature modules, they also need to config information for android {….} and dependencies {…}, so these are candidates that can be moved to the convention plugin&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create Convention Plugin&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll create a plugin (e.g., AndroidLibraryConventionPlugin.kt) in our convention-plugins build src/main/kotlin package. This plugin will encapsulate all the common setup currently in :feature_bookmarks/build.gradle.kts. Specifically, it will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Apply the necessary base Gradle plugins (com.android.library, org.jetbrains.kotlin.android, org.jetbrains.kotlin.plugin.compose).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure the Android extension with standard settings (SDK versions, minSdk, Java compatibility, buildFeatures { compose = true }, composeOptions, default Proguard rules if any).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add common dependencies (like Kotlin stdlib, core AndroidX libraries, Jetpack Compose libraries, common testing libraries).&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import com.android.build.gradle.LibraryExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile // Added import
import org.jetbrains.kotlin.gradle.dsl.JvmTarget // Added import

class AndroidLibraryConventionPlugin : Plugin&amp;lt;Project&amp;gt; {
    override fun apply(project: Project) {
        project.run {
            // Get the version catalog named "libs"
            val libs = extensions.getByType&amp;lt;VersionCatalogsExtension&amp;gt;()
                .named("libs")

            plugins.apply("com.android.library")
            plugins.apply("org.jetbrains.kotlin.android")

            extensions.configure&amp;lt;LibraryExtension&amp;gt; {
                // 'libs' is now correctly typed as VersionCatalog and available from above

                compileSdk = libs.findVersion("compileSdk").get().requiredVersion.toInt()
                defaultConfig {
                    minSdk = libs.findVersion("minSdk").get().requiredVersion.toInt()
                    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
                    consumerProguardFiles("consumer-rules.pro")
                }

                buildTypes {
                    release {
                        isMinifyEnabled = false
                        proguardFiles(
                            getDefaultProguardFile("proguard-android-optimize.txt"),
                            "proguard-rules.pro"
                        )
                    }
                }

                // Get Java version from libs
                val javaVersionFromToml = libs.findVersion("java").get().requiredVersion // e.g., "1.8", "11"
                val javaVersionForCompileOptions = JavaVersion.toVersion(javaVersionFromToml)
                compileOptions {
                    sourceCompatibility = javaVersionForCompileOptions
                    targetCompatibility = javaVersionForCompileOptions
                }

                buildFeatures {
                    compose = true
                }

                composeOptions {
                    kotlinCompilerExtensionVersion =
                        libs.findVersion("composeCompiler").get().requiredVersion
                }

                lint {
                    abortOnError = true // Fail the build on lint errors
                    warningsAsErrors = true
                    checkDependencies = true
                }
            }

            // Configure Kotlin JVM toolchain (JDK for compilation)
            project.extensions.getByType(KotlinAndroidProjectExtension::class.java).jvmToolchain(
                libs.findVersion("jvmTarget").get().requiredVersion.toInt() // e.g., jvmTarget = "8" or "11" in TOML -&amp;gt; results in 8 or 11
            )

            // Configure Kotlin compiler options (bytecode target)
            project.tasks.withType(KotlinCompile::class.java).configureEach {
                compilerOptions {
                    val javaVersionFromTomlForKotlin = libs.findVersion("java").get().requiredVersion // e.g., "1.8", "11"
                    jvmTarget.set(JvmTarget.fromTarget(javaVersionFromTomlForKotlin))
                }
            }

            // Common Dependencies
            dependencies {
                add("implementation", libs.findLibrary("androidx-core-ktx").get())
                add("implementation", libs.findLibrary("androidx-activity-compose").get())

                add("implementation", libs.findLibrary("material").get())
                add("implementation", libs.findLibrary("androidx-material3").get())


                add("implementation", libs.findLibrary("androidx-compose-ui").get())
                add("implementation", libs.findLibrary("androidx-compose-ui-tooling").get())
                add("implementation", libs.findLibrary("androidx-compose-ui-tooling-preview").get())
                add("implementation", libs.findLibrary("androidx-navigation-compose").get())
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In conention-plugins/build.gradle.kts, we register AndroidLibraryConventionPlugin&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// other codes

gradlePlugin {
    plugins {
        register("androidLibrary") {
            id = "pinterest.android-library-convention"
            implementationClass = "AndroidLibraryConventionPlugin"
        }
        // Register other plugins here
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Apply the Convention Plugin to :feature_bookmarks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In feature_bookmarks/build.gradle.kts, we can simplify it to just&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins {
    // Apply your convention plugin using the ID you registered
    id("pinterest.android-library-convention")
    alias(libs.plugins.kotlin.compose)
}

android {
    // The namespace is always specific to the module and must remain here.
    namespace = "com.vsay.pintereststylegriddemo.feature.bookmarks"
}

dependencies {
    // Only dependencies that are SPECIFIC to the feature_bookmarks module
    // and are NOT already included by your "pinterest.android-library-convention" convention plugin.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sync the Gradle files and run the application. The app should run, and you should be able to navigate to the bookmarks screen without any issue.&lt;/p&gt;

&lt;h4&gt;
  
  
  Flow and interaction between modules at this stage
&lt;/h4&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%2Fsxj54nmna2tu7n3zm0vn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxj54nmna2tu7n3zm0vn.png" alt="Module interaction" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How to Interpret This Graph:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Build Logic &amp;amp; Configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This section shows your AndroidLibraryConventionPlugin.kt (which defines how library modules should be built) and the libs.versions.toml (which provides the versions and dependency coordinates).
&lt;/li&gt;
&lt;li&gt;The convention plugin reads from the version catalog to get consistent dependency versions and other settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Module Build Definition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;feature_bookmarks/build.gradle.kts applies the convention plugin. This means the rules and configurations from AndroidLibraryConventionPlugin.kt are used to set up the :feature_bookmarks module.
&lt;/li&gt;
&lt;li&gt;app/build.gradle.kts would have its own configurations (potentially using an app-specific convention plugin if you create one, or applying Android application plugins directly).
&lt;/li&gt;
&lt;li&gt;These Gradle files define how their respective module code (feature_bookmarks module code, app module code) should be processed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compilation &amp;amp; Packaging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;During the Gradle build, the :feature_bookmarks module code is compiled into an .aar (Android Archive) library artifact.&lt;/li&gt;
&lt;li&gt;The :app module code is compiled, and it depends on and includes the :feature_bookmarks.aar.
&lt;/li&gt;
&lt;li&gt;Finally, the app module is packaged into an .apk (Android Package) file that can be installed on a device.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Application Runtime Flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The User interacts with the application.&lt;/li&gt;
&lt;li&gt;The App Runtime (code running from your :app module) handles the main UI, navigation, and overall application flow.&lt;/li&gt;
&lt;li&gt;When the user wants to access bookmarks, the App Runtime navigates to (or invokes) the Bookmarks Feature Runtime (code running from your :feature_bookmarks module). &lt;/li&gt;
&lt;li&gt;The Bookmarks Feature displays its UI and provides its specific functionality to the user.&lt;/li&gt;
&lt;li&gt;The dashed arrow from app_artifact to feature_bookmarks_artifact in this subgraph emphasizes that the compiled code from the feature module is part of, and executed within, the context of the application.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  💡 Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Successful Feature Module Creation: We’ve isolated the ‘bookmarks’ functionality into its own dedicated library module (:feature_bookmarks). This is the foundational step in modularizing your application, allowing for better separation of concerns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Centralized Build Logic with Convention Plugins: We established a convention-plugins module (specifically AndroidLibraryConventionPlugin.kt) to define and apply common build configurations (Android library settings, Kotlin options, Java compatibility, common dependencies) across library modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Standardized Dependency Management: We’ve consistently used the Gradle Version Catalog (libs.versions.toml) within the convention plugin to manage dependency versions and plugin aliases. This ensures consistency and makes version updates much easier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refined Build Scripts: Both the feature_bookmarks/build.gradle.kts and the convention plugin’s build scripts have been refined to be cleaner, more maintainable, and leverage modern Gradle practices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modern Kotlin &amp;amp; Compose Setup: We addressed requirements for Kotlin 2.0+, including the explicit application of the Jetpack Compose Compiler plugin and correctly configuring Kotlin’s jvmTarget using the compilerOptions DSL.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Follow the process for effective results: Isolate -&amp;gt; Configure Manually -&amp;gt; Identify Pattern -&amp;gt; Create Convention Plugin -&amp;gt; Apply Plugin -&amp;gt; Repeat.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the complete implementation and to explore the code in context, you can find the full project on &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/vsay01/PinterestStyleGridDemo/tree/modularization" rel="noopener noreferrer"&gt;PinterestStyleGridDemo Repository&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Thanks for reading! I hope you found this guide on modularizing Android app helpful.&lt;/p&gt;

&lt;p&gt;If you enjoyed this, you'd love my other articles.&lt;/p&gt;

&lt;p&gt;Subscribe to my free newsletter to get my latest tutorials and coding insights delivered directly to your inbox. No spam, ever. Unsubscribe at any time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vsaytech.hashnode.dev/newsletter" rel="noopener noreferrer"&gt;Subscribe Now&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd also love to hear from you! What's your experience with modularizing the Android app? Let me know in the comments below 👇&lt;/p&gt;

</description>
      <category>android</category>
      <category>modularization</category>
      <category>architecture</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
