TIL #94

loci·2024년 8월 5일
0

TIL

목록 보기
88/111
post-custom-banner

안드로이드 Clean Architecture에 대하여 (velog.io)

UI layer

  • activity
  • fragment
  • compose UI
  • ViewModel
    등 UI요소 자체들이 속한 layer

viewmodel이 중요
ViewModel은 비지니스 로직의 출발지점이다.

Viewmodel의 책임

  1. ViewModel은 사용자로부터 UI이벤트를 수신 받아야햐고 이를 DomainLayer나 DataLayer에 이벤트를 전달
  2. DomainLayer or Datalayer로부터 UI에 바인딩될 데이터 구조를 응답받아 UI에 데이터 바인딩을 진행
  3. UI에 바인딩해준 데이터를 상태 홀더 클래스(LiveData or StateFlow)를 통해 보유한다.

Domain Layer

  • UseCase

operator invoke 함수가 중요

class FormatDateUseCase @Inject constructor(
	private val userRepository: UserRepository
) {

    private val formatter = SimpleDateFormat(
        userRepository.getPreferredDateFormat(),
        userRepository.getPreferredLocale()
    )

    operator fun invoke(date: Date): String {
        return formatter.format(date)
    }
}
class HomeViewModel @Inject constructor(
    private val formatDataUseCase: FormatDataUseCase
): ViewModel() {

    fun getUserInfo() {
        val date = formatDataUseCase()
    }
    
}

formatDataUseCase()함수를 정의해주지 않았지만 invoke함수로 인해 사용가능해진다.
아래코드와 같다고 한다.

class HomeViewModel @Inject constructor(
    private val formatDataUseCase: FormatDataUseCase
): ViewModel() {

    fun getUserInfo() {
        val date = formatDataUseCase.invoke()
    }
    
}

Data Layer

두가지 모듈

  • Repository
  • DataSource

Repository

  • upStream - 필요데이터를 DataSource에 요청
  • DownStream - DataSource로부터 받아온 데이터를 새로운 모델로 가공하여 하위레이어에 전달(Domain or UI)
  • 가공한 데이터를 Repository모듈에 캐싱

DataSource는 Remote or Local Server로부터 받은 데이터를 1차 생산자로 제공해주는 모듈
Repository는 DataSource로 부터 받은 데이터를 하위레이어(Domain or UI)에 제공해주는 2차 생산자

DataSource에서 Raw 데이터를 Repository에 반환

data class UserInfoResponse{
    @SerializedName("name")
    val name: String,
    @SerializedName("age")
    val age: Int,
    @SerializedName("phone_number")
    val phoneNumber: String
    @SerializedName("address")
    val address: String
}

interface BaseApi {
    
    @GET("/v1/api/user")
    fun fetchUserInfo(): UserInfoResponse
    
}

class UserInfoDataSource @Inject(
    private val baseApi: BaseApi
) {
    fun fetchUserInfo(): Flow<UserInfoResponse> {
    	return flow {
            emit(baseApi.fetchUserInfo())
        }
    }
}

Repository 에서 반환된 데이터를 가공

class UserInfoRepository @Inject(
    private val userInfoDataSource: UserInfoDataSource
) {
    fun userInfo = userInfoDataSource.fetchUserInfo().map { response ->
        UserInfoUiState(
            val name: String,
            val age: Int
        )
    }
}

UseCase란

서비스를 사용자가(User)가 해당 서비스를 통해 하고자 하는 것을 의미

사용이유

  • ViewModel이 어떤것을 하고자하는지 직관적으로 파악할수있다.
    Screaming Architecture라고도 하는데 어떤 서비스를 제공하는 지 딱봐도 알 수 있게해준다.이름을 어떤것을 수행하는지 알 수 있도록 지어야한다. VM에서 해당 UseCase를 파라미터로 전달받아 사용하게 되니 VM에서 어떤일을 하는지 파라미터만 확인해도 알 수 있게 된다. 유지/보수에서 유용하고 특히 협업구조에서 진가를 보인다.
  • 의존성을 줄일수있다.
    Repository에서 데이터를 전달받아 사용하게 될때 전달 받은 Repository가 수정된다면 해당 Repository를 사용하는 많은 부분에서 수정이 이루어져야 할 가능성이 높다. useCase를 사용하면 영향이 있는 UseCase를 사용하는 부분에서만 수정을 하면 되기 때문에 의존성이 줄어든다.

Flow

Flow는 값을 방출 할때는 emit을 사용하고 회수할때는 collect를 사용
Flow는 cold stream

cold stream - 값을 요청하지 않으면 값을 방출하지 않음
hot stream - 값을 요청하건 말건 계속 값을 방출

cold stream은 원하는 정해진 값을 얻을 수 있고 hot stream은 가변적으로 변하는 값을 얻을수있다.

fun simple(): Flow<Int> = flow { // flow builder
    for (i in 1..3) {
        delay(100) // pretend we are doing something useful here
        emit(i) // emit next value
    }
}

fun main() = runBlocking<Unit> {
    // Launch a concurrent coroutine to check if the main thread is blocked
    launch {
        for (k in 1..3) {
            println("I'm not blocked $k")
            delay(100)
        }
    }
    // Collect the flow
    simple().collect { value -> println(value) } 
}

출력값
I'm not blocked 1
1
I'm not blocked 2
2
I'm not blocked 3
3
fun simple(): Flow<Int> = flow { 
    println("Flow started")
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}

fun main() = runBlocking<Unit> {
    println("Calling simple function...")
    val flow = simple()
    println("Calling collect...")
    flow.collect { value -> println(value) } 
    println("Calling collect again...")
    flow.collect { value -> println(value) } 
}

출력값
Calling simple function...
Calling collect...
Flow started
1
2
3
Calling collect again...
Flow started
1
2
3

Flow는 Coroutine scope안에서 동작하기 때문에 viewModelScope나 lifeCycleScope와 함께 사용하면 생명주기를 감지해 실행하거나 정제할 수 있다.

SharedFlow

hot stream으로 동작하게하는 flow의 종류

StateFlow

SharedFlow에 상태를 부여해 현재값을 얻을수있게 제한을 가한것

뷰가 stopped가 되면 livedata.observe()는 자동으로 등록 취소하는 반면, StateFlow 또는 다른 Flow에서 수집하는 경우 자동으로 수집을 중지하지 않음 동일한 동작을 실행하려면 Lifecycle.repeatOnLifecycle블록에서 흐름을 수집해야함

SharingStarted의 3가지 선택지

  1. SharingStarted.Eagerly : collector가 존재하지 않아도 sharing이 시작, 중지되지 않음
  2. SharingStarted.Lazily : collector가 등록된 이후부터 sharing이 시작, 중지되지 않음
  3. SharingStarted.WhileSubsribed : collector가 등록되면 바로 sharing을 시작, collector가 없어지면 바로중지 됨( 구글에서는 WhileSubsribed(5000)을 권장하는데 flow의 중단타이민과 ㅗ간련)

[안드로이드] Flow (velog.io)


Android Studio에서 폴더구조가 하위폴더로 안나오고 domain.usecase 식으로 나올때



원래 위 방식으로 나오지만 하위폴더가 없을때 두번째 화면처럼 ( . )으로 구분되어 나온다. 이를 위화면으로 공통적으로 적용시키고 싶을때 방법

빨간색원에 해당하는 탭을 설정하는 •••을 눌러준 후

Tree Appearance의 Compact Middle Packages가 체크되어있는것을 해제시켜주면 된다.

profile
편리한 개발자
post-custom-banner

0개의 댓글