Kodio 0.1.1 Help

Material 3 Components

The compose-material3 module provides ready-to-use Material 3 components that work out of the box with RecorderState and PlayerState.

Add the dependency

implementation(libs.kodio.compose.material3)
implementation("space.kodio:compose-material3:0.1.1")

RecordAudioButton

A styled recording button that shows the current state with appropriate icons and colors:

val recorderState = rememberRecorderState() RecordAudioButton( isRecording = recorderState.isRecording, isProcessing = recorderState.isProcessing, onClick = { recorderState.toggle() } )

The button automatically displays:

  • đŸŽ™ī¸ Microphone icon when idle

  • âšī¸ Stop icon while recording

  • âŗ Loading indicator while processing

PlayAudioButton

A playback button that adapts to the player's current state:

val playerState = rememberPlayerState(recording) PlayAudioButton( isPlaying = playerState.isPlaying, isPaused = playerState.isPaused, isReady = playerState.isReady, isFinished = playerState.isFinished, onClick = { playerState.toggle() } )

The button shows:

  • â–ļī¸ Play icon when ready or paused

  • â¸ī¸ Pause icon while playing

  • 🔄 Replay icon when finished

AudioPermissionButton

A button for requesting microphone permission with appropriate messaging:

val recorderState = rememberRecorderState() if (recorderState.needsPermission) { AudioPermissionButton( onClick = { recorderState.requestPermission() } ) }

ErrorDialog

A Material 3 dialog for displaying audio errors:

recorderState.error?.let { error -> ErrorDialog( error = error, onDismiss = { recorderState.clearError() } ) }

The dialog automatically formats error messages based on the error type:

  • PermissionDenied: Suggests enabling microphone access

  • DeviceNotFound: Indicates no microphone available

  • NotInitialized: Reminds to call Kodio.initialize() on Android

Complete example

Here's a full recording UI using all Material 3 components:

@Composable fun MaterialAudioUI() { val recorderState = rememberRecorderState() val playerState = rememberPlayerState() // Load recording into player when available LaunchedEffect(recorderState.recording) { recorderState.recording?.let { playerState.load(it) } } Column( modifier = Modifier.fillMaxWidth().padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { // Permission request if (recorderState.needsPermission) { AudioPermissionButton( onClick = { recorderState.requestPermission() } ) } else { // Waveform during recording if (recorderState.isRecording) { AudioWaveform( amplitudes = recorderState.liveAmplitudes, modifier = Modifier.fillMaxWidth().height(80.dp) ) } // Record button RecordAudioButton( isRecording = recorderState.isRecording, isProcessing = recorderState.isProcessing, onClick = { recorderState.toggle() } ) // Play button (when recording available) if (recorderState.hasRecording) { PlayAudioButton( isPlaying = playerState.isPlaying, isPaused = playerState.isPaused, isReady = playerState.isReady, isFinished = playerState.isFinished, onClick = { playerState.toggle() } ) } } // Error handling recorderState.error?.let { error -> ErrorDialog( error = error, onDismiss = { recorderState.clearError() } ) } } }

Component reference

Component

Purpose

Required Props

RecordAudioButton

Recording toggle

isRecording, onClick

PlayAudioButton

Playback toggle

isPlaying, isReady, onClick

AudioPermissionButton

Permission request

onClick

ErrorDialog

Error display

error, onDismiss

Last modified: 13 January 2026