Skip to content

Commit 8f47ab7

Browse files
Update hybrid SDK (#177)
1 parent 177aaa6 commit 8f47ab7

4 files changed

Lines changed: 122 additions & 5 deletions

File tree

gradle/libs.versions.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[versions]
22
agp = "8.8.2"
33
coilCompose = "3.1.0"
4-
firebaseAiOndevice = "16.0.0-beta01"
5-
firebaseBom = "34.11.0"
4+
firebaseAiOndevice = "16.0.0-beta02"
5+
firebaseBom = "34.14.0"
66
lifecycleRuntimeCompose = "2.9.1"
77
mlkitGenAi = "1.0.0-beta1"
88
kotlin = "2.1.0"

samples/gemini-hybrid/src/main/java/com/android/ai/samples/geminihybrid/GeminiHybridScreen.kt

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import com.android.ai.uicomponent.GenerateButton
7777
import com.android.ai.uicomponent.SampleDetailTopAppBar
7878
import com.android.ai.uicomponent.UndoButton
7979
import com.google.firebase.ai.InferenceMode
80+
import com.google.firebase.ai.OnDeviceModelOption
8081
import com.google.firebase.ai.type.PublicPreviewAPI
8182

8283

@@ -135,6 +136,9 @@ fun GeminiHybridScreen(viewModel: GeminiHybridViewModel = hiltViewModel()) {
135136
onTagToggle = viewModel::toggleTag,
136137
selectedMode = uiState.selectedMode,
137138
onModeSelected = viewModel::setInferenceMode,
139+
selectedModelOption = uiState.selectedModelOption,
140+
onModelOptionSelected = viewModel::setModelOption,
141+
isPreviewModelAvailable = uiState.isPreviewModelAvailable,
138142
onGenerate = {
139143
val tagStrings =
140144
uiState.selectedTags.map { ContextCompat.getString(context, it) }
@@ -185,6 +189,9 @@ fun InitialReviewUi(
185189
onTagToggle: (Int) -> Unit,
186190
selectedMode: InferenceMode,
187191
onModeSelected: (InferenceMode) -> Unit,
192+
selectedModelOption: OnDeviceModelOption?,
193+
onModelOptionSelected: (OnDeviceModelOption?) -> Unit,
194+
isPreviewModelAvailable: Boolean,
188195
onGenerate: () -> Unit,
189196
) {
190197
Text(
@@ -219,6 +226,12 @@ fun InitialReviewUi(
219226
selectedMode = selectedMode,
220227
onModeSelected = onModeSelected,
221228
)
229+
if (isPreviewModelAvailable) {
230+
ModelOptionDropdown(
231+
selectedOption = selectedModelOption,
232+
onOptionSelected = onModelOptionSelected,
233+
)
234+
}
222235

223236
GenerateButton(
224237
text = stringResource(R.string.gemini_hybrid_generate_btn),
@@ -457,6 +470,66 @@ fun InferenceModeDropdown(
457470
}
458471
}
459472

473+
@PublicPreviewAPI
474+
@Composable
475+
fun ModelOptionDropdown(
476+
selectedOption: OnDeviceModelOption?,
477+
onOptionSelected: (OnDeviceModelOption?) -> Unit,
478+
modifier: Modifier = Modifier,
479+
) {
480+
var expanded by remember { mutableStateOf(false) }
481+
val options = listOf(
482+
null to stringResource(R.string.gemini_hybrid_model_default),
483+
OnDeviceModelOption.PREVIEW to stringResource(R.string.gemini_hybrid_model_preview),
484+
OnDeviceModelOption.PREVIEW_FAST to stringResource(R.string.gemini_hybrid_model_preview_fast),
485+
)
486+
val selectedText = options.find { it.first == selectedOption }?.second ?: ""
487+
488+
Box(modifier = modifier.padding(start = 8.dp, top = 12.dp)) {
489+
SplitButtonLayout(
490+
leadingButton = {
491+
SplitButtonDefaults.LeadingButton(
492+
onClick = { expanded = true },
493+
colors = ButtonDefaults.buttonColors(
494+
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
495+
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
496+
),
497+
) {
498+
Text(selectedText)
499+
}
500+
},
501+
trailingButton = {
502+
SplitButtonDefaults.TrailingButton(
503+
onClick = { expanded = true },
504+
colors = ButtonDefaults.buttonColors(
505+
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
506+
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
507+
),
508+
) {
509+
Icon(
510+
imageVector = Icons.Default.ArrowDropDown,
511+
contentDescription = null,
512+
)
513+
}
514+
},
515+
)
516+
DropdownMenu(
517+
expanded = expanded,
518+
onDismissRequest = { expanded = false },
519+
) {
520+
options.forEach { (option, label) ->
521+
DropdownMenuItem(
522+
text = { Text(label) },
523+
onClick = {
524+
onOptionSelected(option)
525+
expanded = false
526+
},
527+
)
528+
}
529+
}
530+
}
531+
}
532+
460533
@Composable
461534
fun StatusText(text: String) {
462535
Text(

samples/gemini-hybrid/src/main/java/com/android/ai/samples/geminihybrid/GeminiHybridViewModel.kt

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import com.google.firebase.ai.InferenceMode
2323
import com.google.firebase.ai.InferenceSource
2424
import com.google.firebase.ai.OnDeviceConfig
2525
import com.google.firebase.ai.ai
26+
import com.google.firebase.ai.OnDeviceModelOption
27+
import com.google.firebase.ai.OnDeviceModelStatus
2628
import com.google.firebase.ai.type.GenerativeBackend
2729
import com.google.firebase.ai.type.PublicPreviewAPI
2830
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -53,6 +55,8 @@ sealed interface GeminiStatus {
5355
@OptIn(PublicPreviewAPI::class)
5456
data class GeminiHybridUiState(
5557
val selectedMode: InferenceMode = InferenceMode.ONLY_ON_DEVICE,
58+
val selectedModelOption: OnDeviceModelOption? = null,
59+
val isPreviewModelAvailable: Boolean = false,
5660
val selectedTags: List<Int> = emptyList(),
5761
val reviewText: String = "",
5862
val reviewInferenceStatus: Int? = null,
@@ -66,6 +70,31 @@ class GeminiHybridViewModel @Inject constructor() : ViewModel() {
6670
private val _uiState = MutableStateFlow(GeminiHybridUiState())
6771
val uiState: StateFlow<GeminiHybridUiState> = _uiState.asStateFlow()
6872

73+
init {
74+
checkPreviewModelAvailability()
75+
}
76+
77+
private fun checkPreviewModelAvailability() {
78+
viewModelScope.launch {
79+
try {
80+
val model = Firebase.ai(backend = GenerativeBackend.googleAI())
81+
.generativeModel(
82+
"gemini-2.5-flash-lite",
83+
onDeviceConfig = OnDeviceConfig(
84+
mode = InferenceMode.ONLY_ON_DEVICE,
85+
modelOption = OnDeviceModelOption.PREVIEW
86+
)
87+
)
88+
val status = model.onDeviceExtension?.checkStatus()
89+
val isAvailable = status == OnDeviceModelStatus.AVAILABLE
90+
_uiState.update { it.copy(isPreviewModelAvailable = isAvailable) }
91+
} catch (e: Exception) {
92+
Log.e("GeminiHybrid", "Failed to check preview model availability", e)
93+
_uiState.update { it.copy(isPreviewModelAvailable = false) }
94+
}
95+
}
96+
}
97+
6998
val tags = listOf(
7099
R.string.location,
71100
R.string.view,
@@ -87,6 +116,10 @@ class GeminiHybridViewModel @Inject constructor() : ViewModel() {
87116
_uiState.update { it.copy(selectedMode = mode) }
88117
}
89118

119+
fun setModelOption(option: OnDeviceModelOption?) {
120+
_uiState.update { it.copy(selectedModelOption = option) }
121+
}
122+
90123
fun toggleTag(tagResId: Int) {
91124
_uiState.update { state ->
92125
val newTags = if (state.selectedTags.contains(tagResId)) {
@@ -130,7 +163,10 @@ class GeminiHybridViewModel @Inject constructor() : ViewModel() {
130163
val model = Firebase.ai(backend = GenerativeBackend.googleAI())
131164
.generativeModel(
132165
"gemini-2.5-flash-lite",
133-
onDeviceConfig = OnDeviceConfig(mode = _uiState.value.selectedMode)
166+
onDeviceConfig = OnDeviceConfig(
167+
mode = _uiState.value.selectedMode,
168+
modelOption = _uiState.value.selectedModelOption
169+
)
134170
)
135171
model.generateContentStream(prompt).collect { chunk ->
136172
val isCloud = chunk.inferenceSource == InferenceSource.IN_CLOUD
@@ -200,7 +236,10 @@ class GeminiHybridViewModel @Inject constructor() : ViewModel() {
200236
val model = Firebase.ai(backend = GenerativeBackend.googleAI())
201237
.generativeModel(
202238
"gemini-2.5-flash-lite",
203-
onDeviceConfig = OnDeviceConfig(mode = _uiState.value.selectedMode)
239+
onDeviceConfig = OnDeviceConfig(
240+
mode = _uiState.value.selectedMode,
241+
modelOption = _uiState.value.selectedModelOption
242+
)
204243
)
205244

206245
model.generateContentStream(prompt).collect { chunk ->
@@ -246,6 +285,8 @@ class GeminiHybridViewModel @Inject constructor() : ViewModel() {
246285
}
247286

248287
fun reset() {
249-
_uiState.value = GeminiHybridUiState()
288+
_uiState.value = GeminiHybridUiState(
289+
isPreviewModelAvailable = _uiState.value.isPreviewModelAvailable
290+
)
250291
}
251292
}

samples/gemini-hybrid/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@
2525
<string name="spacious">SPACIOUS</string>
2626
<string name="natural_light">NATURAL LIGHT</string>
2727
<string name="select_topics_for_your_review">Select topics for your review:</string>
28+
<string name="gemini_hybrid_model_default">Gemini Nano (Default)</string>
29+
<string name="gemini_hybrid_model_preview">Gemini Nano v4 (Full)</string>
30+
<string name="gemini_hybrid_model_preview_fast">Gemini Nano v4 (Fast)</string>
2831
</resources>

0 commit comments

Comments
 (0)