String Resource Provider Using Dependency Inversion In Android

Badea Mihai Bogdan
3 min readDec 2, 2022

--

Midjourney generated image

Whatever architecture you are using in your application, the high-level components shouldn’t have a direct dependency on Android classes like context, fragment, resources or others because they should be reusable and unaffected by changes in lower level components or Android classes.

A common use case is constructing objects in a application-level layer that needs access to string resources. There are several solutions to keep the layer free from context or other Android objects, but we will cover a resource provider that will have an interface in your high-level classes that has an implementation containing a Resources object, basically decoupling the layer from Android dependencies, or infra layer, by inverting the dependency.

Please note that this implementation is for demonstrating the power of the Dependency Inversion Principle by retrieving a string with a parameter. Also take into account locale configurations changes as detailed here, and for simple use cases involving only a resource id, you should pass it directly to the view layer.

Dependency Inversion Principle short summary

The Dependency Inversion Principle (DIP) states that:

“high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions.”

This help us to decouple high-level layer from Android or infra layer.

Our sample use case

Suppose we have an use case that needs to access a string from android resources and compose a message, let’s name it: GetWelcomeMessageUseCase.

import android.content.res.Resources

class GetWelcomeMessageUseCase(private val resources: Resources) {
fun get(userFullName: String): String =
resources.getString(R.string.welcome_message, userFullName)
}

Removing Android dependency

To remove android.content.res.Resources dependency from this use case we will use an interface, StringResourceProvider:

import androidx.annotation.StringRes

interface StringResourceProvider {
fun getString(@StringRes resourceId: Int, parameter: String): String
}

Refactoring GetWelcomeMessageUseCase

Next we will refactor GetWelcomeMessageUseCase by replacing Resources with our newly created interface and that’s it.

class GetWelcomeMessageUseCase(private val stringResourceProvider: StringResourceProvider) {
fun get(userFullName: String): String =
stringResourceProvider.getString(R.string.welcome_message, userFullName)
}

Now our application-level class, GetWelcomeMessageUseCase, depends upon an abstraction, StringResourceProvider interface, and not Android component.

We have a Clean Code now! 🤩

StringResourceProvider implementation

And the implementation of StringResourceProvider interface:

import android.content.res.Resources

class StringResourceProviderImplementation(private val resources: Resources) :
StringResourceProvider {

override fun getString(resourceId: Int, parameter: String): String =
resources.getString(resourceId, parameter)
}

ViewModel with GetWelcomeMessageUseCase

Following is the full integration of the mentioned use case in a ViewModel:

class WelcomeViewModel(private val getWelcomeMessageUseCase: GetWelcomeMessageUseCase) :
ViewModel() {
val welcomeMessageLiveData = MutableLiveData<String>()

private val userName = "Mihai"

fun displayWelcomeMessage() {
welcomeMessageLiveData.value = getWelcomeMessageUseCase.get(userName)
}
}

Full code integration

ViewModel will be available in your Fragment or Activity using a ViewModelFactory. Please note that for a clean production code in a large app you should use a Dependency Injection framework or Service Locator pattern. Also ViewModel creation is different and you should follow ViewModel’s documentation.


class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModelFactory =
MyViewModelFactory(StringResourceProviderImplementation(resources))

val welcomeViewModel = viewModelFactory.create(WelcomeViewModel::class.java)

welcomeViewModel.welcomeMessageLiveData.observe(this) { welcomeMessage ->
Log.d(MainActivity::class.java.simpleName, welcomeMessage)
}

welcomeViewModel.displayWelcomeMessage()
}
}

class MyViewModelFactory(private val stringResourceProvider: StringResourceProvider) :
ViewModelProvider.Factory {

override fun <T : ViewModel> create(modelClass: Class<T>): T =
WelcomeViewModel(GetWelcomeMessageUseCase(stringResourceProvider)) as T
}

Unit testing

By using a fake StringResourceProvider as a test double, we could easily assert that our GetWelcomeMessageUseCase implementation is correct.

class GetWelcomeMessageUseCaseTest {
private val fakeStringResourceProvider = object : StringResourceProvider {
override fun getString(resourceId: Int, parameter: String): String = when (resourceId) {
R.string.welcome_message -> String.format("Test %s", parameter)
else -> ""
}
}

private val getWelcomeMessageUseCase = GetWelcomeMessageUseCase(fakeStringResourceProvider)

@Test
fun displayWelcomeMessage() {
val name = "Badea Mihai Bogdan"
val welcomeMessage = getWelcomeMessageUseCase.get(userFullName = name)
Assert.assertEquals("Test Badea Mihai Bogdan", welcomeMessage)
}
}

Conclusion

We have seen that by using the Dependency Inversion Principle we can achieve cleaner code, improve maintainability and not pollute the application layer with Android framework specific software components, also it allows us to unit test without Android dependencies.

You could follow me also on Twitter for more tech related subjects.

--

--

Badea Mihai Bogdan

Senior Android Software Engineer with focus on Clean Code, Clean Architecture and TDD.