안드로이드에서 Preference 관리

오리·2024년 8월 22일

안드로이드 애플리케이션에서 SharedPreferences를 사용하는 방법에는 여러 가지가 있다.

전역 변수 방식 / DI 방식

전역 변수 방식

전역 변수 방식에서는 애플리케이션 전역에서 SharedPreferences 인스턴스를 접근할 수 있도록 설정한다.

// 애플리케이션의 전역에서 접근할 수 있는 MintPreference 객체를 선언
@HiltAndroidApp
class App : Application() {

  companion object {
    lateinit var mintPreference: MintPreference
      private set
  }

  // 애플리케이션 초기화 시 MintPreference 인스턴스를 생성하고 초기화
  override fun onCreate() {
    super.onCreate()
    mintPreference = MintPreference(applicationContext)
  }
}

@HiltViewModel
class MintViewModel @Inject constructor() : ViewModel() {

  init {
    // App 클래스에서 생성된 mintPreference 인스턴스를 사용하여 id 값을 설정
    App.mintPreference.id = "A"
  }
}

장점:

  • 접근이 간편하고 코드가 직관적이다.
  • 설정 값에 접근할 때마다 DI 설정을 거치지 않아도 된다.

단점:

  • 테스트가 어려워지고, 코드가 의존적이 되어 유지보수가 어렵다.
  • 멀티스레드 환경에서 안전하지 않을 수 있다.

추천:

  • 설정 값 접근이 단순하고 빈번한 경우 간편하게 사용할 수 있지만, 적절한 초기화와 스레드 안전성에 주의해야 한다.

DI(Dependency Injection) 방식

DI 방식을 사용하면 의존성 주입을 통해 SharedPreferences 인스턴스를 관리한다.

@InstallIn(SingletonComponent::class)
@Module
class PreferenceModule {
  // Application 인스턴스를 사용하여 MintPreference 인스턴스를 생성하고 반환
  @Provides
  @Singleton
  fun provideMintPreference(application: Application): MintPreference {
    return MintPreference(application)
  }
}

@HiltViewModel
class MintViewModel @Inject constructor(
  // MintPreference 인스턴스를 주입받음
  private val mintPreference: MintPreference
) : ViewModel() {

  init {
    // ViewModel 초기화 시 MintPreference의 id 값을 설정
    mintPreference.id = "A"
  }
}

장점:

  • 코드가 더 모듈화되고 테스트하기 쉽다.
  • 의존성을 명확히 하고, 멀티스레드 환경에서 안전하다.

단점:

  • DI 설정이 번거로울 수 있고, 초기 학습 비용이 있다.
  • 설정 값 접근 시 DI 프레임워크를 통해 접근해야 하므로 약간의 성능 오버헤드가 있을 수 있다.

추천:

  • 유지보수성과 테스트 용이성을 중시하는 경우 DI를 사용하는 것이 좋다. 앱의 복잡도가 높아질수록 DI의 장점이 두드러진다.

여러 개의 Preference 사용 시 주의사항

class APreference(context: Context) : BasePreference(context) {
  var id by stringPref("")
}

class BPreference(context: Context) : BasePreference(context) {
  var id by stringPref("")
}

@HiltViewModel
class MintViewModel @Inject constructor(
  private val aPreference: APreference,
  private val bPreference: BPreference
) : ViewModel() {
  init {
    aPreference.id = "A"
    bPreference.id = "B"

    // 예상과 다르게 BPreference의 id 값이 APreference에 반영됨
    Log.d("APreference", aPreference.id)  // 출력: "B"
  }
}

원인

문제의 원인은 stringPref 함수가 내부적으로 동일한 key 값을 사용하기 때문이다.

/**
 * Delegate string shared preferences property.
 * @param default default string value
 * @param key custom preferences key
 * @param commitByDefault commit this property instead of apply
 */
protected fun stringPref(
    default: String = "",
    key: String? = null,
    commitByDefault: Boolean = commitAllPropertiesByDefault
): AbstractPref<String> = StringPref(default, key, commitByDefault)

해결법은 stringPref의 key 값을 유니크하게 설정해주는 것

class APreference(context: Context) : BasePreference(context) {
  var id by stringPref("", key = "Aid")
}

class BPreference(context: Context) : BasePreference(context) {
  var id by stringPref("", key = "Bid")
}

0개의 댓글