안드로이드 Clean Architecture에 대하여 (velog.io)
viewmodel이 중요
ViewModel은 비지니스 로직의 출발지점이다.
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()
}
}
두가지 모듈
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
)
}
}
서비스를 사용자가(User)가 해당 서비스를 통해 하고자 하는 것을 의미
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와 함께 사용하면 생명주기를 감지해 실행하거나 정제할 수 있다.
hot stream으로 동작하게하는 flow의 종류
SharedFlow에 상태를 부여해 현재값을 얻을수있게 제한을 가한것
뷰가 stopped가 되면 livedata.observe()는 자동으로 등록 취소하는 반면, StateFlow 또는 다른 Flow에서 수집하는 경우 자동으로 수집을 중지하지 않음 동일한 동작을 실행하려면 Lifecycle.repeatOnLifecycle블록에서 흐름을 수집해야함
원래 위 방식으로 나오지만 하위폴더가 없을때 두번째 화면처럼 ( . )으로 구분되어 나온다. 이를 위화면으로 공통적으로 적용시키고 싶을때 방법
빨간색원에 해당하는 탭을 설정하는 •••을 눌러준 후
Tree Appearance의 Compact Middle Packages가 체크되어있는것을 해제시켜주면 된다.