Circuit을 사용하면서, Circuit Navigation 사용 시 feature 모듈간의 참조 문제가 있었는데, 팀원인 지훈님이 해결한 코드를 보고 어떻게 해결했는지 분석해보고자 한다!
Circuit에 대한 분석, 지훈님 블로그 참고
https://velog.io/@mraz3068/Circuit-Try-Out
자세한 내용 및 코드는 아래 PR 참고
https://github.com/YAPP-Github/Reed-Android/pull/39
Circuit에서 Screen은 해당 화면을 식별하는 key 역할을 하며, 다음과 같은 역할을 한다
Compose Navigation과는 달리, Circuit에서는 Screen 객체가 하나의 진입점이며 이에 맞춰 Presenter, UI, 상태(State), 이벤트(Event)가 연결된다
📦 feature
├── 📂 home
│ ├── HomePresenter.kt
│ └── HomeScreen.kt // 내부에 UiState + Event 중첩 정의됨
feature:home 모듈의 HomeScreen.kt파일 내 HomeScreen 정의는 다음과 같이 구현되어 있었다
// 식별자 역할을 하는 Screen
@Parcelize
data object HomeScreen : Screen {
// Screen 내부에 State와 Event가 중첩
data class State(
val eventSink: (Event) -> Unit,
) : CircuitUiState
sealed interface Event : CircuitUiEvent
}
// Composable UI
@CircuitInject(HomeScreen::class, ActivityRetainedComponent::class)
@Composable
internal fun Home(
state: HomeScreen.State,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
HomeContent(
state = state,
modifier = modifier,
)
}
}
navigator.goTo(HomeScreen)과 같은 코드를 호출할 경우 직접적인 참조를 갖게 된다📦 feature
├── 📂 home
│ ├── HomePresenter.kt
│ ├── HomeUiState.kt
│ └── HomeScreen.kt
└── 📂 screens
└── ReedScreen.kt
1. feature:screens 모듈에 추상화된 ReedScreen 정의
abstract class ReedScreen(val name: String) : Screen {
override fun toString(): String = name
}
@Parcelize
data object HomeScreen : ReedScreen(name = "Home()") // HomeScreen이 ReedScreen을 상속
2. feature:home의 HomeUiState.kt에서 UiState/UiEvent 정의
data class HomeUiState(
val eventSink: (HomeUiEvent) -> Unit,
) : CircuitUiState
sealed interface HomeUiEvent : CircuitUiEvent {
data object OnOcrButtonClicked : HomeUiEvent
}
3.HomePresenter.kt에서는 screens 모듈만 참조한다
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import com.ninecraft.booket.screens.HomeScreen
import com.ninecraft.booket.screens.OcrScreen // screens 모듈 참조
import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.components.ActivityRetainedComponent
@Suppress("unused")
class HomePresenter @AssistedInject constructor(
@Assisted private val navigator: Navigator,
) : Presenter<HomeUiState> {
@Composable
override fun present(): HomeUiState {
fun handleEvent(event: HomeUiEvent) {
when (event) {
is HomeUiEvent.OnOcrButtonClicked -> {
navigator.goTo(OcrScreen)
}
}
}
return HomeUiState(
eventSink = ::handleEvent,
)
}
@CircuitInject(HomeScreen::class, ActivityRetainedComponent::class)
@AssistedFactory
fun interface Factory {
fun create(navigator: Navigator): HomePresenter
}
}