Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion admob/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion analytics/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion appdistribution/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion auth/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ plugins {
alias(libs.plugins.firebase.perf) apply false
alias(libs.plugins.navigation.safeargs) apply false
alias(libs.plugins.gradle.versions) apply true
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.ksp) apply false
}

allprojects {
repositories {
google()
mavenLocal()
google()
mavenCentral()
}
}
Expand Down
2 changes: 1 addition & 1 deletion config/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion crash/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion database/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion dataconnect/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Wed May 08 19:29:05 BST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
27 changes: 25 additions & 2 deletions firebase-ai/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.google.services)
alias(libs.plugins.ksp)
}

configurations.all {
resolutionStrategy {
force("org.jetbrains.kotlin:kotlin-stdlib:2.0.21")
force("org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.21")
force("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21")
}
}

android {
Expand All @@ -30,8 +40,16 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs += listOf(
"-XXLanguage:+ExplicitBackingFields",
"-Xskip-metadata-version-check",
"-Xskip-prerelease-check"
)
}
buildFeatures {
compose = true
Expand Down Expand Up @@ -66,6 +84,7 @@ dependencies {
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.camera.extensions)
implementation("com.google.guava:guava:33.3.1-android")

// Material for XML-based theme
implementation(libs.material)
Expand All @@ -74,6 +93,10 @@ dependencies {
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.ai)
implementation(libs.firebase.ai.ondevice)
implementation(libs.genai.prompt)
implementation(libs.genai.structured.output)
ksp(libs.firebase.ai.ksp.processor)
ksp(libs.genai.structured.compiler)

// Image loading
implementation(libs.coil.compose)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.google.firebase.quickstart.ai

import kotlin.reflect.KClass
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.BitmapFactory
Expand All @@ -26,20 +27,56 @@ import androidx.navigation.NavDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.firebase.quickstart.ai.feature.live.BidiViewModel
import com.google.firebase.quickstart.ai.feature.hybrid.HybridInferenceRoute
import com.google.firebase.quickstart.ai.feature.hybrid.HybridInferenceViewModel
import com.google.firebase.quickstart.ai.feature.structured.StructuredOutputRoute
import com.google.firebase.quickstart.ai.feature.structured.StructuredOutputViewModel
import com.google.firebase.quickstart.ai.feature.live.StreamAudioViewModel
import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeAudioRoute
import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoRoute
import com.google.firebase.quickstart.ai.feature.live.StreamVideoViewModel
import com.google.firebase.quickstart.ai.feature.text.AudioSummarizationRoute
import com.google.firebase.quickstart.ai.feature.text.AudioSummarizationViewModel
import com.google.firebase.quickstart.ai.feature.text.AudioTranslationRoute
import com.google.firebase.quickstart.ai.feature.text.AudioTranslationViewModel
import com.google.firebase.quickstart.ai.feature.text.AutoFunctionCallRoute
import com.google.firebase.quickstart.ai.feature.text.AutoFunctionCallViewModel
import com.google.firebase.quickstart.ai.feature.text.ChatViewModel
import com.google.firebase.quickstart.ai.feature.text.DocumentComparisonRoute
import com.google.firebase.quickstart.ai.feature.text.DocumentComparisonViewModel
import com.google.firebase.quickstart.ai.feature.text.GoogleSearchGroundingRoute
import com.google.firebase.quickstart.ai.feature.text.GoogleSearchGroundingViewModel
import com.google.firebase.quickstart.ai.feature.text.ImageBlogCreatorRoute
import com.google.firebase.quickstart.ai.feature.text.ImageBlogCreatorViewModel
import com.google.firebase.quickstart.ai.feature.text.NanoBanana2Route
import com.google.firebase.quickstart.ai.feature.text.NanoBanana2ViewModel
import com.google.firebase.quickstart.ai.feature.text.NanoBananaLiteRoute
import com.google.firebase.quickstart.ai.feature.text.NanoBananaLiteViewModel
import com.google.firebase.quickstart.ai.feature.text.NanoBananaProRoute
import com.google.firebase.quickstart.ai.feature.text.NanoBananaProViewModel
import com.google.firebase.quickstart.ai.feature.text.NanoBananaRoute
import com.google.firebase.quickstart.ai.feature.text.NanoBananaViewModel
import com.google.firebase.quickstart.ai.feature.text.ServerPromptTemplateRoute
import com.google.firebase.quickstart.ai.feature.text.ServerPromptTemplateViewModel
import com.google.firebase.quickstart.ai.feature.text.SvgRoute
import com.google.firebase.quickstart.ai.feature.text.SvgViewModel
import com.google.firebase.quickstart.ai.feature.text.TranslationRoute
import com.google.firebase.quickstart.ai.feature.text.TranslationViewModel
import com.google.firebase.quickstart.ai.feature.text.VideoHashtagGeneratorRoute
import com.google.firebase.quickstart.ai.feature.text.VideoHashtagGeneratorViewModel
import com.google.firebase.quickstart.ai.feature.text.VideoSummarizationRoute
import com.google.firebase.quickstart.ai.feature.text.VideoSummarizationViewModel
import com.google.firebase.quickstart.ai.feature.text.WeatherChatRoute
import com.google.firebase.quickstart.ai.feature.text.WeatherChatViewModel
import com.google.firebase.quickstart.ai.ui.ChatScreen
import com.google.firebase.quickstart.ai.ui.HybridInferenceScreen
import com.google.firebase.quickstart.ai.ui.ServerPromptScreen
import com.google.firebase.quickstart.ai.ui.StreamRealtimeScreen
import com.google.firebase.quickstart.ai.ui.StreamRealtimeVideoScreen
import com.google.firebase.quickstart.ai.ui.HybridInferenceScreen
import com.google.firebase.quickstart.ai.ui.StructuredOutputScreen
import com.google.firebase.quickstart.ai.ui.SvgScreen
import com.google.firebase.quickstart.ai.ui.navigation.FIREBASE_AI_SAMPLES
import com.google.firebase.quickstart.ai.ui.navigation.MainMenuScreen
import com.google.firebase.quickstart.ai.ui.navigation.ScreenType
import com.google.firebase.quickstart.ai.ui.theme.FirebaseAILogicTheme

class MainActivity : ComponentActivity() {
Expand Down Expand Up @@ -84,48 +121,32 @@ class MainActivity : ComponentActivity() {
}

// Add navigation for all of the samples
FIREBASE_AI_SAMPLES.forEach { sample ->
composable(
route = sample.route::class,
typeMap = emptyMap()
) {
val viewModelClass = sample.viewModelClass?.java
?: return@composable
val vm = viewModel(modelClass = viewModelClass)

when (sample.screenType) {
ScreenType.CHAT -> {
(vm as? ChatViewModel)?.let { ChatScreen(it) }
}

ScreenType.SVG -> {
(vm as? SvgViewModel)?.let { SvgScreen(it) }
}

ScreenType.SERVER_PROMPT -> {
(vm as? ServerPromptTemplateViewModel)?.let { ServerPromptScreen(it) }
}

ScreenType.BIDI -> {
(vm as? BidiViewModel)?.let {
@SuppressLint("MissingPermission")
StreamRealtimeScreen(it)
}
}

ScreenType.BIDI_VIDEO -> {
(vm as? BidiViewModel)?.let {
@SuppressLint("MissingPermission")
StreamRealtimeVideoScreen(it)
}
}

ScreenType.HYBRID -> {
(vm as? HybridInferenceViewModel)?.let { HybridInferenceScreen(it) }
}
}
}
composable<TranslationRoute> { ChatScreen(viewModel<TranslationViewModel>()) }
composable<SvgRoute> { SvgScreen(viewModel<SvgViewModel>()) }
composable<AudioSummarizationRoute> { ChatScreen(viewModel<AudioSummarizationViewModel>()) }
composable<VideoSummarizationRoute> { ChatScreen(viewModel<VideoSummarizationViewModel>()) }
composable<AudioTranslationRoute> { ChatScreen(viewModel<AudioTranslationViewModel>()) }
composable<ImageBlogCreatorRoute> { ChatScreen(viewModel<ImageBlogCreatorViewModel>()) }
composable<NanoBananaLiteRoute> { ChatScreen(viewModel<NanoBananaLiteViewModel>()) }
composable<NanoBanana2Route> { ChatScreen(viewModel<NanoBanana2ViewModel>()) }
composable<NanoBananaProRoute> { ChatScreen(viewModel<NanoBananaProViewModel>()) }
composable<NanoBananaRoute> { ChatScreen(viewModel<NanoBananaViewModel>()) }
composable<DocumentComparisonRoute> { ChatScreen(viewModel<DocumentComparisonViewModel>()) }
composable<VideoHashtagGeneratorRoute> { ChatScreen(viewModel<VideoHashtagGeneratorViewModel>()) }
composable<GoogleSearchGroundingRoute> { ChatScreen(viewModel<GoogleSearchGroundingViewModel>()) }
composable<WeatherChatRoute> { ChatScreen(viewModel<WeatherChatViewModel>()) }
composable<AutoFunctionCallRoute> { ChatScreen(viewModel<AutoFunctionCallViewModel>()) }
composable<StreamRealtimeAudioRoute> {
@SuppressLint("MissingPermission")
StreamRealtimeScreen(viewModel<StreamAudioViewModel>())
}
composable<StreamRealtimeVideoRoute> {
@SuppressLint("MissingPermission")
StreamRealtimeVideoScreen(viewModel<StreamVideoViewModel>())
}
composable<ServerPromptTemplateRoute> { ServerPromptScreen(viewModel<ServerPromptTemplateViewModel>()) }
composable<HybridInferenceRoute> { HybridInferenceScreen(viewModel<HybridInferenceViewModel>()) }
composable<StructuredOutputRoute> { StructuredOutputScreen(viewModel<StructuredOutputViewModel>()) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.google.firebase.quickstart.ai.feature.structured

import com.google.firebase.ai.annotations.Generable
import com.google.firebase.ai.annotations.Guide
import kotlinx.serialization.Serializable

@Generable(description = "A structured review and analysis of a movie or TV show")
@Serializable
data class MovieReview(
@Guide(description = "The official title of the movie or TV show")
val title: String,
@Guide(description = "A concise 1-sentence summary of the critique")
val summary: String,
@Guide(description = "The rating out of 5 stars (1 to 5)")
val rating: Int,
@Guide(description = "Key genre tags or themes")
val tags: List<String>
) {
Comment on lines +9 to +18

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When deserializing structured outputs from LLMs, it is highly recommended to provide default values for all properties. LLMs (especially smaller or on-device models) can occasionally omit fields or return incomplete JSON structures. Providing default values ensures that the parser can fall back gracefully instead of throwing a SerializationException.

Suggested change
data class MovieReview(
@Guide(description = "The official title of the movie or TV show")
val title: String,
@Guide(description = "A concise 1-sentence summary of the critique")
val summary: String,
@Guide(description = "The rating out of 5 stars (1 to 5)")
val rating: Int,
@Guide(description = "Key genre tags or themes")
val tags: List<String>
) {
data class MovieReview(
@Guide(description = "The official title of the movie or TV show")
val title: String = "",
@Guide(description = "A concise 1-sentence summary of the critique")
val summary: String = "",
@Guide(description = "The rating out of 5 stars (1 to 5)")
val rating: Int = 0,
@Guide(description = "Key genre tags or themes")
val tags: List<String> = emptyList()
) {

companion object
}
Loading
Loading