Android

Android Studio_ 11. Mapping 처리 방법

아이른 2024. 6. 1. 19:15

1. Mapping 처리를 하는 이유

  • 어떠한 API에서 받아온 데이터를 바로 쓰기엔 의존성 문제가 생기기 때문에 약하게 해주어야 함 (clean architeture의 원칙_solid)
  • 의존성(=결합)을 낮추기 위해서 Data > Domain > Ui(Presentation) 구성으로 하여 Mapper라는 확장 함수를 통해 Entity로 변환하여 사용 (변환 된 Entity는 어느 곳에서도 간접하지 않음)

Guide to app architecture  |  Android Developers

 

앱 아키텍처 가이드  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 앱 아키텍처 가이드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 가이드에는 고품질의 강력한

developer.android.com

 

https://velog.io/@lyh990517/Android-Clean-Architecture-들여다보기

 

[Android] Clean Architecture 들여다보기

클린아키텍처란 Presentation - Domain - Data각 레이어가 역할이 명확히 구분되어 분리된 아키텍처입니다.

velog.io

 

2. Data > Domain (Google Places API를 통한 예시)

  • google place API에서 받아온 data
@Parcelize
data class GooglePlace(
    @SerializedName("html_attributions")
    //사용자에게 표시되어야 하는 이 목록
    val htmlAttributions: List<String>,

    @SerializedName("next_page_token")
    //최대20개 사용할수있는 토큰
    val nextPageToken: String,
    //장소검색
    val results: List<ResultReponse>,
    //요청이 실패한 이유를 추적
    val status: String
): Parcelable
  • GooglePlacesEntity.kt 생성
    • Entity는 어느 곳에서 간섭을 받으면 안되므로 <ResultResponse> 에서 새로만든 <ResultEntity> 로 변경하여 <ResultResponse>를 가져옴으로써 import 된 것을 삭제
data class GooglePlacesEntity(

    val htmlAttributions: List<String>,
    val nextPageToken: String,
    //val results: List<ResultResponse> -> <ResultEntity>
    val results: List<ResultEntity>,
    val status: String
)

data class ResultEntity()
  • Mapper.kt 생성
    • 확장 함수인 toEntity()를 사용하여 해당 클래스를 Mapping 처리
    • this.result는 GooglePlace 안에 List 형태로 있기 때문에 map{} 을 사용
fun GooglePlace.toEntity():GooglePlacesEntity{
    return GooglePlacesEntity(
    this.htmlAttributions, 
    this.nextPageToken, 
    //this.results -> this.results.map {resultReponse -> resultReponse.toEntity()}
    this.results.map { resultReponse -> resultReponse.toEntity() }, 
    this.status)
}
  • Entity에 GooglePlace가 import 가 되지 않는 것이 중요하기 때문에 에러나는 부분을 확인하면서 같은 작업을 반복
더보기
  • GooglePlacesEntity.kt
data class GooglePlacesEntity(

    val htmlAttributions: List<String>,
    val nextPageToken: String,
    //val results: List<ResultResponse> 
    //-> <ResultEntity> 사용을 위해 data class ResultEntity 생성
    val results: List<ResultEntity>,
    val status: String
)

data class ResultEntity(

    val businessStatus: String,
    //val geometry: Geometry -> val geometry: GeometryEntity
    val geometry: GeometryEntity,
    val icon: String,
    val iconBackgroundColor: String,
    val iconMaskBaseUri: String,
    val name: String,
    //val openingHours: OpeningHours -> val openingHours: OpeningHoursEntity
    val openingHours: OpeningHoursEntity,
    // val photos:<Photo> -> val photos:List<PhotoEntity>
    val photos: List<PhotoEntity>,
    val placeId: String,
    //val plusCode: PlusCode ->  val plusCode: PlusCodeEntity
    val plusCode: PlusCodeEntity,
    val priceLevel: Int,
    val rating: Double,
    val reference: String,
    val scope: String,
    val types: List<String>,
    val userRatingsTotal: Int,
    val vicinity: String

)

data class PhotoEntity(
...
)

data class PlusCodeEntity(
...
)

data class GeometryEntity(
...
)

data class OpeningHoursEntity(
...
)

 

  • Mapper.kt
fun GooglePlace.toEntity():GooglePlacesEntity{
    return GooglePlacesEntity(this.htmlAttributions, this.nextPageToken, this.results.map { resultReponse -> resultReponse.toEntity() } , this.status)
}

fun ResultReponse.toEntity(): ResultEntity {
    return ResultEntity(this.businessStatus, geometry.toEntity(), icon, iconBackgroundColor, iconMaskBaseUri, name, openingHours.toEntity(), photos.map { it.toEntity() }, placeId, plusCode.toEntity(), priceLevel, rating, reference, scope, types, userRatingsTotal, vicinity)
}
/*
ptotos 또한 list 형태이기 때문에 map{}
plusCode는 API에서 받아온 다른 data class를 따르기 때문에 toEntity() 사용
this 생략 가능
*/


fun Photo.toEntity() : PhotoEntity{
    return PhotoEntity(height, htmlAttributions, photoReference, width)
}

fun Geometry.toEntity() : GeometryEntity{
    return GeometryEntity(location.toEntity(), viewport)
}

...

 

3. Domain > Ui (Google Places API를 통한 예시)

  • GooglePlacesModel.kt 와 domain에서 사용하는 Mapper.kt 를 생성하여 2번 제목과 같은 작업 실시
더보기
  • GooglePlacesModel.kt
data class GooglePlacesModel(

    val htmlAttributions: List<String>,
    val nextPageToken: String,
    //val results: List<ResultEntity> -> val results: List<ResultModel>
    //data class ResultModel 생성
    val results: List<ResultModel>,
    val status: String
)

data class ResultModel(

    val businessStatus: String,
    val icon: String,
    val iconBackgroundColor: String,
    val iconMaskBaseUri: String,
    val name: String,
    //val openingHours: OpeningHoursEntity -> val openingHours: OpeningHoursModel
    val openingHours: OpeningHoursModel,
    //val photos: List<PhotoEntity> -> val photos: List<PhotoModel>
    val photos: List<PhotoModel>,
    val placeId: String,
    val priceLevel: Int,
    val rating: Double,
    val types: List<String>,
    val userRatingsTotal: Int,

)

data class PhotoModel(

    val height: Int,
    val htmlAttributions: List<String>,
    val photoReference: String,
    val width: Int

)

data class OpeningHoursModel(
    val openNow: Boolean
)

...

 

  • Mapper.kt
fun GooglePlacesEntity.toModel() = GooglePlacesModel(
    htmlAttributions, nextPageToken, results, status
)

fun ResultEntity.toModel() = ResultModel(
    businessStatus, icon, iconBackgroundColor, iconMaskBaseUri, name, openingHours.toModel(),
    photos, placeId, priceLevel, rating, types, userRatingsTotal
)

fun PhotoEntity.toModel() = PhotoModel(
    height, htmlAttributions, photoReference, width
)


fun OpeningHoursEntity.toModel() = OpeningHoursModel(
    openNow
)

...
  • List 형태는 List에 대한 확장 함수를 따로 생성
fun GooglePlacesEntity.toModel() = GooglePlacesModel(
    htmlAttributions, nextPageToken, results.toModelResult(), status
)

fun List<ResultEntity>.toModelResult(): List<ResultModel> {

    return map {
        ResultModel(
            it.businessStatus,
            it.icon,
            it.iconMaskBaseUri,
            it.iconBackgroundColor,
            it.name,
            it.openingHours.toModel(),
            it.photos.toModelPhoto(),
            it.placeId,
            it.priceLevel,
            it.rating,
            it.types,
            it.userRatingsTotal
        )
    }
}