[Flutter] Flutter에서 SharedPreferences에 저장한 값을, Android 네이티브 영역에서 참조해보자.

2021. 10. 28. 18:42Programming/Flutter

반응형

여기서는 Flutter 패키지 중 SharedPreferences를 지원하는 shared_preferences 패키지를 사용합니다. 다른 패키지를 사용하는 경우, 이 글의 내용은 별 다른 쓸모가 없을 가능성이 높습니다.

Flutter에서의 Shared Preferences

요구사항을 구현하다보면 값을 저장해야되는 경우가 생깁니다. 복잡한 데이터라면 데이터베이스를 사용하는 방법이 좋겠지만, 간단한 플래그나 설정값 등을 저장하기 위해서 데이터베이스를 사용하긴 아무래도 부담되기 마련입니다. 이럴 때 우리는 Shared Preferences를 사용하곤 합니다. Flutter에서도 이런 요구사항은 당연히 필요하기 마련인지, shared_preferences 패키지가 제공됩니다. 이번 글에서는 shared_preferences를 사용하여 저장해놓은 값을, Android Native 코드에서 읽는 방법에 대해 간단하게 정리합니다.

shared_preferences 패키지를 까보자!

우선은 shared_preferences가 Android와 어떻게 연동하고 있는지 파악해봅시다. shared_preferences 패키지 자체는 싱글톤 패턴으로 작성되어있는데, getInstance() 메소드를 따라가보면 아래와 같은 방법으로 Android 네이티브 코드와 연동하고 있는 것을 볼 수 있습니다.

const MethodChannel _kChannel = MethodChannel('plugins.flutter.io/shared_preferences');

안드로이드 스튜디오를 켜서 shared_preferences 패키지 내에 선언된 MethodCallHandlerImpl의 생성자를 살펴보면 다음과 같습니다.

private static final String SHARED_PREFERENCES_NAME = "FlutterSharedPreferences";

...

MethodCallHandlerImpl(Context context) {
  preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
  executor =
      new ThreadPoolExecutor(0, 1, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
  handler = new Handler(Looper.getMainLooper());
}

FlutterSharedPreferences라는 키 값으로 저장된 SharedPreferences를 읽어와서, 여기에 값을 세팅하는 걸로 보이죠. 내친김에 MethodCall 부분에서 setBool을 처리하는 코드를 살펴보면 다음과 같습니다.

public void onMethodCall(MethodCall call, MethodChannel.Result result) {
  String key = call.argument("key");
  try {
    switch (call.method) {
      case "setBool":
        commitAsync(preferences.edit().putBoolean(key, (boolean) call.argument("value")), result);
        break;

        ...

단순히 keyvalue를 인자로 받아서 SharedPreferences에 저장하고 있는 걸 볼 수 있죠. 자, 그럼 이제 Flutter에서 SharedPreferences에 저장한 값을 Android에서 확인해봅시다.

Android 네이티브 코드와 Flutter가 SharedPreferences를 참조하는 방법

Flutter에서 SharedPreferences에 값을 저장하는 방법

테스트를 위해 Flutter에서 shared_preferences에 값을 저장해봅시다.

import 'package:shared_preferences/shared_preferences.dart';

Future<void> test() async {
  SharedPreferences _sharedPreferences = await SharedPreferences.getInstance();
  await _sharedPreferences.setString("KEY", "Hello, world!");

  print(_sharedPreferences.getString("KEY") ?? "Can't find KEY value from SharedPreferences.")
}

test() 함수에서는 shared_preferences 패키지를 사용해서, shared_preferences에 "Hello, world!"라는 문자열을 "KEY" 키값에 저장하고 있습니다. 그 뒤, "KEY" 키값으로 저장된 문자열을 불러와 콘솔에 출력하고 있죠. test() 함수를 실행하면 Android 네이티브 코드에서도 불러올 수 있게 됩니다.

Flutter에서 SharedPreferences에 저장한 값을, Android 네이티브 코드에서 참조하는 방법 1

좋아, 이제 Android 네이티브 코드로 돌아와서 SharedPreferences에 저장된 값을 참조해보도록 합시다. 위에서 살펴봤듯 Android 네이티브 코드에서는 FlutterSharedPreferences라는 키값으로 저장된 SharedPreferences를 불러와서, 값을 처리합니다. 그렇다면 간단하게 다음과 같이 Kotlin 코드를 작성해봅시다. 대충 눈치챘겠지만, 다음의 코드는 제대로 동작했다면 이런 포스팅을 작성하는 일도 없었을겁니다. 아마도.

val sharedPreferences = this.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
val helloWorld = sharedPreferences.getString("KEY", "Can't find KEY value from SharedPreferences.")

Log.d("sharedPreferences", helloWorld)

위의 코드를 실행하면 예외가 발생해버립니다. FlutterSharedPreferences 키값으로 저장된 SharedPreferences를 불러온 것까지는 좋았지만, Flutter에서 "KEY"라는 키값으로 저장한 값을 찾을 수 없었기 때문이죠. 다음과 같이 수정해보면, Logcat에 Can't find KEY value from SharedPreferences.라고 메시지가 출력되는 걸 확인할 수 있을거에요.

val sharedPreferences = this.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
if (sharedPreferences.contains("KEY")) {
  val helloWorld = sharedPreferences.getString("KEY", "Can't find KEY value from SharedPreferences.")

  Log.d("sharedPreferences", helloWorld)
} else {
  Log.e("sharedPreferences", "Can't find KEY value from SharedPreferences.")
}

대단한 건 아니고, SharedPreferences에서 제공하는 contains()를 사용해서 키 값이 존재하는지 체크했습니다. 키 값이 없다면 logcat에 Can't find KEY value from SharedPreferences.라는 에러를 출력하게 되죠. 자, 그럼 키 값을 못 찾는 이유에 대해 살펴보도록 하죠.

shared_preferences의 prefix

SharedPreferences 클래스를 살펴보면 다음의 코드를 확인할 수 있습니다.

static const String _prefix = 'flutter.';

...

Future<bool> _setValue(String valueType, String key, Object value) {
  ArgumentError.checkNotNull(value, 'value');
  final String prefixedKey = '$_prefix$key';
  if (value is List<String>) {
    // Make a copy of the list so that later mutations won't propagate
    _preferenceCache[key] = value.toList();
  } else {
    _preferenceCache[key] = value;
  }
  return _store.setValue(valueType, prefixedKey, value);
}

대충 _prefix라는 변수명만 봐도 눈치를 채셨을텐데, shared_preferences에서 키 값을 저장할때는 앞에 flutter.라는 _prefix를 붙여서 저장합니다. 다시 Android로 돌아갑니다.

Flutter에서 SharedPreferences에 저장한 값을, Android 네이티브 코드에서 참조하는 방법 2

바로 _prefix값으로 작성되어있던 flutter.를 키값 앞에 붙여줍니다.

val sharedPreferences = this.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
if (sharedPreferences.contains("KEY")) {
  val helloWorld = sharedPreferences.getString("flutter.KEY", "Can't find KEY value from SharedPreferences.")

  Log.d("sharedPreferences", helloWorld)
} else {
  Log.e("sharedPreferences", "Can't find KEY value from SharedPreferences.")
}

이제 logcat을 확인해보면 Flutter에서 KEY값으로 저장했던 값이, Android 네이티브 코드에서 제대로 출력되는 걸 확인할 수 있습니다. 언제나 그렇듯 글을 쓰기로 마음먹었을 때랑은 다르게 글이 좀 길어져서, 슬슬 마치고 싶은데요. 한가지 걸리는게 있습니다.

 

Flutter에서 SharedPreferences에 값을 저장한 뒤, Android 네이티브 코드에서 SharedPreferences 값을 저장할 땐 항상 contains()를 사용해서 키값이 저장되어있는지 확인해야합니다. 키 값이 한 두 개 일때는 아무래도 상관없지만, 키 값이 늘면 늘어날수록 귀찮고 짜증나는 일이 아닐 수 없습니다. 귀찮으니 다음과 같이 Android 네이티브 함수에 SharedPreferences에 저장된 키값을 불러오는 함수를 작성합니다.

private inline fun <reified T> getValueFromSharedPreferences(key: String, defaultValue: T): T {
  val sharedPreferences = this.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
  val _key = "flutter.${key}"

  if (!sharedPreferences.contains(key)) {
    return defaultValue
  }

  return when (T::class) {
    String::class -> sharedPreferences.getString(_key, defaultValue as String) as T
    Int::class -> sharedPreferences.getInt(_key, defaultValue as Int) as T
    Long::class -> sharedPreferences.getLong(_key, defaultValue as Long) as T
    Boolean::class -> sharedPreferences.getBoolean(_key, defaultValue as Boolean) as T
    else -> defaultValue
  }
}

위 코드는 reified 키워드를 사용해서 T 타입의 원형을 참조할 수 있게 만든 뒤, 전달된 T값의 원형에 따라 처리를 하는 함수입니다. T는 인자로 전달된 defaultValue값이나 할당되는 변수에 따라 추론되며, 인자로 전달된 key 앞에 prefix인 flutter.를 붙여서 sharedPreferences를 참조하게 됩니다. 만약 contains()함수가 해당 키 값을 확인했을 때 false를 반환할 경우, 인자로 전달된 기본값 defaultValue를 반환하게 됩니다.

 

일단 제가 자주 쓰는 String, Int, Long, Boolean에 대해서만 대응하고 있지만, 필요한 값이 있으면 추가해서 쓰면 되겠죠. 이걸로 Flutter에서 shared_preferences 패키지를 사용해서 SharedPreferences에 값을 저장한 뒤, Android 네이티브 코드에서 SharedPreferences로 불러올 수 있게 됐습니다. 반대의 경우도 prefix만 유의한다면 별 문제 없겠죠.

 

이번 글은 여기서 마치도록 하겠습니다.
읽어주셔서 감사합니다. :)

반응형