본문 바로가기
Android

Android Coroutine example 코루틴 권장사항

by kkong93 2024. 7. 4.
반응형

1. 새 코루틴을 만들거나 withContext를 호출할때 Dispatchers를 하드코딩하지 마세요. -> 코드의 유연성, 테스트 용이성을 위함. 직접 하드코딩하게 되면 테스트나 리팩토링이 어려워진다.

 

하드코딩된 Dispatchers

import kotlinx.coroutines.*

fun fetchData() {
    GlobalScope.launch(Dispatchers.IO) {
        // 일부 오래 동작하는 작업
        val data = performNetworkRequest()
        
        withContext(Dispatchers.Main) {
            // UI 업데이트
            updateUI(data)
        }
    }
}

 

하드코딩을 피하는 방법

1) 의존성 주입을 사용하여 Dispatchers를 주입하는 방법

 

디스패처를 주입할 수 있는 클래스 만들기

class CoroutineDispatchers(
    val io: CoroutineDispatcher = Dispatchers.IO,
    val main: CoroutineDispatcher = Dispatchers.Main
)

 

이 디스패처 클래스를 의존성으로 주입

class MyRepository(private val dispatchers: CoroutineDispatchers) {

    suspend fun fetchData() {
        withContext(dispatchers.io) {
            val data = performNetworkRequest()

            withContext(dispatchers.main) {
                updateUI(data)
            }
        }
    }
}

 

val testDispatchers = CoroutineDispatchers(
    io = Dispatchers.Unconfined,  // 테스트용 디스패처
    main = Dispatchers.Unconfined  // 테스트용 디스패처
)

val repository = MyRepository(testDispatchers)

 

 

2) 함수 인자로 Dispatchers를 전달하는 방법

suspend fun fetchData(dispatcherIO: CoroutineDispatcher = Dispatchers.IO, dispatcherMain: CoroutineDispatcher = Dispatchers.Main) {
    withContext(dispatcherIO) {
        val data = performNetworkRequest()

        withContext(dispatcherMain) {
            updateUI(data)
        }
    }
}
runBlocking {
    fetchData(Dispatchers.Unconfined, Dispatchers.Unconfined)
}

 

 

2. 정지 함수는 기본 스레드에서 호출하기에 안전해야 함

네트워크 요청을 'Dispatchers.IO'에서 처리하고 UI업데이트를 'Dispatchers.Main' 에서 처리하는 예제

 

import kotlinx.coroutines.*

class NetworkService {
    suspend fun fetchUserData(userId: String): UserData = withContext(Dispatchers.IO) {
        // 네트워크 요청을 통해 사용자 데이터 가져오기 (이 예제에서는 가정)
        simulateNetworkRequest(userId)
    }
}

class UserProfileViewModel(private val networkService: NetworkService) {

    fun fetchAndDisplayUserData(userId: String) {
        // ViewModel에서 launch는 기본적으로 Dispatchers.Main에서 실행됨
        viewModelScope.launch {
            try {
                val userData = networkService.fetchUserData(userId)
                displayUserData(userData)
            } catch (e: Exception) {
                handleError(e)
            }
        }
    }

    private fun displayUserData(userData: UserData) {
        // UI 업데이트
    }

    private fun handleError(e: Exception) {
        // 에러 처리
    }
}

data class UserData(val id: String, val name: String)

suspend fun simulateNetworkRequest(userId: String): UserData {
    delay(1000) // 네트워크 지연 시뮬레이션
    return UserData(userId, "John Doe")
}

 

`fetchUserData` 정지 함수는 `Dispatchers.IO`를 통해 네트워크 요청을 수행하고, `fetchAndDisplayUserData` 함수는 기본 스레드(UI 스레드)에서 호출되어도 안전하게 작동합니다. 데이터가 불러와지면 `Dispatchers.Main`에서 UI 업데이트를 수행합니다.

 

 

3. ViewModel은 코루틴을 만들어야 함 - 정지 함수를 노출하는 대신 코루틴을 만들게 되는데 데이터 스트림을 사용해 상태를 노출하는 하나의 값만 방출하는 경우 ViewModel의 정지 함수가 유용하다.

// DO create coroutines in the ViewModel
class LatestNewsViewModel(
    private val getLatestNewsWithAuthors: GetLatestNewsWithAuthorsUseCase
) : ViewModel() {

    private val _uiState = MutableStateFlow<LatestNewsUiState>(LatestNewsUiState.Loading)
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    fun loadNews() {
        viewModelScope.launch {
            val latestNewsWithAuthors = getLatestNewsWithAuthors()
            _uiState.value = LatestNewsUiState.Success(latestNewsWithAuthors)
        }
    }
}

 

반응형

댓글