Reputation: 218
I have auto slider banner every 3 sec it goes to next banner and also you can manually change banner.
That's my launched effect and I use Horizontal Pager
from foundation I also needed to calculate if 1/2 of the banner is visible, so I have done another Box
on it.
In terms of testing when I have turned off my banner compose section
and test it for hours it didn't crash with unconcurrent state change
. When banner works crash appears from time to time not so often, but it happens.
stacktrace
Fatal Exception: java.lang.IllegalStateException
Unsupported concurrent change during composition. A state object was modified by composition as well as being modified outside composition.
androidx.compose.runtime.Recomposer.applyAndCheck (Recomposer.kt:1)
version tested 1.6.1, 1.6.8, 1.7.0-rc01
const val foundation = "androidx.compose.foundation:foundation:$composeVersion"
const val AUTO_SLIDER_DELAY = 3000.toLong()
val state = rememberPagerState(pageCount = { sliderItems.count() })
LaunchedEffect(state.currentPage, sliderHoldTimestamp, sliderItems.size) {
state.let { localState ->
delay(AUTO_SLIDER_DELAY)
coroutineScope.launch {
if (sliderItems.isNotEmpty() && localState.pageCount != 0) {
var newPosition = localState.currentPage + 1
if (newPosition > sliderItems.size - 1) newPosition = 0
localState.animateScrollToPage(newPosition.mod(localState.pageCount))
}
}
}
}
Part with Horizontal Pager
Box(
modifier = Modifier
.fillMaxWidth()
.shadow(
elevation = 4.dp, ambientColor = ShadowColor, spotColor = ShadowSpotColor
)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.bottomElevation(4.dp)
.onGloballyPositioned { layoutCoordinates ->
coroutineScope.launch {
delay(10L)
val height = layoutCoordinates.boundsInWindow().bottom
if (height != previousHeight.floatValue) {
isVisibleFlow.value =
calculateIsVisible(layoutCoordinates, screenHeightPx)
previousHeight.floatValue = height
}
}
},
contentAlignment = Alignment.BottomCenter,
) {
if (sliderItems.isNotEmpty() && !isLoading) {
HorizontalPager(
state = state
) { page ->
when (val sliderItem = sliderItems[page]) {
is SliderBannerItem -> {
imageUrl.value = sliderItem.link
sliderItemPromo.value = sliderItem
if (state.currentPage == page) {
sliderItemPromoForEvent.value = sliderItemPromo.value
}
if ((sliderItem.containsAlco == true) && !ageVerificationState.isConfirmed()) {
AsyncBlurryImage(
imageUrl = imageUrl.value,
contentScale = ContentScale.Crop,
contentDescription = null,
modifier = Modifier
.aspectRatio(1.77f)
.clickable {
events(
PromoSliderEvents.RequestAgeVerification(
AgeVerificationEventSource.Slider(null)
)
)
},
)
}
Also this is my code to send event, but the problem is it triggers all the time and I quite not sure why. It seems like this banner recompose almost every 1ms, should it work like this?
BEFORESLIDERITEM
Logs practically all the time.
if (sliderItems.isNotEmpty() && state.pageCount != 0 && sliderItemPromoForEvent.value != null) {
Log.d("sliderItems", "BEFORESLIDERITEM")
LaunchedEffect(sliderItemPromoForEvent.value) {
Log.d("sliderItems", "AfterInLaunchedEffect")
withContext(Dispatchers.Main) {
try {
delay(1000L)
val sliderItemCurrentPage = sliderItemPromoForEvent.value
if (isVisibleFlow.value) {
sliderItemCurrentPage.let {
withContext(Dispatchers.IO) {
events.invoke(
PromoSliderEvents.SendViewPromotionEvent(
it?.redirectUrl.toString(),
it?.link.toString(),
state.currentPage.toString()
)
)
}
}
}
sliderItemPromoForEvent.value = null
} catch (e: Exception) {
Napier.d("${e.stackTraceToString()} - PromoSlider.kt; SliderItems")
}
}
}
}
Thanks for any help and advices.
Upvotes: 1
Views: 61