2021. 11. 19. 17:52ㆍProgramming/Flutter
예쓰! 검은곰입니다. 오늘은 Flutter의 isolate와 shared_preferences에 대한 내용을 정리하려고 합니다. 정확히는 이번에 굉장히 골머리를 겪게했던 이슈에 대한 정리로, 여러분들은 Isolate 내에서는 native 코드에 접근할 수 없다. shared_preferences 패키지는 native 코드의 SharedPreferences를 참조하므로, 사용할 수 없다.
라는 내용만 기억하시면 됩니다. 개발자로써 코드를 작성하다보면 오만가지 코드와 상황을 만나게 되기 마련이지만, 저랑 비슷한 상황을 만나기는 쉽지 않을 거거든요.
아무튼 isolate에서 shared_preferences에 저장해놓은 값을 읽어서 분기처리해야 하는 상황에 처했습니다. 보통 직접 isolate를 생성하는 경우에는 Dart Isolate 2-Way Communication, Leland Zach와 같은 방식으로 통신을 하면 됩니다. 당연히 '요렇게 하면 됩니다!'라고 답을 제시했을 때 어렴풋이 눈치채셨겠지만, 저는 이런 방식을 사용할 수 없는 상황이었죠. FirebaseMessaging.onBackgroundMessage()
의 결과로 생성된 isolate에서 값을 참조해야했기 때문입니다.
Handling messages whilst your application is in the background is a little different. Messages can be handled via the onBackgroundMessage handler. When received, an isolate is spawned (Android only, iOS/macOS does not require a separate isolate) allowing you to handle messages even when your application is not running.
공식 문서에 따르면 애석하게도 안드로이드만 isolate가 spawn된다고 기재되어있습니다. 맙소사. 아무튼 백그라운드에서 FirebaseMessage를 수신했을 때 spawn되는 isolate와 통신할 방법은 딱히 없어 보입니다.
결국 안드로이드 Natvie에서 제공하는 SharedPreferences도 어딘가에 값을 저장해놓는 형태. 이 방법 저 방법을 다 쓰다 결국 shared_preferences 패키지의 코드를 까본 뒤에야 Native 코드로 구현된 것을 발견한 저는, 그만 정신줄을 놓아버린 채 '야, 그냥 파일에 써버리면 isolate고 나발이고 다 읽을 수 있는 거 아니냐?'라는 1차원적인 결론에 도달하고 맙니다. 결론만 말하자면 isolate에서도 path_provider의 getApplicationSupportDirectory()
에 접근이 가능하므로, 여기에 적당한 구조로 파일을 작성해서 저장하면 shared_preferences와 비슷한 방식으로 사용이 가능합니다. 아래는 단순히 Map<String, dyanmic>
타입으로 작성된 객체를 stringify된 json형식으로 만들어, 파일에 저장하고 불러오는 코드입니다. 하지만 이렇게 해서, Isolate에서도 접근 가능한 SharedPreferences 비슷한 녀석을 만들어줄 수 있죠.
Future<File> get _localFile async {
Directory directory = await getApplicationDocumentsDirectory();
return File("${directory.path}/settings.json");
}
Future<Map<String, dynamic>> readSettingsFromFile() async {
final file = await _localFile;
Map<String, dynamic> settings;
try {
final contents = await file.readAsString();
settings = jsonDecode(contents);
} catch (e) {
settings = {
"booleanTypeSettingA": true,
"booleanTypeSettingB": false,
"numberTypeSettingA": 0,
"numberTypeSettingB": 0,
"numberTypeSettingC": 0,
"stringTypeSettings": "foo",
"stringTypeSettings": "bar",
};
file.writeAsString(
jsonEncode(settings),
);
}
return settings;
}
Future<void> saveSettingsToFile<T>(String key, T value) async {
final file = await _localFile;
Map<String, dynamic> settings = await readSettingsFromFile();
settings.containsKey(key)
? settings[key] = value
: settings.addAll({key: value});
await file.writeAsString(
jsonEncode(settings),
);
}
_localFile()
_localFile()
은settings.json
을 불러오는 코드입니다.readSettingsFromFile()
와saveSettingsToFile()
에서 공통으로 사용하는 코드이므로, 별도의 함수로 분리했습니다.
readSettingsFromFile()
settings.json
파일을 읽어서,Map<String, dynamic>
값을 반환해주는 함수입니다.localFile()
을 통해settings.json
파일을 불러오는떼 실패할 경우, 기본값이 설정된Map<String, dyanic>
값을 생성해줍니다.
saveSettingsToFile<T>(String key, T value)
- 키값과 저장할 값을 인자로 받아, settings에 키-값 쌍을 저장하고, 파일에 값을 씁니다. 만약
settings
에 키값이 포함되어있지 않다면, 키값을 추가해줍니다.
- 키값과 저장할 값을 인자로 받아, settings에 키-값 쌍을 저장하고, 파일에 값을 씁니다. 만약
지금 상태에서는 Map<String, dynamic>
을 다루기때문에 키값이 항상 문자열이어서, 코드를 작성할 때 오타로 인한 휴먼 에러가 발생할 소지가 있긴 합니다. 이런 휴먼 에러를 방지하려면, 이전에 만들었던 SharedPreferencesHelper
같은 녀석을 만들어주면 되겠죠.
오늘은 여기까지. Isolate
에서는 native 영역에 접근할 수 없기 때문에, 패키지로 제공되는 SharedPreferences에 접근할 수 없다는 점. 그리고 이를 회피하기 위해서 파일에 값을 저장하고 써서, SharedPreferences를 대체하는 방법에 대해 알아봤습니다.
혹시 질문이나 혹은 잘못된 점은 댓글로 달아주세요!
잘못된 점은 최대한 빠르게 수정하도록 하겠습니다. ' ㅂ')/