Album_Screen_구현_관련_기록 - boostcampwm-2024/and04-Nature-Album GitHub Wiki
관련 PR: Album Screen 구현 및 Navigation 연결 #87
https://github.com/boostcampwm-2024/and04-Nature-Album/pull/87
- Hilt를 통해 AlbumViewModel을 주입받아 생성하고,
AlbumScreen
UI에 필요한 데이터를AlbumDto
로 관리. - Room Database에서 쿼리를 통해 앨범 데이터를 가져와서 UI에 표시하며, 더미 데이터를 생성하여 초기 테스트에 사용.
- Navigation을 구현하여 Album 관련 화면을 Compose와 Hilt를 통합하여 탐색 가능하게 구성.
- Hilt를 사용한 AlbumViewModel 생성
- Room Database 쿼리 함수 수정
- AlbumDto 생성
- 더미 데이터 생성하여 Room DB에 저장
- ViewModel에서 Repository를 통한 Room DB 접근
- Navigation 구현 및 Hilt 통합
- 더미 데이터로 도감 UI Test
@HiltViewModel
어노테이션을 통해 AlbumViewModel
을 생성하고, ViewModel의 매개변수로 Repository
를 주입받아 데이터 관리 및 가공을 담당하게 구성하였다.
@HiltViewModel
class AlbumViewModel @Inject constructor(
private val repository: DataRepository
)
getPhotoDetailUriById(): List<String>
와 기타 몇 몇 함수의 반환값이 잘못됐다.
- 단일
Uri
를 가져오는 부분인데List<String>
으로 되어있었기에String
반환 값으로 수정 -
AlbumDao
에 존재하던 위 쿼리 함수를PhotoDetailDao
로 이동
실제 UI에 필요한 데이터들을 가지고 있는 AlbumDto
생성했다.
AlbumDto
의 역할은 Room DB에서 쿼리를 통해 가져온 값들 중 AlbumScreen
에 사용되는 값들을 저장한다.
data class AlbumDto(
val labelId: Int,
val labelName: String,
val labelBackgroundColor: String,
val photoDetailUri: String
)
또한, 초기 데이터가 없는 상황에서 UI 테스트를 위해 createDummyData()
함수를 사용해 더미 데이터를 Room DB에 삽입하였다.
ViewModel의 loadAlbums()
함수를 통해 Room DB
에서 데이터를 가져와 UI에 필요한 형태로 가공한 후 AlbumDto
에 담아 LiveData를 통해 UI에 반영되도록 하였다.
또한, 도감 로딩에 관련된 세부 동작은 아래와 같다.
-
Room DB에서 앨범 전체 가져오기
-
repository.getALLAlbum()
을 호출하여List<Album>
형태로 전체 앨범 데이터를 가져온다.
-
-
Album의
label_id
와photo_detail_id
를 이용한 추가 쿼리- 각
Album
항목의label_id
와photo_detail_id
를 사용하여 관련 데이터를 개별적으로 조회한다.
- 각
-
각 항목별 데이터 조회
-
Label 이름 조회:
Album.labelId
를 이용해repository.getLabelNameById(labelId)
를 호출하여 라벨 이름을 가져온다. -
대표 이미지 URI 조회:
Album.photoDetailId
를 통해repository.getPhotoDetailUriById(photoDetailId)
를 호출하여 대표 이미지 URI를 가져온다. -
라벨 배경 색상 조회:
Album.labelId
를 사용해repository.getLabelBackgroundColorById(labelId)
를 호출하여 라벨의 배경 색상을 가져온다.
-
Label 이름 조회:
-
가공한 데이터
AlbumDto
에 저장- 조회된 데이터를
AlbumDto
에 저장하여 UI에서 필요한 정보를 담도록 구성한다.AlbumDto
에는 다음 정보가 포함된다:-
labelId
: 라벨 ID (추후 쿼리용으로 사용) -
labelName
: 라벨 이름 -
labelBackgroundColor
: 라벨 배경 색상 -
photoDetailUri
: 대표 이미지 URI
-
- 조회된 데이터를
-
UI에 반영
- 최종적으로 LiveData를 통해 가공된
AlbumDto
리스트가 UI에 반영되어 앨범 화면에 표시된다.
- 최종적으로 LiveData를 통해 가공된
Compose 내에서 hiltViewModel()
을 사용하여 Hilt와 Navigation을 통합하였다. AlbumScreen
을 탐색하는 화면에서 viewModel()
대신 hiltViewModel()
을 사용하여 Hilt가 주입된 ViewModel 인스턴스를 관리하도록 설정하였다.
fun AlbumScreen(viewModel: AlbumViewModel = hiltViewModel())
이렇게 설정함으로써, AlbumScreen
에서 ViewModel 주입이 정상적으로 이루어지며, Hilt를 통한 의존성 관리가 유지되었다.
-
DTO, Mapper
UI에 보여줄 용도로 데이터를 변환하고 가공하는 작업을 한다면 DTO와 함께 Mapper 패턴을 사용하는 것이 유용하다. DTO는 데이터를 전달하기 위한 객체로서, DAO에서 가져온 데이터와는 다른 형태로 UI에 최적화된 데이터를 가질 수 있다. 반면, Mapper는 데이터 변환 로직을 담당하는 객체로서, 다음과 같은 차이점이 있다.
-
DTO (Data Transfer Object):
- DTO는 순수하게 데이터를 전달하는 용도로 생성된 객체다.
- 변환된 데이터를 담아 UI와 함께 사용되거나, 다양한 계층 간의 데이터를 전달할 때 쓰인다.
- UI가 필요로 하는 최소한의 정보만 담고 있어 더 가볍고, UI가 필요한 형식에 맞게 데이터를 제공한다.
- DTO 자체는 데이터를 단순히 전달하는 역할이기 때문에 데이터 변환 로직은 포함하지 않는다.
-
Mapper:
- Mapper는 데이터 변환 로직을 추상화하는 객체다.
- 일반적으로 DAO로부터 가져온 데이터 엔티티를 UI에 필요한 DTO로 변환하거나 반대로 변환하는 역할을 한다.
- Mapper는 엔티티와 DTO 사이의 데이터 이동을 돕고, 이를 통해 데이터의 형식을 UI에 맞게 가공한다.
- 예를 들어, Room DB에서 가져온
UserEntity
를UserDTO
로 변환할 때 Mapper를 사용해 변환 로직을 관리하면 DTO의 목적을 더 명확하게 유지할 수 있다.
정리하자면: UI에 보여줄 용도로 데이터를 가공해야 한다면, 데이터 전달을 위한 DTO를 정의하고, 이 데이터로 변환하는 책임은 Mapper에서 관리하는 것이 좋다. 이 방법을 통해 코드의 책임이 분리되어 유지보수성과 가독성이 높아지며, 데이터 변환이 필요한 다른 영역에서도 재사용할 수 있다.
-
DTO (Data Transfer Object):
-
LiveData에서 postValue() vs setValue() 의 차이
- LiveData에서 postValue() vs setValue() 의 차이
-
postValue()
는 백그라운드 스레드에서 메인 스레드에 있는 LiveData의 값에 접근하기 위해 사용 -
setValue()
는 메인스레드에서 LiveData의 값을 변경하는 것
-
- LiveData에서 postValue() vs setValue() 의 차이
-
Room DB에서 데이터를 가져올 때
DisPatcher.IO
를 명시해줘야할까?- Room DB에서 데이터를 가져올 때
DisPatcher.IO
를 명시해줘야할까?- Retrofit2 에서는
[Dispatcher.IO](http://dispatcher.io/)
를 명시하지 않는 이유는 Retrofit2에서 내부적으로 비동기 네트워크 요청을 처리하도록 설계되어 있어, 네트워크 통신 시 별도의 쓰레드에서 작업을 수행하기 때문이다. - 반면에 Room DB는 Retrofit2의 처리방식과 다르기에
[DisPatcher.IO](http://dispatcher.io/)
를 지정하여 앨범 가져오는 등의 작업을 별도의 IO 쓰레드에서 하는 것이 적절하다.
- Retrofit2 에서는
- Room DB에서 데이터를 가져올 때
-
LiveData에서 get() 을 쓰지 않아도 되는 이유는?
- LiveData에서 get() 을 쓰지 않아도 되는 이유는?
private val _issues = MutableLiveData<List<IssueGet>>() val issues: LiveData<List<IssueGet>> get() = _issues
- DTO (Data Transfer Object): 데이터를 전달하기 위한 객체로, UI에 필요한 최소한의 정보만 포함하여 가볍게 만든다.
- Mapper: 데이터 변환 로직을 담당하는 객체이다.
정리하자면: UI에 보여줄 용도로 데이터를 가공해야 한다면, 데이터 전달을 위한 DTO를 정의하고, 이 데이터로 변환하는 책임은 Mapper에서 관리하는 것이 좋다. 이 방법을 통해 코드의 책임이 분리되어 유지보수성과 가독성이 높아지며, 데이터 변환이 필요한 다른 영역에서도 재사용할 수 있다.
-
postValue()
: 백그라운드 스레드에서 LiveData 값을 변경할 때 사용. -
setValue()
: 메인 스레드에서 LiveData 값을 변경할 때 사용.
Room DB는 비동기 처리가 자동화되어 있지 않으므로 Dispatcher.IO
를 명시하여 IO 쓰레드에서 데이터 작업을 수행하는 것이 적절하다.
LiveData는 get()
없이도 데이터를 안전하게 노출할 수 있으며, 외부 수정 없이 데이터 접근을 가능하게 하는 방식으로 설계되었다.
원인 : 1.8 → 17 (options)
- 내 자바 문제인가?? 싶어서 자바 1.8 버전으로 변경했음에도 계속 에러 발생했다.
- 해당 에러를 검색해보니 자바 컴파일 옵션을 17로 내리라는 문제였다.
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
- 또한 위 에러가 뜨지만 앱 빌드는 정상적으로 실행됐다.
해결
- 기존의 1.8 버전을 썼음에도 갑자기 에러가 사라졌다..
해결
- 도윤님께서 이 부분에 관한 문제를 해결하여 dev 브랜치에 merge했다.
- 원인 : Viewmodel 인스턴스를 생성할 수 없음
- 추정 : 에러 코드로 들어가보니 Compose Navigation 하면서 AlbumScreen 매개변수의 viewModel에서 에러 발생
//Error
fun AlbumScreen(viewModel: AlbumViewModel = viewModel())
//정상적으로 실행
fun AlbumScreen(viewModel: AlbumViewModel = hiltViewModel())
참고 자료 : https://velog.io/@wlsrhkd4023/Compose-hiltViewModel%EA%B3%BC-viewModel-%EC%B0%A8%EC%9D%B4
- 결론 : Hilt를 통해 의존성을 주입받은 Viewmdoel에서는
hiltViewModel()
로 사용해야한다.
문제점:
처음 더미 데이터를 생성할 때 photoUri
를 제대로 된 URI 형식으로 저장하지 못하여, drawable
리소스 이미지를 가져오는 데 실패했다. URI가 "uri_up_to_~" 형태로 저장되어 앱에서 사용하기 어려운 상태였다.
해결 과정:
-
getDrawableUri
함수 생성:Android에서
drawable
리소스를 URI 형식으로 변환하기 위해getDrawableUri
함수를 생성했다. 이 함수는drawable
ID를Uri.parse
를 통해android.resource://
형식의 URI로 변환한다.fun getDrawableUri(context: Context, drawableId: Int): String { return Uri.parse("android.resource://${context.packageName}/$drawableId").toString() }
-
더미 데이터 생성 시 URI 변환 적용:
createDummyData
함수에서getDrawableUri
를 사용하여drawable
리소스를 URI로 변환하고, 이를photoUri
에 할당하여 Room DB에 저장하였다.val uri01 = getDrawableUri(context, R.drawable.sample_image_01) val uri02 = getDrawableUri(context, R.drawable.sample_image_02) val uri03 = getDrawableUri(context, R.drawable.sample_image_01)
결과:
변환된 URI가 올바르게 저장되면서 더미 데이터 테스트가 성공적으로 이루어졌다. drawable
리소스 이미지를 올바르게 불러와 UI에 표시할 수 있게 되었다. 이를 통해 더미 데이터를 활용한 UI 테스트가 가능해졌으며, 이후 실제 데이터 로딩 과정에서도 유효한 URI를 사용할 수 있게 되었다.