I am absolutely baffled by this: I want to show a composable miniplayer in a ComposeView (in the layout.xml of a Fragment) as the rest of the screen (apart from one other ComposeView) is still in xml. The miniplayer recomposes just fine when state changes but this isn't always reflected on screen as sometimes the view doesn't update when state changes but if I run the app again it might just work as expected (or not).
What I have tried:
Some code:
Part of the XML layout containing the ComposeView
<androidx.compose.ui.platform.ComposeView
android:id="@+id/miniPlayerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
How I put the composable content in the ComposeView
ui.miniPlayerView.setContent {
AppTheme {
MiniAudioPlayer()
}
}
The composable
private const val MINIMUM_DRAG_AMOUNT = -100f
@Composable
fun MiniAudioPlayer(
modifier: Modifier = Modifier,
playerHandler: PlayerHandler = koinInject()
) {
val context = LocalContext.current
val state by playerHandler.miniPlayerState.collectAsStateWithLifecycle()
val progress by remember { derivedStateOf { state.progress } }
val showPlayer by remember { derivedStateOf { state.showPlayer } }
val isPlaying by remember { derivedStateOf { state.isPlaying } }
val title by remember { derivedStateOf { state.title } }
val thumbnailUrl by remember { derivedStateOf { state.thumbnailUrl } }
var totalDragAmount by remember { mutableFloatStateOf(0f) }
val interactionSource = remember { MutableInteractionSource() }
if (showPlayer) {
Row(
modifier = modifier
.clickable(interactionSource = interactionSource, indication = null) { context.openAudioPlayer() }
.pointerInput(Unit) {
detectVerticalDragGestures(
onDragStart = { totalDragAmount = 0f },
) { _, dragAmount ->
totalDragAmount += dragAmount
if (totalDragAmount < MINIMUM_DRAG_AMOUNT) context.openAudioPlayer()
}
}
.background(color = LocalCustomColors.current.background)
.background(color = colorResource(R.color.miniplayer_background))
.padding(16.dp),
) {
MMImage(
url = thumbnailUrl,
size = DpSize(54.dp, 54.dp),
modifier = Modifier
.size(54.dp)
.clip(RoundedCornerShape(8.dp))
)
Spacer(modifier = Modifier.width(12.dp))
Column(
modifier = Modifier
.weight(1f)
) {
Spacer(modifier = Modifier.height(8.dp))
Header5Text(
text = title,
maxLines = 1
)
Spacer(modifier = Modifier.height(10.dp))
MMProgressBar(progress = { progress })
}
Spacer(modifier = Modifier.width(18.dp))
MMIcon(
drawableId = if (isPlaying) R.drawable.ic_miniplayer_pause_button else R.drawable.ic_miniplayer_play_button,
size = 34.dp,
modifier = Modifier
.clickable(interactionSource = interactionSource, indication = null) {
playerHandler.togglePlayState()
}
.align(Alignment.CenterVertically)
)
}
}
}
How I update the values in the ViewModel (PlayerHandler class)
private val _miniPlayerState = MutableStateFlow(MiniPlayerUiState())
val miniPlayerState = _miniPlayerState.asStateFlow()
_miniPlayerState.update { it.copy(progress = progress / 100f) }
The state
data class MiniPlayerUiState(
val showPlayer: Boolean = false,
val isPlaying: Boolean = false,
val progress: Float = 0f,
val title: String = "",
val thumbnailUrl: String? = null
)
Turns out this issue was caused by having strong skipping mode enabled in the compose compiler options... I guess ComposeViews are not (yet?) compatible with strong skipping mode.
composeCompiler {
enableStrongSkippingMode = true
...
}