AdManagerAdView not rendering ad image when off screen in LazyColumn - android-jetpack-compose

I am wrapping an AdManagerAdView in an AndroidView so I can use it in Jetpack Compose. The image fails to load when I use it in a LazyColumn AND the AdManagerAdView tries to load the image before the composable is on screen.
If I scroll quickly to that element, so LazyColumn composes it AND it is on screen before the image comes back from the ad server, it works as expected.
LazyColumn {
items(5) {
SomeOtherComposable(it)
}
item {
AndroidView(
modifier = Modifier
.width(300.dp)
.height(250.dp)
.background(Color.Green),
factory = { context ->
val adView = AdManagerAdView(context)
adView.adSize = AdSize.MEDIUM_RECTANGLE
adView.adUnitId = adUnitId
adView.loadAd(Builder().build())
adView
}
)
}
items(5) {
SomeOtherComposable(it)
}
}
For demo purposes...
#Composable
fun SomeOtherComposable(i: Int) {
Text(
text = "SomeOtherComposable $i",
modifier = Modifier
.fillMaxWidth()
.height(320.dp)
.background(Color.White)
)
}
This also works fine if the wrapped AdManagerAdView is used in a non-lazy Column or any other Compose layout.
This feels like a weird timing thing in LazyColumn that just happens to manifest when the Composable isn't on screen yet since using it in a regular Column works fine under the same scenario.
Has anyone experienced anything like this?
SOLVED
See my answer below

Ok, the issue is actually that both factory{} and update{} are called before the AndroidView has gone through the layout pass. In my steps to reproduce, the ad image is coming back and being added to the internal view before it has a measured width and height.
The solution is to delay that load call until after the layout pass using doOnLayout{} like so:
AndroidView(
modifier = Modifier
.width(300.dp)
.height(250.dp)
.background(Color.Green),
factory = { context ->
val adView = AdManagerAdView(context)
adView.adSize = AdSize.MEDIUM_RECTANGLE
adView
},
update = { adView ->
adView.adUnitId = adUnitId
adView.doOnLayout {
adView.loadAd(Builder().build())
}
}
)

Related

Column does not vertically scroll although it was configured to

I am trying to add scrolling behaviour to a Column by setting verticalScroll(state = rememberScrollState()) modifier.
I checked out some examples in the official compose-jb repository, and it seems that that is the right way to do it, yet in my case the content is not scrollable.
Here the full code:
#Composable
#Preview
fun App() {
MaterialTheme {
// add scroll behaviour
val stateVertical = rememberScrollState(0)
Column(modifier = Modifier.verticalScroll(state = stateVertical)) {
repeat(100){
Text("item: $it")
}
}
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
Any ideas why it does not work in my case?
The Column is populated with 100 Text items, more than enough to exceed the default window height.
It actually works!
For some reason I was trying to use click and drag... which lead me to confusion.

Why lights and shadows are not properly drawn on device but are showing fine on the design tab?

I am using the https://github.com/adrianwitaszak/neumorph-ui library.
When i tried to do a simple test with this code
#Preview
#Composable
fun TestButton() {
MorphUiTheme() {
MorphBackground() {
Box(modifier = Modifier
.fillMaxSize()
) {
MorphButtonRounded(modifier = Modifier
.height(80.dp)
.width(240.dp)
.align(Alignment.Center)) {
}
}
}
}
Here is an image that i think explains the issue
You can clearly see that lights and shadows have only a color and now that transparent "gradient" you see on the right

AnimatedVisibility and BringIntoViewRequester not working together

I'm building an expandable Composable which would be expanded when clicked.
This would be implemented by using the AnimatedVisibility which works perfectly.
Code for the visibility animation:
AnimatedVisibility(
visible = isExpanded,
) {
// Content removed.
}
The problem I'm currently facing is that this is located in a vertical scrollable column and it should scroll to the expanded content when clicked next to expanding it.
As I read this would be done by using the BringIntoViewRequester as in the code snippet below:
var isExpanded by remember { mutableStateOf(false) }
val intoViewRequester = remember { BringIntoViewRequester() }
ClickableComposable(modifier = Modifier.clickable {
isExpanded = !isExpanded
if(isExpanded) {
coroutineScope.launch {
// delay(200)
intoViewRequester.bringIntoView(rect = null)
}
}
})
AnimatedVisibility(
modifier = Modifier.bringIntoViewRequester(intoViewRequester),
visible = isExpanded,
) {
// Content removed.
}
The code above works with the delay but that's not a perfect interaction for the user. To first see the content expanding and afterwards see the page scroll. The ideal situation would be that it would happen at the same time, however the content is not yet measured in any way. By removing the delay it does not work as the content is not yet visible.
Is there anything in Compose to do the expanding and scrolling to at the same time?

Avoid one frame delay in calculating child size

I have to create a reusable component that tries to achieve this goal: I have a column that can have content that's larger than the screen height. On the bottom of the screen we have panel with gradient background that can contain button or something else (it's basically a slot in the component). This bottom panel have to be always visible on the screen, and in case of the column being bigger than screen - bottom panel have to be on the top of this column. Gradient background does a nice UX effect so user knows what is going on. It looks like that:
I have that solved, but here's the challenge. The column content have to be scrollable to be on top of the bottom panel when scrolled to the end. Current solution I have is to add a spacer on the bottom of this column. This spacer have the calculated height of the bottom parent. And here's the issue - right now we have calculation done in onSizeChanged which basically results in additional frame needed for the spacer to have correct size.
We did not observe any negative impact of that performance or UX wise. The spacer height calculation never does anything that user can see, but I still want to solve that properly.
AFAIK this can be done using custom Layout, but that seems a little bit excessive for what I want to achieve. Is there another way to do this properly?
Current solution:
#Composable
fun FloatingPanelColumn(
modifier: Modifier = Modifier,
contentModifier: Modifier = Modifier,
contentHorizontalAlignment: Alignment.Horizontal = Alignment.Start,
bottomPanelContent: #Composable ColumnScope.() -> Unit,
content: #Composable ColumnScope.() -> Unit
) {
val scrollState = rememberScrollState()
var contentSize by remember {
mutableStateOf(1)
}
Box(modifier) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(state = scrollState)
.then(contentModifier),
horizontalAlignment = contentHorizontalAlignment,
) {
content()
val contentSizeInDp = with(LocalDensity.current) { contentSize.toDp() }
Spacer(modifier = Modifier.height(contentSizeInDp))
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.onSizeChanged {
contentSize = it.height
}
.wrapContentHeight()
.align(Alignment.BottomStart)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0x00FAFCFF),
Color(0xFFF6F9FB),
)
)
),
content = bottomPanelContent
)
}
}
The best way to depend on an other view size during layout is using SubcomposeLayout:
SubcomposeLayout { constraints ->
// subcompose the view you need to measure first
val bottomPanel = subcompose("bottomPanel") {
Column(
// ...
)
}[0].measure(constraints)
// use calculated value in next views layout, like bottomPanel.height
val mainList = subcompose("mainList") {
LazyColumn(
contentPadding = PaddingValues(bottom = bottomPanel.height.toDp())
) {
// ...
}
}[0].measure(constraints)
layout(mainList.width, mainList.height) {
mainList.place(0, 0)
bottomPanel.place(
(mainList.width - bottomPanel.width) / 2,
mainList.height - bottomPanel.height
)
}
}

AnimatedVisibility breaks constraints in ConstraintLayout in Jetpack Compose

Edit: Also happens when I swap ConstraintLayout for a Box using alignment as well...
Seems like AnimatedVisibility doesn't play well with ConstraintLayout in JP Compose at the moment.
AnimatedVisibility(
visible = entryListState.firstVisibleItemIndex > 3,
enter = fadeIn() + expandIn(expandFrom = Alignment.Center),
exit = fadeOut() + shrinkOut(shrinkTowards = Alignment.Center)
) {
ExtendedFloatingActionButton(
modifier = Modifier.constrainAs(scrollToTop) {
start.linkTo(parent.start)
bottom.linkTo(parent.bottom)
},
text = { Text(text = "TOP") },
onClick = { scope.launch { entryListState.animateScrollToItem(0) } }
)
}
The ( TOP ) fab should appear in the bottom left corner, but instead I get
When I remove the AnimatedVisibility wrapper, everything works fine. :( I know I can work around this, but I'm curious if there's something I'm doing incorrectly with the configuration of the AnimatedVisibility composable?
Turns out I was thinking about AnimatedVisibility all wrong. It's just another Composable. I needed to lift the FAB's modifiers re: positioning to the AnimatedVisibilty's modifiers.

Resources