일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- livedata
- pandas
- ProGuard
- Rxjava
- Kotlin
- 코드포스
- 프로그래머스
- MyVoca
- Gradle
- Hilt
- TEST
- 코루틴
- Compose
- textfield
- androidStudio
- Coroutines
- GitHub
- relay
- 백준
- Python
- MiTweet
- Codeforces
- activity
- Coroutine
- 쿠링
- 암호학
- AWS
- android
- boj
- architecture
- Today
- Total
이동식 저장소
Gson에서 null이 반환될 때 with ProGuard 본문
알 수 없는 에러
Proguard를 적용한 후 이상한 에러가 발생한다.
java.lang.NullPointerException: Parameter specified as non-null is null:
method com.practice.hanbitlunch.screen.Menu.<init>, parameter name
at com.practice.hanbitlunch.screen.Menu.<init>(Unknown Source:2)
at com.practice.hanbitlunch.screen.MainUiStateKt.a(Unknown Source:52)
at com.practice.hanbitlunch.screen.MainScreenViewModel.e(Unknown Source:89)
at j4.b$a.j(Unknown Source:24)
at j4.b$a.K(Unknown Source:12)
at u5.o.j(Unknown Source:43)
at u5.o.I(Unknown Source:17)
at v5.j$a$a$a.j(Unknown Source:34)
at v5.j$a$a$a.K(Unknown Source:12)
at r5.a.x0(Unknown Source:25)
at a0.c.Z(Unknown Source:46)
at a0.c.a0(Unknown Source:11)
... (하략)
Non-null인 ``Menu`` 객체의 파라미터로 null이 주어진다는 뜻이다. 무슨 말인지 이해하기 위해 ``Menu``의 정의를 보자.
data class Menu(
val name: String,
)
그냥 메뉴의 이름을 담는 단순한 pojo 객체이다. 뭐가 null이라는 거지?
흠..
1시간 정도 고민한 결과 잘 모르겠다. 그래서 데이터가 전달되는 과정을 추적하기로 했다. ``Room``에 저장된 식단 정보는 ``DataSource``, ``Repository``, ``UseCase``, ``ViewModel``을 거쳐 UI에 전달되는데, 어딘가에서 null로 바뀌었을 가능성이 높다.
일단 ``Room``에는 제대로 저장되어 있다. App Inspection에서 확인할 수 있다.
그렇다면 전달 과정에서 에러가 났다는 뜻이다. 이제 어느 객체에서 오류가 발생했는지 확인해 보자.
추적 60분
로그를 찍어 본 결과, ``UseCase``에서 Room entity를 domain의 객체로 변환할 때 에러가 발생했다.
``UseCase``는 변환 과정에서 ``Gson``을 사용하여 json 문자열을 Kotlin 객체로 변환한다. 다른 앱에서도 ``Gson``이 종종 문제를 일으키곤 했었는데, 사실 이건 ``Gson``의 문제라기보단 data parser의 본질적인 속성 때문이다.
``Gson``은 변수의 이름을 참조하여 객체를 만드는데, R8 컴파일러가 변수의 이름을 바꾸거나 아예 지워버리면 ``Gson`` 입장에서는 답이 없다. ``Menu`` 클래스의 ``name`` 속성이 ``a``로 바뀌었다고 해 보자. Json에는 당연히 ``a``라는 이름의 변수가 존재하지 않는다. 값이 없으니 ``Gson``은 항상 하던 대로 null을 넣어 반환하고, 그렇게 모든 비극이 시작되는 것이다.
해결방법
구글도 이 문제를 알고 있어서, GitHub 리포지토리에 이렇게 언급하고 있다.
ProGuard is a tool for 'shrinking' and obfuscating... (중략) or remove them if they appear to be unused. This can cause issues for Gson which uses Java reflection to access the fields of a class. It is necessary to configure ProGuard to make sure that Gson works correctly.
Gson과 ProGuard를 함께 사용하면 문제가 발생할 수 있으니, ProGuard가 Gson을 방해하지 않도록 구성할 필요가 있다. App level의 ``proguard-rules.pro`` 파일에 다음의 코드를 추가하자.
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
맨 위의 ``-keep class`` 뒤에 Gson으로 변환되는 클래스의 이름을 적어야 한다. 에러 난 것처럼 빨간 줄이 그어져도 상관 없다.
이제 앱을 다시 빌드해 보자. 객체가 제대로 변환된다!
후기
휴.. 이제 편히 잘 수 있겠다.
'Primary > Android' 카테고리의 다른 글
[Android] APK와 AAB (0) | 2022.10.13 |
---|---|
[Android] Proguard가 사람 잡는다 (0) | 2022.10.01 |
[Android] 디버깅할 때 앱이 느리다면 (0) | 2022.09.25 |
[Hilt] Entry Points (0) | 2022.09.10 |
[Hilt] Modules (0) | 2022.08.29 |