Translatable Strings In View Models, Without Android SDK imports

aniketsmk

Aniket Kadam

Posted on December 7, 2019

Translatable Strings In View Models, Without Android SDK imports

The Problem

You've gotten all the logic tested. Now you just need to turn that last

enum class TimesOfDay(@StringRes val displayNameStringId: Int) {
    MORNING(R.string.morning),
    NIGHT(R.string.night)
}

to the text "Morning Sunshine!" or "Good night sweetie!"

But to do it in such a way that it can be translated to other languages easily, you can't just use a string. You'd need to go through the Resources, or Context's getString methods.

Drat!

The Solution

DI to the rescue!

Following a standard DI pattern, we're going to put the real Android implementation behind an interface and inject that interface into the place we need it!

Interface

We start off with the basic interface we need

import androidx.annotation.StringRes

interface TranslationStringMapper {
    fun getStringForId(@StringRes id: Int): String
}

This ensures you'll only be passing String Resource ids in there, not just any old Int in production.

Concrete Class for the Interface

This class extends the interface and provides the real version that we'll be using in production.

import android.app.Application
import android.content.res.Resources
import javax.inject.Inject

class AndroidTranslationStringMapper @Inject constructor(application: Application) :
    TranslationStringMapper {

    val res: Resources = application.resources

    override fun getStringForId(id: Int): String = res.getString(id)

}

Injecting the interface where needed

You may not need this if you're not using View Models, but the idea can be adapted to presenters just as well.

class EmotionJourneyViewModelFactory @Inject constructor(
    private val emotionDao: EmotionDao,
    private val stringMapper: TranslationStringMapper
) : ViewModelProvider.Factory { ... }

This will get you the TranslationStringMapper to the ViewModel, or you can directly inject it into the Presenter if that's what you use.

Where do you get the TranslationStringMapper?

Now, there are a few ways to do this but here's one I like the most.
All we need, is a to use the AndroidTranslationStringMapper from earlier, and tell Dagger that we want to use this wherever it's needed.

But the right place to do this, something that could be used app-wide and is just a cast from the concrete to the interface, is....

AppModule!

If you're used to Dagger you'd have seen the AppModule. It's usually binding the application to a context.
Binds, should be preferred when you're just casting from one object to another and that's exactly what we're doing here!
The scope is perfect too since this truly could be used anywhere in the app, and it'll be super easy to change the concrete implementation of the StringMapper in one centralized location rather than each individual activity or fragment module.

That looks like:

@Module
abstract class AppModule {
    @Binds
    abstract fun bindApplication(app: DaggerApplication): Application

    ...

    @Binds
    abstract fun bindStringMapper(androidTranslationStringMapper: AndroidTranslationStringMapper): TranslationStringMapper

}

Conclusion

That's it. Now the translations will be available wherever you need them and you can even quickly mock out responses if you wanted to, while always using just the enum representation in tests if that's what you prefer.
This is really great use of Dagger that lets you forget about where the translation is coming from.

Notes: It's really important that you use the resources from Application rather than an activity context if you're going to pass it into a ViewModel. The ViewModel is preserved beyond the Activity lifecycle and waaay past the fragment lifecycle and using either of these could cause serious problems like memory leaks and exceptions.


I'm currently looking for a job as a senior or lead android developer so if you're hiring and want someone to supercharge your teams and raise everyone up, while learning from them too. Reach out to me at anikadamg att gmail!

💖 💪 🙅 🚩
aniketsmk
Aniket Kadam

Posted on December 7, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related