Skip to main content
https://mintcdn.com/halo-c614fc4d/n1cCDjQImiwl-ZA3/images/authors/mambo.png?fit=max&auto=format&n=n1cCDjQImiwl-ZA3&q=85&s=08273d91c4d009986a2472edd13b0aa4

Maverick Mambo

Pathfinder @ Thamani
Hero Light
Resources should be central and feature agnostic
Every Android application has resources — images, videos, animation files, you name it. Once you start modularising an application, sharing those resources gets tricky.

The Problem

Every new feature module comes with its own set of resources. Sometimes a resource is needed in more than one module, so we duplicate it. Do this enough times and you end up with something like:

Example
project
features
featureA
res
drawable
my_image.png
featureB
res
drawable
my_image.png

Problems
  • duplicated resources
  • increased application size
  • harder to maintain (an update to the image requires you to update it in all modules its used)
Although tempting, AVOID feature interdependency, featureA shouldn’t know about featureB

The Shared resources Module

Create a single module, put all your shared resources inside it, and have every feature module depend on it.
This solves all the problems above.
1

create a new module

We will introduce a new module called resources

app
features
resources (your new module)
Keep this module lean. It shouldn’t need many dependencies
2

add all your resources into the new module

app
resources
res
drawable
my_image.png
3

add the resources module into each feature

Add the resources module as a dependency in each feature
build.gradle.kts
implementation(project("resources"))
If you’re going to repeat this for each module, you could create a convention plugin for it
4

using the resources

Import the R class from your new module and use the resources as usual

import {your.new.module.path}.R

...

@Composable
fun Composable(){
    val stringId = R.string.title            // for strings
    val drawableId = R.drawable.my_image    // for images
    val rawFileId = R.raw.raw_file          // for raw files
    ... and so forth
}
You now have one module for all your resources, shared throughout the app.
But there’s always more 😉

The Thamani Way

At Thamani, we think about developer experience the same way we think about user experience — if something feels off, it’s worth fixing. The shared resources module worked, but using it didn’t feel right. The problem was the R class. You’d import it, autocomplete would show you two options, and you’d have to pause and think: is this R from my feature module or from the shared resources module? That small friction added up across the codebase. So we wrapped the shared R class in a helper that makes the source obvious at a glance:

internal typealias ThamaniResourcesRaw = R.raw
internal typealias ThamaniResourcesString = R.string
internal typealias ThamaniResourcesDrawable = R.drawable
...

object ThamaniResource {
    typealias Raw = ThamaniResourcesRaw
    typealias String = ThamaniResourcesString
    typealias Drawable = ThamaniResourcesDrawable
    ...
}

Now instead of guessing which R you’re looking at, you write this:

@Composable
fun Composable(){
    val stringId = ThamaniResource.String.title            // for strings
    val drawableId = ThamaniResource.Drawable.my_image    // for images
    val rawFileId = ThamaniResource.Raw.raw_file           // for raw files
    ... and so forth
}
When you read ThamaniResource.Drawable.my_image, you know exactly where it lives. No second-guessing. Small details like this are what turn a working codebase into one that’s actually pleasant to work in — and at Thamani, that matters to us just as much as the experience our users get.