이 글은 코루틴 베스트 프랙티스 구글 문서를 살짝 요약한 글입니다.
코루틴 Best Practice
1.Inject Dispatchers
// DO inject Dispatchers
class NewsRepository(
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend fun loadNews() = withContext(defaultDispatcher) { /* ... */ }
}
// DO NOT hardcode Dispatchers
class NewsRepository {
// DO NOT use Dispatchers.Default directly, inject it instead
suspend fun loadNews() = withContext(Dispatchers.Default) { /* ... */ }
}
Dispatcher 선언시 직접 dispatcher를 하드코딩하지말고 생성자로 만들어서 프로그래머가 원하는 dispatcher를 넣을 수 있도록 하자.
2. 코루틴은 background thread에서 실행하라
Making a network request on the main thread causes it to wait, or block, until it receives a response. Since the thread is blocked, the OS isn't able to call onDraw(), which causes your app to freeze and potentially leads to an Application Not Responding (ANR) dialog. For a better user experience, let's run this operation on a background thread.
3.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)
}
}
}
// Prefer observable state rather than suspend functions from the ViewModel
class LatestNewsViewModel(
private val getLatestNewsWithAuthors: GetLatestNewsWithAuthorsUseCase
) : ViewModel() {
// DO NOT do this. News would probably need to be refreshed as well.
// Instead of exposing a single value with a suspend function, news should
// be exposed using a stream of data as in the code snippet above.
suspend fun loadNews() = getLatestNewsWithAuthors()
}
단순히 suspend fun을 노출시켜 사용하기 보다는 viewmodel에서 coroutine의 값을 받아와 노출시키는것이 훨씬 좋다
4. Mutable 변수를 노출시키지 말자
// DO expose immutable types
class LatestNewsViewModel : ViewModel() {
private val _uiState = MutableStateFlow(LatestNewsUiState.Loading)
val uiState: StateFlow<LatestNewsUiState> = _uiState
/* ... */
}
class LatestNewsViewModel : ViewModel() {
// DO NOT expose mutable types
val uiState = MutableStateFlow(LatestNewsUiState.Loading)
/* ... */
}
5.데이터 및 비지니스 레이어는 suspend 함수 및 flows를 노출시키자
// Classes in the data and business layer expose
// either suspend functions or Flows
class ExampleRepository {
suspend fun makeNetworkRequest() { /* ... */ }
fun getExamples(): Flow<Example> { /* ... */ }
}
5.1 데이터 및 비지니스 레이어에서는 coroutinescope 혹은 supervisorscope를 사용하는것이 좋다.
6. 코루틴을 cancellable하게 만들자
코루틴은 job이 캔슬 되더라도 suspend되거나 cancellation이 check되기 전까지는 coroutine이 cancel되지 않는다. 그래서 coroutine을 cancellable하게 만드는것이 중요하다.
someScope.launch {
for(file in files) {
ensureActive() // Check for cancellation
readFile(file)
}
}
7. 코루틴 exception을 조심하자
코루틴이 unexpected exception을 만날 경우에는 이를 핸들링 할 수 있는 코드가 필요하다.
class LoginViewModel(
private val loginRepository: LoginRepository
) : ViewModel() {
fun login(username: String, token: String) {
viewModelScope.launch {
try {
loginRepository.login(username, token)
// Notify view user logged in successfully
} catch (exception: IOException) {
// Notify view login attempt failed
}
}
}
}
최근댓글