Last active
March 6, 2026 12:10
-
-
Save marenovakovic/b47db7bb153f441e2ff8a27b731d12f1 to your computer and use it in GitHub Desktop.
Circuit composite Producer/Presenter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @Inject | |
| @CircuitInject(CitiesScreen::class, AppScope::class) | |
| class CitiesPresenter( | |
| @Assisted private val navigator: Navigator, | |
| private val eventTracker: EventTracker, | |
| private val shouldShowPaywallOnCitiesScreen: ShouldShowPaywallOnCitiesScreen, | |
| private val cityOneStateProducer: CityStateProducer, | |
| private val cityTwoStateProducer: CityStateProducer, | |
| ) : Presenter<State> { | |
| @Composable | |
| override fun present(): State { | |
| LaunchedImpressionEffect(Unit) { | |
| if (shouldShowPaywallOnCitiesScreen()) | |
| navigator.goTo(PaywallScreen) | |
| } | |
| val coroutineScope = rememberCoroutineScope() | |
| val cityOneState = cityOneStateProducer.present() | |
| val cityTwoState = cityTwoStateProducer.present() | |
| val eventSink: (Event) -> Unit = { event -> | |
| when (event) { | |
| GoBack -> navigator.pop() | |
| ScoreCities -> coroutineScope.launch { | |
| if (shouldShowPaywallOnCitiesScreen()) | |
| navigator.goTo(PaywallScreen) | |
| else { | |
| if (cityOneState.bytes == null) | |
| cityOneState.eventSink(MissingPhoto) | |
| if (cityTwoState.bytes == null) | |
| cityTwoState.eventSink(MissingPhoto) | |
| if (cityOneState.bytes != null && cityTwoState.bytes != null) { | |
| navigator.goTo( | |
| ScoreScreen( | |
| cityOnePhoto = cityOneState.bytes, | |
| cityOneMilitaryPoints = cityOneState.militaryPoints.points, | |
| cityTwoPhoto = cityTwoState.bytes, | |
| cityTwoMilitaryPoints = cityTwoState.militaryPoints.points, | |
| ), | |
| ) | |
| } | |
| } | |
| eventTracker.track( | |
| SCORE_CITIES_BUTTON_CLICK, | |
| mapOf("showPaywall" to shouldShowPaywallOnCitiesScreen()), | |
| ) | |
| } | |
| } | |
| } | |
| return State( | |
| cityOneState = cityOneState, | |
| cityTwoState = cityTwoState, | |
| eventSink = eventSink, | |
| ) | |
| } | |
| } | |
| // Producer that has it's own State and Events. Can be a Presenter too | |
| @Inject | |
| class CityStateProducer(private val eventTracker: EventTracker) { | |
| sealed interface Event : CircuitUiEvent { | |
| data object TakePhoto : Event | |
| data object MissingPhoto : Event | |
| data class PhotoTaken(val bytes: ByteArray?) : Event | |
| data class SetMilitaryPoints(val point: MilitaryPoint) : Event | |
| } | |
| data class CityState( | |
| val takePhoto: Boolean = false, | |
| val bytes: ByteArray? = null, | |
| val militaryPoints: MilitaryPoint = Zero, | |
| val error: Error? = null, | |
| val eventSink: (Event) -> Unit, | |
| ) : CircuitUiState | |
| enum class MilitaryPoint(val points: Int) { | |
| Zero(0), | |
| Two(2), | |
| Five(5), | |
| Ten(10), | |
| } | |
| @Immutable | |
| sealed interface Error { | |
| data object MissingCityPhoto : Error | |
| } | |
| @Composable | |
| fun present(): CityState { | |
| var takePhoto by rememberRetained { mutableStateOf(false) } | |
| var photo by remember { mutableStateOf<ByteArray?>(null) } | |
| var militaryPoints by rememberRetained { mutableStateOf(Zero) } | |
| var error by rememberRetained { mutableStateOf<Error?>(null) } | |
| val eventSink: (Event) -> Unit = { event -> | |
| when (event) { | |
| TakePhoto -> takePhoto = true | |
| MissingPhoto -> error = MissingCityPhoto | |
| is PhotoTaken -> Snapshot.withMutableSnapshot { | |
| if (event.bytes != null) { | |
| eventTracker.track(CITY_PHOTO_TAKEN) | |
| photo = event.bytes | |
| takePhoto = false | |
| error = null | |
| } | |
| } | |
| is SetMilitaryPoints -> militaryPoints = event.point | |
| } | |
| } | |
| return CityState( | |
| takePhoto = takePhoto, | |
| bytes = photo, | |
| militaryPoints = militaryPoints, | |
| error = error, | |
| eventSink = eventSink, | |
| ) | |
| } | |
| } | |
| // UI. Just hand over the specific city state to CityCard responsible for showcasing it | |
| @OptIn(ExperimentalMaterial3Api::class) | |
| @CircuitInject(CitiesScreen::class, AppScope::class) | |
| @Composable | |
| fun CitiesUi( | |
| state: State, | |
| modifier: Modifier = Modifier, | |
| ) { | |
| Scaffold( | |
| topBar = { ... }, | |
| floatingActionButton = { ... }, | |
| content = { paddingValues -> | |
| Column( | |
| verticalArrangement = Arrangement.spacedBy(32.dp), | |
| horizontalAlignment = Alignment.CenterHorizontally, | |
| ) { | |
| CityCard(state.cityOneState) | |
| CityCard(state.cityTwoState) | |
| } | |
| }, | |
| modifier = modifier, | |
| ) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment