문제의 시작

음.. Hilt를 사용하여 Dependency Injection 을 하고 있는 프로젝트가 있다. Module을 통해 객체를 생성하고 필요한 곳에 주입을 하는 대충 이런 느낌의 것이다.

필자는 EncryptedSharedPreference 라는 Android의 보안 라이브러리 중 하나를 쓰고 있다. 해당 라이브러리는 앱에서 설정값을 저장할 때 Key, Value 모두 암호화 하여 값을 읽고 쓸 수 있도록 도와준다.

아무튼, 본인은 저장되는 값들의 도메인이 각각 다르기에 따로 파일을 나눠 저장하고 싶었다. 그런데, Di를 사용하고 있어 같은 자료형에 대해서 구분해줄 무언가가 필요했다. 그래서 이 방법에 대해 작성하려 한다.


Qualifier를 사용하자

Qualifier 지정

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Qualifier
    @Retention(AnnotationRetention.BINARY)
    annotation class AppSharedPreference

    @Qualifier
    @Retention(AnnotationRetention.BINARY)
    annotation class ConfigSharedPreference
  ...
}

예시로 앱 설정에 관한 것들을 저장하는 SharedPreference 객체와, 다른 설정값들을 저장하기 위한 SharedPreference 객체를 생성하기 위해 두 가지로 분리하여 지정해보도록 하겠다.

SharedPreference 객체 생성

private val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
private val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

EncryptedSharedPreference를 사용하기 위한 key생성을 한다.

그 다음 각각 다른 파일로 생성할 같은 SharedPreference 객체를 만든다.

@AppSharedPreference
@Provides
@Singleton
fun provideAppPreference(@ApplicationContext context: Context): SharedPreferences {
    return EncryptedSharedPreferences.create(
        "app_pref",
        mainKeyAlias,
        context,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
}

@ConfigSharedPreference
@Provides
@Singleton
fun provideConfigPreference(@ApplicationContext context: Context): SharedPreferences {
    return EncryptedSharedPreferences.create(
        "config_pref",
        mainKeyAlias,
        context,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
}

위 코드에서 눈여겨보아야 할 부분은 @AppSharedPreference@ConfigSharedPreference 이다. 위에서 @Qualifier 로 만들어놓은 한정자를 사용한다.


사용할 땐 어떻게 구분할까?

그럼 실제 클래스에서 사용할 땐 아래와 같이 주입받을 수 있다.

class AppPreference @Inject constructor(
    @AppModule.AppSharedPreference private val appPreference: SharedPreferences,
    @AppModule.ConfigSharedPreference private val configPreference: SharedPreferences
) {

    fun getBoolValue(): Boolean {
        return appPreference.getBoolean("key_boolean", false)
    }

    fun getConfigValue(): Boolean {
        return configPreference.getBoolean("config_boolean", true)
    }
}

이렇게 되면 각각 다른 Preference에서 원하는 값을 가져올 수 있다. 물론 지정할 때도 마찬가지.

AppDataImage

실제 앱 저장소를 따라 가보면 원하는 대로 파일이 구분되어 생성된 것을 볼 수 있다.