feat(firebase-ai): add transcriptions to Live API sample and reduce abstraction - #2836
feat(firebase-ai): add transcriptions to Live API sample and reduce abstraction#2836thatfiredev wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the live streaming feature by replacing the abstract BidiViewModel with two dedicated ViewModels, StreamAudioViewModel and StreamVideoViewModel, and adding support for real-time transcriptions in both audio and video screens. The review feedback highlights several critical improvements: managing the conversation lifecycle with DisposableEffect and a lifecycle-aware CoroutineScope to prevent leaks, avoiding blocking the main thread with runBlocking during ViewModel initialization, and replacing animateScrollToItem with scrollToItem to prevent UI stuttering during rapid transcription updates.
| @OptIn(PublicPreviewAPI::class) | ||
| class StreamAudioViewModel : BidiViewModel() { | ||
| class StreamAudioViewModel : ViewModel() { | ||
| private var liveSession: LiveSession |
There was a problem hiding this comment.
Declaring liveSession as a non-nullable property initialized via runBlocking in the init block blocks the main thread during ViewModel creation to establish a network connection. This can cause UI freezes or ANR (Application Not Responding) errors. Instead, declare liveSession as lateinit var (or nullable) and connect to the live model asynchronously using viewModelScope.launch.
| private var liveSession: LiveSession | |
| private lateinit var liveSession: LiveSession |
| @OptIn(PublicPreviewAPI::class) | ||
| class StreamVideoViewModel : BidiViewModel() { | ||
| class StreamVideoViewModel : ViewModel() { | ||
| private var liveSession: LiveSession |
There was a problem hiding this comment.
Declaring liveSession as a non-nullable property initialized via runBlocking in the init block blocks the main thread during ViewModel creation to establish a network connection. This can cause UI freezes or ANR (Application Not Responding) errors. Instead, declare liveSession as lateinit var (or nullable) and connect to the live model asynchronously using viewModelScope.launch.
| private var liveSession: LiveSession | |
| private lateinit var liveSession: LiveSession |
| LaunchedEffect(transcriptions.size, transcriptions.lastOrNull()?.text) { | ||
| if (transcriptions.isNotEmpty()) { | ||
| listState.animateScrollToItem(transcriptions.size - 1) | ||
| } | ||
| } |
There was a problem hiding this comment.
Using animateScrollToItem for rapidly updating streaming text (like live transcriptions) can cause severe UI stuttering, lag, and high CPU usage because smooth scroll animations are repeatedly interrupted and restarted. Using scrollToItem is much more efficient and ensures smooth rendering during active streaming.
| LaunchedEffect(transcriptions.size, transcriptions.lastOrNull()?.text) { | |
| if (transcriptions.isNotEmpty()) { | |
| listState.animateScrollToItem(transcriptions.size - 1) | |
| } | |
| } | |
| LaunchedEffect(transcriptions.size, transcriptions.lastOrNull()?.text) { | |
| if (transcriptions.isNotEmpty()) { | |
| listState.scrollToItem(transcriptions.size - 1) | |
| } | |
| } |
| LaunchedEffect(transcriptions.size, transcriptions.lastOrNull()?.text) { | ||
| if (transcriptions.isNotEmpty()) { | ||
| listState.animateScrollToItem(transcriptions.size - 1) | ||
| } | ||
| } |
There was a problem hiding this comment.
Using animateScrollToItem for rapidly updating streaming text (like live transcriptions) can cause severe UI stuttering, lag, and high CPU usage because smooth scroll animations are repeatedly interrupted and restarted. Using scrollToItem is much more efficient and ensures smooth rendering during active streaming.
| LaunchedEffect(transcriptions.size, transcriptions.lastOrNull()?.text) { | |
| if (transcriptions.isNotEmpty()) { | |
| listState.animateScrollToItem(transcriptions.size - 1) | |
| } | |
| } | |
| LaunchedEffect(transcriptions.size, transcriptions.lastOrNull()?.text) { | |
| if (transcriptions.isNotEmpty()) { | |
| listState.scrollToItem(transcriptions.size - 1) | |
| } | |
| } |
There are 2 main changes in this PR: