Using String Resources in a ViewModel

Margin KS
2 min readAug 1, 2023

--

When creating Android apps, One common pitfall is the use of string resources in ViewModels.

In this article, we’ll investigate why incorporating String resources directly into ViewModels can pose issues and examine alternative methods for dealing with them.

One way we can use Strings in ViewModel is by using AndroidViewModel

 
class MainViewModel(application: Application) : AndroidViewModel(application) {
fun getString(): String? {
return getApplication<Application>().resources.getString(R.string.sample_string)
}
}

This approach has some drawbacks

  1. Separation of Concerns: ViewModels are primarily responsible for managing the state and behaviour of the UI components in an Android app, while strings and other resources typically belong to the View layer. Mixing strings with ViewModel logic violates the separation of concerns principle and can lead to a more tightly coupled and less maintainable codebase.
  2. Testability and Unit Testing: ViewModels need to be easily testable. However, using string resources within ViewModels can make it difficult to write thorough unit tests for these components because they require context.
  3. Viewmodel won’t be recreated for Locale changes: If the string resource is used in the ViewModel’s constructor, it is resolved only once. This can cause a problem if there is a change in locale, as the ViewModel will not be recreated. As a result, the app may display outdated data and will not be completely localized.

the recommended approach is to handle the string resource-related task from the UI layer. The view (activity, fragment) will be recreated after a configuration change since it is lifecycle-aware. This means that the resource will be reloaded correctly, and we can also avoid using context in the ViewModel.

For this, we need a sealed class

sealed class StringValue {

data class DynamicString(val value: String) : StringValue()

object Empty : StringValue()

class StringResource(
@StringRes val resId: Int,
vararg val args: Any
) : StringValue()

fun asString(context: Context?): String {
return when (this) {
is Empty -> ""
is DynamicString -> value
is StringResource -> context?.getString(resId, *args).orEmpty()
}
}
}

in the ViewModel

private val _logMessage by lazy { MutableLiveData<StringValue>() }
val logMessage: LiveData<StringValue>
get() = _logMessage

Update the live data with the resource id

_logMessage.postValue(StringResource(R.string.invalid_type))

in your fragment/activity, inside live data observer use it like this

 logMessage.observe(this@MainActivity) { 
debug(TAG, it.asString(this@MainActivity))
}

It eliminates the need for context in the ViewModel, and even dynamic strings obtained from APIs can be handled easily 🙌🏼👍🏼

I hope this is helpful to someone and makes their life a bit easier. Happy coding! 👨‍💻

Thanks to Philipp Lackner, Preetham & Annapoorna.

--

--

Margin KS

I am a Senior Android Engineer who loves getting creative and enjoys daily challenges