-
Gson을 통해 API Response를 Enum으로 매핑하기Android 2020. 9. 25. 19:02
글을 시작하기 전에
안녕하세요, 블로그에는 굉장히 오랜만에 글을 게시하는 것 같네요. 최근 얼마전 안드로이드 개발자로 인턴을 시작하면서 시간이 많이 나지 않아 블로그에 신경을 많이 못썼던 것 같습니다. 그래도 역시 경험을 정리하는데는 블로그만한 것이 없는 것 같네요. 업무를 하면서 직면한 문제들과 해결한 경험에 대해 지속해서 포스팅 할 계획입니다. 그럼 오늘부터 시작해볼까요?
Adapter에 지나치게 많은 코드
(보안을 위해 코드는 비슷하게 작성하되, 일부 수정했습니다.)
class ViewHolder extends RecyclerView.ViewHolder { public void bind() { binding.setPoint(point); binding.createdAt.setText(changeToDate(point.getCreatedAt())); binding.description.setText( binding.getRoot().getContext().getResources().getIdentifier( point.getDescription(), "string", binding.getRoot().getContext().getPackageName())); binding.point.setText( point.getPoint() > 0 ? ("+" + point.getPoint()) : String.valueOf(point.getPoint())); if (point.getPoint() < 0) { binding.point.setTextColor(Color.parseColor("#ff5252")); } else { binding.point.setTextColor(Color.parseColor("#08c1ce")); } } }
서버에서 데이터를 받아오면 뷰홀더에 데이터를 매핑시키는 작업을 하게 되는데,
Date 포맷으로 넘어온 데이터를 원하는 형식에 맞게 포맷을 다시 설정하는 작업,
포인트의 수치에 따라 색과 텍스트의 양식이 달라지는 코드 등이 들어가있죠.
여기서 발견한 문제는
1. 데이터 바인딩을 사용하는데도 어댑터에 바인딩하는 코드를 많이 사용한 부분
2. binding.description.~~~ 하는 코드가 어떤 코드인지 이해하기 어려웠던 부분
3. 텍스트 색과 양식을 포인트에 따라 다르게 하는 코드를
더 효율적으로 작성할 수 없는가에 대한 의문
이 있었습니다.
특히 2번은 1번을 해결하던 중 발견한 부분인데 서버에서 영어로 된 데이터가 오면 다시 한글로 된 텍스트 데이터로 변환하는 작업입니다. 이 작업은 string.xml 에 영어 이름으로 된 리소스를 생성하고 내용을 한글로 만들어서 리소스의 id값과 서버에서 넘어온 데이터의 내용이 같으면 리소스의 내용을 넘겨줘서 변환을 시켜주는 방식이었어요.
리소스가 추가될 때마다 일일이 작업해야하고, 또 이런 방식을 이해하기도 어렵고 리소스가 어디있는지 알아내는데도 시간이 걸렸죠. 그래서 먼저 바인딩 작업부터 들어갔습니다.
데이터 바인딩을 통한 xml 내에서의 분기 처리
가장 먼저 어댑터에는 바인딩 변수를 주입시켜주는 부분만 남기고 나머지를 다 제거하기로 했습니다.
class ViewHolder extends RecyclerView.ViewHolder { public void bind() { binding.setPoint(point); } }
그리고 나머지 매핑 작업은 xml 내에서 이뤄지게 코드를 수정했습니다. 이렇게 되면 xml에서 다시 디버깅을 해야되지만 어댑터가 길어지는 것보다 xml내에서 직접 설정하는 것이 관리하기에도 훨씬 좋다고 생각했습니다.
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="point" type=".model.Point" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:padding="16dp">
<TextView android:id="@+id/point" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:fontFamily="sans-serif" android:gravity="end" android:letterSpacing="-0.02" android:textColor="@{point.point > 0 ? @color/tab_selected : @color/pointcolor}" android:textSize="17sp" android:textStyle="bold" android:text="@{point.point > 0 ? '+' + point.point.toString() : point.point.toString()}" tools:text="+ 11,000" /> </LinearLayout>
이런 식으로 다시 작성했습니다. 데이터 바인딩을 사용하면 xml 내에서 코드를 작성할 수 있기에 매우 편리하게 사용이 가능해요. 데이터 바인딩에 익숙하지 않다면 xml 내에 작성하는 것이 더 보기 힘들 수도 있지만, 데이터 바인딩을 어느정도 사용해보셨다면 xml 내에서 관리하는 것이 편하다는걸 아시게 될거에요.
Enum을 통한 데이터 변환
이전에는 Enum을 상수를 관리하는데만 주로 사용했었습니다. 쉽게 말하면 data Class의 확장판이라고 할까요. 분명 Enum은 단순히 상수를 관리하는 것 이상으로 많은 기능을 할 수 있습니다. 특히 그냥 String으로 하드 코딩하는 것보다 훨씬 안정성 있는 프로그래밍이 가능합니다.
String으로 직접 작성할 때는 오타가 날 수 있지만, Enum을 사용하면 IDE에서 자동완성을 지원해주므로, 프로젝트에 투입된지 얼마 안된 프로그래머도 충분히 실수할 여지를 줄일 수가 있죠. 그리고 상수값들을 한 클래스에서 관리하기 때문에 리소스를 관리하는 파일에 다 넣는 것보다 훨씬 구분하기 쉽습니다.
public enum Point { RECOMMEND(R.string.point_recommend), RECOMMENDED(R.string.point_recommended), BUYGOODS(R.string.point_buy_goods), WRITE_REVIEW(R.string.point_write_review), REFUND_PAYMENT(R.string.point_refund_payment), SUCCESS_PAYMENT(R.string.point_success_payment), WELCOME(R.string.point_welcome), CANCEL_PAYMENT(R.string.point_cancel_payment), CANCEL_GOODS(R.string.point_cancel_goods); @StringRes private int toDescription; Point(int toDescription) { this.toDescription = toDescription; } public int getToDescription() { return toDescription; } }
서버에서 받아온 영문 String 데이터를 다시 한글로 바꾸기 위해서 각 영문 데이터에 맞게 toDescription이라는 Enum 클래스의 value 값을 정의했습니다. 이러면 영문 데이터와 일치하는 오브젝트를 찾아서 해당 오브젝트의 value를 리턴해주면되죠.
하지만 이렇게 되면 다시 영문 데이터와 일치하는 오브젝트를 찾는 로직을 구성해야합니다. 이를 해결하기 위해서 Response를 받을 때 바로 Enum 값으로 매핑시키면 어떨까요? 즉, "recommended" 라는 데이터가 왔다고 하면 Point.RECOMMENDED로 매핑시키는겁니다. 그리고 아주 쉽게 매핑을 시킬 수 있습니다.
기본적으로 Gson은 Enum으로 Serialize, Deserialize를 지원합니다. 그래서 @SerializedName 어노테이션만 추가하면 되죠.
@SerializedName("recommend") RECOMMEND(R.string.point_recommend), @SerializedName("recommended") RECOMMENDED(R.string.point_recommended), @SerializedName("buy_goods") BUYGOODS(R.string.point_buy_goods), @SerializedName("write_review") WRITE_REVIEW(R.string.point_write_review), @SerializedName("refund_payment") REFUND_PAYMENT(R.string.point_refund_payment), @SerializedName("success_payment") SUCCESS_PAYMENT(R.string.point_success_payment), @SerializedName("welcome") WELCOME(R.string.point_welcome), @SerializedName("cancel_payment") CANCEL_PAYMENT(R.string.point_cancel_payment), @SerializedName("cancel_goods") CANCEL_GOODS(R.string.point_cancel_goods);
이렇게 작성하면 Response를 받으면서 바로 Enum에 매핑시킬 수 있고, xml에서도 Enum의 value 값을 그대로 활용할 수 있습니다.
최종적으로 코드는,
Binding Adapter를 활용해서 이렇게 코드가 작성되었습니다. toDescription에서 바로 String을 넘겨줌으로써 Textview에서 setText할 수 있겠지만, 서비스의 Localization을 위해 String Resource에 값을 매치시키는 것이 좋을 것이라 생각했고, 바인딩 어댑터에서 한번 더 int형 resourceId를 String형으로 변환시켜줍니다. Enum은 Context가 없기 때문에 getString이나 getResource와 같은 Api를 호출할 수가 없거든요!
만약 Enum에서 Application Context를 참조한다고하면, Enum은 Static이기 때문에 프로그램 전체적으로 좋지 않은 설계가 될 것입니다.
마치며
Enum으로 다양한 문제를 해결할 수 있는 것처럼 보였습니다. 특히 우아한 형제들에서 Enum을 통해 문제를 해결한 사례인
그리고, 반대로 Enum을 API Request 인자로 바로 사용하고 싶을 때 convert하는 사례인
Retrofit의 Query, Path 등에 Enum클래스 사용하기
을 함께 읽어보시는 것을 추천드립니다.
'Android' 카테고리의 다른 글
Fragment에서 ViewBinding 사용 시 발생할 수 있는 메모리릭 (0) 2020.11.13 변수의 동시성 제어에 관한 이슈 및 해결 (1) 2020.10.27 [안드로이드 9.0 프로토콜 접속 변경사항] CLEARTEXT communication to XXXX not permitted by network security policy (0) 2020.07.24 SharedPreference 이야기 (0) 2020.06.19 EditText 자동 포커스 기능 막기 (0) 2020.05.25