Skip to content

Commit 11d35ad

Browse files
Introduce liveSessionState
1 parent d992fd8 commit 11d35ad

File tree

3 files changed

+34
-55
lines changed

3 files changed

+34
-55
lines changed

ai-catalog/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/TodoScreen.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,14 +203,15 @@ fun TodoInput(text: String, onTextChange: (String) -> Unit, onAddClick: () -> Un
203203
@Composable
204204
fun MicButton(uiState: TodoScreenUiState, onToggle: () -> Unit) {
205205
if (uiState is TodoScreenUiState.Success) {
206-
val successState = uiState as TodoScreenUiState.Success
207206
val micIcon = when {
208-
!successState.isLiveSessionReady -> Icons.Filled.MicOff
209-
successState.isLiveSessionRunning -> Icons.Filled.Mic
207+
uiState.liveSessionState is LiveSessionState.Ready -> Icons.Filled.MicOff
208+
uiState.liveSessionState is LiveSessionState.Running -> Icons.Filled.Mic
209+
uiState.liveSessionState is LiveSessionState.NotReady -> Icons.Filled.MicNone
210+
uiState.liveSessionState is LiveSessionState.Error -> Icons.Filled.MicNone
210211
else -> Icons.Filled.MicNone
211212
}
212213

213-
val containerColor = if (successState.isLiveSessionRunning) {
214+
val containerColor = if (uiState.liveSessionState is LiveSessionState.Running) {
214215
val infiniteTransition =
215216
rememberInfiniteTransition(label = "mic_color_transition")
216217
infiniteTransition.animateColor(
@@ -227,7 +228,7 @@ fun MicButton(uiState: TodoScreenUiState, onToggle: () -> Unit) {
227228
}
228229

229230
FloatingActionButton(
230-
onClick = { if (successState.isLiveSessionReady) onToggle() },
231+
onClick = { if (uiState.liveSessionState !is LiveSessionState.NotReady) onToggle() },
231232
containerColor = containerColor,
232233
) {
233234
Icon(micIcon, stringResource(R.string.interact_with_todolist_by_voice))

ai-catalog/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/TodoScreenUiState.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,18 @@ sealed interface TodoScreenUiState {
2222

2323
data class Success(
2424
val todos: List<Todo> = emptyList(),
25-
val isLiveSessionReady: Boolean = false,
26-
val isLiveSessionRunning: Boolean = false,
25+
val liveSessionState: LiveSessionState
2726
) : TodoScreenUiState
2827

2928
data class Error(
3029
val todos: List<Todo> = emptyList(),
31-
val isLiveSessionReady: Boolean = false,
32-
val isLiveSessionRunning: Boolean = false,
30+
val liveSessionState: LiveSessionState
3331
) : TodoScreenUiState
3432
}
33+
34+
sealed interface LiveSessionState {
35+
data object NotReady : LiveSessionState
36+
data object Ready : LiveSessionState
37+
data object Running : LiveSessionState
38+
data object Error: LiveSessionState
39+
}

ai-catalog/samples/gemini-live-todo/src/main/java/com/android/ai/samples/geminilivetodo/ui/TodoScreenViewModel.kt

Lines changed: 19 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ import com.google.firebase.ai.type.liveGenerationConfig
4343
import dagger.hilt.android.lifecycle.HiltViewModel
4444
import javax.inject.Inject
4545
import kotlinx.coroutines.flow.MutableStateFlow
46+
import kotlinx.coroutines.flow.SharingStarted
4647
import kotlinx.coroutines.flow.StateFlow
4748
import kotlinx.coroutines.flow.asStateFlow
49+
import kotlinx.coroutines.flow.combine
50+
import kotlinx.coroutines.flow.map
51+
import kotlinx.coroutines.flow.stateIn
4852
import kotlinx.coroutines.flow.update
4953
import kotlinx.coroutines.launch
5054
import kotlinx.serialization.json.JsonObject
@@ -56,25 +60,18 @@ import kotlinx.serialization.json.long
5660
@HiltViewModel
5761
class TodoScreenViewModel @Inject constructor(private val todoRepository: TodoRepository) : ViewModel() {
5862
private val TAG = "TodoScreenViewModel"
59-
6063
private var session: LiveSession? = null
6164

62-
private val _uiState = MutableStateFlow<TodoScreenUiState>(TodoScreenUiState.Initial)
63-
val uiState: StateFlow<TodoScreenUiState> = _uiState.asStateFlow()
65+
private val liveSessionState = MutableStateFlow<LiveSessionState>(LiveSessionState.NotReady)
66+
private val todos = todoRepository.todos
6467

65-
init {
66-
viewModelScope.launch {
67-
todoRepository.todos.collect { todos ->
68-
_uiState.update {
69-
if (it is TodoScreenUiState.Success) {
70-
it.copy(todos = todos)
71-
} else {
72-
TodoScreenUiState.Success(todos = todos)
73-
}
74-
}
75-
}
76-
}
77-
}
68+
val uiState: StateFlow<TodoScreenUiState> = combine(liveSessionState, todos) { liveSessionState, todos ->
69+
TodoScreenUiState.Success(todos, liveSessionState)
70+
}.stateIn(
71+
scope = viewModelScope,
72+
started = SharingStarted.WhileSubscribed(5000L),
73+
initialValue = TodoScreenUiState.Initial
74+
)
7875

7976
fun addTodo(taskDescription: String) {
8077
todoRepository.addTodo(taskDescription)
@@ -91,34 +88,21 @@ class TodoScreenViewModel @Inject constructor(private val todoRepository: TodoRe
9188
@SuppressLint("MissingPermission")
9289
fun toggleLiveSession(activity: Activity) {
9390
viewModelScope.launch {
94-
val currentState = _uiState.value
95-
if (currentState !is TodoScreenUiState.Success) return@launch
91+
if (liveSessionState.value is LiveSessionState.NotReady) return@launch
9692

9793
session?.let {
98-
if (!currentState.isLiveSessionRunning) {
94+
if (liveSessionState.value is LiveSessionState.Ready) {
9995
if (ContextCompat.checkSelfPermission(
10096
activity,
10197
Manifest.permission.RECORD_AUDIO,
10298
) == PackageManager.PERMISSION_GRANTED
10399
) {
104100
it.startAudioConversation(::handleFunctionCall)
105-
_uiState.update {
106-
if (it is TodoScreenUiState.Success) {
107-
it.copy(isLiveSessionRunning = true)
108-
} else {
109-
it
110-
}
111-
}
101+
liveSessionState.value = LiveSessionState.Running
112102
}
113103
} else {
114104
it.stopAudioConversation()
115-
_uiState.update {
116-
if (it is TodoScreenUiState.Success) {
117-
it.copy(isLiveSessionRunning = false)
118-
} else {
119-
it
120-
}
121-
}
105+
liveSessionState.value = LiveSessionState.Ready
122106
}
123107
}
124108
}
@@ -192,21 +176,10 @@ class TodoScreenViewModel @Inject constructor(private val todoRepository: TodoRe
192176
session = generativeModel.connect()
193177
} catch (e: Exception) {
194178
Log.e(TAG, "Error connecting to the model", e)
195-
_uiState.update {
196-
TodoScreenUiState.Error(
197-
isLiveSessionReady = false,
198-
isLiveSessionRunning = false,
199-
)
200-
}
179+
liveSessionState.value = LiveSessionState.Error
201180
}
202181

203-
_uiState.update {
204-
if (it is TodoScreenUiState.Success) {
205-
it.copy(isLiveSessionReady = true)
206-
} else {
207-
it
208-
}
209-
}
182+
liveSessionState.value = LiveSessionState.Ready
210183
}
211184
}
212185

0 commit comments

Comments
 (0)