From c3626e43ae9dc75e671c5c56eb062c26eee8db52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Mon, 4 Aug 2025 13:35:38 +0100 Subject: [PATCH 01/52] refactor: use minSdk = 23 (#2703) --- admob/app/build.gradle.kts | 2 +- analytics/app/build.gradle.kts | 2 +- appdistribution/app/build.gradle.kts | 2 +- config/app/build.gradle.kts | 2 +- crash/app/build.gradle.kts | 2 +- dynamiclinks/app/build.gradle.kts | 2 +- inappmessaging/app/build.gradle.kts | 2 +- messaging/app/build.gradle.kts | 2 +- perf/app/build.gradle.kts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index ae2abe3def..8fef66c5fb 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -16,7 +16,7 @@ android { defaultConfig { applicationId = "com.google.samples.quickstart.admobexample" - minSdk = 21 + minSdk = 23 targetSdk = 36 versionCode = 1 versionName = "1.0" diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index 6363217ea6..863b6c8eb9 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -16,7 +16,7 @@ android { defaultConfig { applicationId = "com.google.firebase.quickstart.analytics" - minSdk = 21 + minSdk = 23 targetSdk = 36 versionCode = 1 versionName = "1.0" diff --git a/appdistribution/app/build.gradle.kts b/appdistribution/app/build.gradle.kts index 7e30e7b47e..af22e165f6 100644 --- a/appdistribution/app/build.gradle.kts +++ b/appdistribution/app/build.gradle.kts @@ -10,7 +10,7 @@ android { defaultConfig { applicationId = "com.google.firebase.appdistributionquickstart" - minSdk = 21 + minSdk = 23 targetSdk = 36 versionCode = 1 versionName = "1.0" diff --git a/config/app/build.gradle.kts b/config/app/build.gradle.kts index ae548e4656..6af20ec5a7 100644 --- a/config/app/build.gradle.kts +++ b/config/app/build.gradle.kts @@ -16,7 +16,7 @@ android { defaultConfig { applicationId = "com.google.samples.quickstart.config" - minSdk = 21 + minSdk = 23 targetSdk = 36 versionCode = 1 versionName = "1.0" diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index 0c9122af80..31a226e6dd 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -17,7 +17,7 @@ android { defaultConfig { applicationId = "com.google.samples.quickstart.crash" - minSdk = 21 + minSdk = 23 targetSdk = 36 versionCode = 1 versionName = "1.0" diff --git a/dynamiclinks/app/build.gradle.kts b/dynamiclinks/app/build.gradle.kts index 75308067f9..c6160b9a59 100644 --- a/dynamiclinks/app/build.gradle.kts +++ b/dynamiclinks/app/build.gradle.kts @@ -16,7 +16,7 @@ android { defaultConfig { applicationId = "com.google.firebase.quickstart.deeplinks" - minSdk = 21 + minSdk = 23 targetSdk = 36 versionCode = 1 versionName = "1.0" diff --git a/inappmessaging/app/build.gradle.kts b/inappmessaging/app/build.gradle.kts index f55c855cba..aff7b54cfb 100644 --- a/inappmessaging/app/build.gradle.kts +++ b/inappmessaging/app/build.gradle.kts @@ -9,7 +9,7 @@ android { compileSdk = 36 defaultConfig { applicationId = "com.google.firebase.fiamquickstart" - minSdk = 21 + minSdk = 23 targetSdk = 36 versionCode = 1 versionName = "1.0" diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index 40998198c4..6dd8b29d25 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -16,7 +16,7 @@ android { defaultConfig { applicationId = "com.google.firebase.quickstart.fcm" - minSdk = 21 + minSdk = 23 targetSdk = 36 versionCode = 1 versionName = "1.0" diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index 09512d7186..d35398ef6e 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -18,7 +18,7 @@ android { defaultConfig { applicationId = "com.google.firebase.quickstart.perfmon" - minSdk = 21 + minSdk = 23 targetSdk = 36 versionCode = 1 versionName = "1.0" From 277192c2a136ef801c1b2e6cee97a173cc7390b7 Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Mon, 4 Aug 2025 12:01:11 -0400 Subject: [PATCH 02/52] [FirebaseAI] Add Grounding with Google search sample (#2687) --- firebase-ai/app/build.gradle.kts | 7 +- .../quickstart/ai/FirebaseAISamples.kt | 13 ++ .../quickstart/ai/feature/text/ChatScreen.kt | 118 ++++++++++++++++-- .../ai/feature/text/ChatViewModel.kt | 42 +++++-- .../app/src/main/res/values/themes.xml | 3 +- gradle/libs.versions.toml | 6 +- 6 files changed, 167 insertions(+), 22 deletions(-) diff --git a/firebase-ai/app/build.gradle.kts b/firebase-ai/app/build.gradle.kts index 7c361bc007..d0c453b7bb 100644 --- a/firebase-ai/app/build.gradle.kts +++ b/firebase-ai/app/build.gradle.kts @@ -60,6 +60,11 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.savedstate) implementation(libs.kotlinx.serialization.json) + // Webkit + implementation(libs.androidx.webkit) + + // Material for XML-based theme + implementation(libs.material) // Firebase implementation(platform(libs.firebase.bom)) @@ -72,4 +77,4 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) -} \ No newline at end of file +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index c61a12aa0a..f8a68c1a02 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -241,4 +241,17 @@ val FIREBASE_AI_SAMPLES = listOf( text("What was the weather in Boston, MA on October 17, 2024?") } ), + Sample( + title = "Grounding with Google Search", + description = "Use Grounding with Google Search to get responses based on up-to-date information from the web.", + navRoute = "chat", + categories = listOf(Category.TEXT, Category.DOCUMENT), + modelName = "gemini-2.5-flash", + tools = listOf(Tool.googleSearch()), + initialPrompt = content { + text( + "What's the weather in Chicago this weekend?" + ) + }, + ), ) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt index b55cc89ced..768841d892 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt @@ -1,17 +1,23 @@ package com.google.firebase.quickstart.ai.feature.text +import android.content.Intent import android.graphics.Bitmap import android.net.Uri import android.provider.OpenableColumns import android.text.format.Formatter +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -22,6 +28,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Send @@ -31,6 +38,7 @@ import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults @@ -50,16 +58,22 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel -import com.google.firebase.ai.type.Content +import androidx.webkit.WebSettingsCompat +import androidx.webkit.WebViewFeature import com.google.firebase.ai.type.FileDataPart import com.google.firebase.ai.type.ImagePart import com.google.firebase.ai.type.InlineDataPart import com.google.firebase.ai.type.TextPart +import com.google.firebase.ai.type.WebGroundingChunk import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @@ -70,7 +84,7 @@ class ChatRoute(val sampleId: String) fun ChatScreen( chatViewModel: ChatViewModel = viewModel() ) { - val messages: List by chatViewModel.messages.collectAsStateWithLifecycle() + val messages: List by chatViewModel.messages.collectAsStateWithLifecycle() val isLoading: Boolean by chatViewModel.isLoading.collectAsStateWithLifecycle() val errorMessage: String? by chatViewModel.errorMessage.collectAsStateWithLifecycle() val attachments: List by chatViewModel.attachments.collectAsStateWithLifecycle() @@ -162,17 +176,19 @@ fun ChatScreen( @Composable fun ChatBubbleItem( - chatMessage: Content + message: UiChatMessage ) { - val isModelMessage = chatMessage.role == "model" + val isModelMessage = message.content.role == "model" - val backgroundColor = when (chatMessage.role) { + val isDarkTheme = isSystemInDarkTheme() + + val backgroundColor = when (message.content.role) { "user" -> MaterialTheme.colorScheme.tertiaryContainer else -> MaterialTheme.colorScheme.secondaryContainer } val textColor = if (isModelMessage) { - MaterialTheme.colorScheme.onSecondaryContainer + MaterialTheme.colorScheme.onBackground } else { MaterialTheme.colorScheme.onTertiaryContainer } @@ -196,7 +212,7 @@ fun ChatBubbleItem( .fillMaxWidth() ) { Text( - text = chatMessage.role?.uppercase() ?: "USER", + text = message.content.role?.uppercase() ?: "USER", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(bottom = 4.dp) ) @@ -212,7 +228,7 @@ fun ChatBubbleItem( .padding(16.dp) .fillMaxWidth() ) { - chatMessage.parts.forEach { part -> + message.content.parts.forEach { part -> when (part) { is TextPart -> { Text( @@ -272,6 +288,56 @@ fun ChatBubbleItem( } } } + message.groundingMetadata?.let { metadata -> + HorizontalDivider(modifier = Modifier.padding(vertical = 18.dp)) + + // Search Entry Point (WebView) + metadata.searchEntryPoint?.let { searchEntryPoint -> + val context = LocalContext.current + AndroidView(factory = { + WebView(it).apply { + webViewClient = object : WebViewClient() { + override fun shouldOverrideUrlLoading( + view: WebView?, + request: WebResourceRequest? + ): Boolean { + request?.url?.let { uri -> + val intent = Intent(Intent.ACTION_VIEW, uri) + context.startActivity(intent) + } + // Return true to indicate we handled the URL loading + return true + } + } + + setBackgroundColor(android.graphics.Color.TRANSPARENT) + loadDataWithBaseURL( + null, + searchEntryPoint.renderedContent, + "text/html", + "UTF-8", + null + ) + } + }, + modifier = Modifier + .clip(RoundedCornerShape(22.dp)) + .fillMaxHeight() + .fillMaxWidth() + ) + } + + if (metadata.groundingChunks.isNotEmpty()) { + Text( + text = "Sources", + style = MaterialTheme.typography.titleSmall, + modifier = Modifier.padding(top = 16.dp, bottom = 8.dp) + ) + metadata.groundingChunks.forEach { chunk -> + chunk.web?.let { SourceLinkView(it) } + } + } + } } } } @@ -279,9 +345,41 @@ fun ChatBubbleItem( } } +@Composable +fun SourceLinkView( + webChunk: WebGroundingChunk +) { + val context = LocalContext.current + val annotatedString = AnnotatedString.Builder(webChunk.title ?: "Untitled Source").apply { + addStyle( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline + ), + start = 0, + end = webChunk.title?.length ?: "Untitled Source".length + ) + webChunk.uri?.let { addStringAnnotation("URL", it, 0, it.length) } + }.toAnnotatedString() + + Row(modifier = Modifier.padding(bottom = 8.dp)) { + Icon( + Icons.Default.Attachment, + contentDescription = "Source link", + modifier = Modifier.padding(end = 8.dp) + ) + ClickableText(text = annotatedString, onClick = { offset -> + annotatedString.getStringAnnotations(tag = "URL", start = offset, end = offset) + .firstOrNull()?.let { annotation -> + context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(annotation.item))) + } + }) + } +} + @Composable fun ChatList( - chatMessages: List, + chatMessages: List, listState: LazyListState, modifier: Modifier = Modifier ) { @@ -470,4 +568,4 @@ fun AttachmentsList( } } } -} +} \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt index 665d2ef616..33afaad2ee 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt @@ -15,6 +15,8 @@ import com.google.firebase.ai.type.Content import com.google.firebase.ai.type.FileDataPart import com.google.firebase.ai.type.FunctionResponsePart import com.google.firebase.ai.type.GenerateContentResponse +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.GroundingMetadata import com.google.firebase.ai.type.TextPart import com.google.firebase.ai.type.asTextOrNull import com.google.firebase.ai.type.content @@ -25,6 +27,14 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.serialization.json.jsonPrimitive +/** + * A wrapper for a model [Content] object that includes additional UI-specific metadata. + */ +data class UiChatMessage( + val content: Content, + val groundingMetadata: GroundingMetadata? = null, +) + class ChatViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { @@ -42,10 +52,10 @@ class ChatViewModel( private val _errorMessage = MutableStateFlow(null) val errorMessage: StateFlow = _errorMessage - private val _messageList: MutableList = - sample.chatHistory.toMutableStateList() - private val _messages = MutableStateFlow>(_messageList) - val messages: StateFlow> = + private val _messageList: MutableList = + sample.chatHistory.map { UiChatMessage(it) }.toMutableStateList() + private val _messages = MutableStateFlow>(_messageList) + val messages: StateFlow> = _messages private val _attachmentsList: MutableList = @@ -86,16 +96,28 @@ class ChatViewModel( .text(userMessage) .build() - _messageList.add(prompt) + _messageList.add(UiChatMessage(prompt)) viewModelScope.launch { _isLoading.value = true try { val response = chat.sendMessage(prompt) if (response.functionCalls.isEmpty()) { - // Samples without function calling can simply display - // the response in the UI - _messageList.add(response.candidates.first().content) + // Samples without function calling can display the response in the UI + val candidate = response.candidates.first() + + // Compliance check for grounding + if (candidate.groundingMetadata != null + && candidate.groundingMetadata?.groundingChunks?.isNotEmpty() == true + && candidate.groundingMetadata?.searchEntryPoint == null) { + _errorMessage.value = + "Could not display the response because it was missing required attribution components." + } else { + _messageList.add( + UiChatMessage(candidate.content, candidate.groundingMetadata) + ) + _errorMessage.value = null // clear errors + } } else { // Samples WITH function calling need to perform // additional handling @@ -154,7 +176,9 @@ class ChatViewModel( }) Log.d("ChatViewModel", "Model responded with: ${finalResponse.text}") - _messageList.add(finalResponse.candidates.first().content) + val candidate = finalResponse.candidates.first() + _messageList.add(UiChatMessage(candidate.content, + candidate.groundingMetadata)) } else -> { diff --git a/firebase-ai/app/src/main/res/values/themes.xml b/firebase-ai/app/src/main/res/values/themes.xml index d82976cfb7..35071aca45 100644 --- a/firebase-ai/app/src/main/res/values/themes.xml +++ b/firebase-ai/app/src/main/res/values/themes.xml @@ -1,5 +1,6 @@ - - - diff --git a/dynamiclinks/app/src/screen.png b/dynamiclinks/app/src/screen.png deleted file mode 100644 index a50025f863e1e27cbd45360e1326eb0d79e4cbbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139728 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 diff --git a/dynamiclinks/build.gradle.kts b/dynamiclinks/build.gradle.kts deleted file mode 100644 index 62709b0df1..0000000000 --- a/dynamiclinks/build.gradle.kts +++ /dev/null @@ -1,22 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false - id("com.google.gms.google-services") version "4.4.3" apply false -} - -allprojects { - repositories { - mavenLocal() - google() - mavenCentral() - } -} - -tasks { - register("clean", Delete::class) { - delete(rootProject.layout.buildDirectory) - } -} diff --git a/dynamiclinks/gradle.properties b/dynamiclinks/gradle.properties deleted file mode 100644 index 6dd0218ed7..0000000000 --- a/dynamiclinks/gradle.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m -android.useAndroidX=true - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true diff --git a/dynamiclinks/gradle/wrapper/gradle-wrapper.jar b/dynamiclinks/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index c1962a79e29d3e0ab67b14947c167a862655af9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62076 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 diff --git a/dynamiclinks/gradle/wrapper/gradle-wrapper.properties b/dynamiclinks/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d30212c04b..0000000000 --- a/dynamiclinks/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/dynamiclinks/gradlew b/dynamiclinks/gradlew deleted file mode 100755 index aeb74cbb43..0000000000 --- a/dynamiclinks/gradlew +++ /dev/null @@ -1,245 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/dynamiclinks/gradlew.bat b/dynamiclinks/gradlew.bat deleted file mode 100644 index 6689b85bee..0000000000 --- a/dynamiclinks/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/dynamiclinks/settings.gradle.kts b/dynamiclinks/settings.gradle.kts deleted file mode 100644 index 76fe7b94aa..0000000000 --- a/dynamiclinks/settings.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -pluginManagement { - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -include(":app") - -// Required so that gradle can resolve these dependencies even when -// building only a single project. -include(":internal:lintchecks") -project(":internal:lintchecks").projectDir = file("../internal/lintchecks") -include(":internal:lint") -project(":internal:lint").projectDir = file("../internal/lint") -include(":internal:chooserx") -project(":internal:chooserx").projectDir = file("../internal/chooserx") \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index aef55c6cf9..38bf5ce183 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,6 @@ include(":admob:app", ":crash:app", ":database:app", ":dataconnect:app", - ":dynamiclinks:app", ":firestore:app", ":functions:app", ":internal:chooserx", From 4a85465565573b46d9efb5cf0a25928885040b5b Mon Sep 17 00:00:00 2001 From: DPEBot Date: Wed, 13 Aug 2025 06:29:52 -0700 Subject: [PATCH 08/52] Auto-update dependencies. (#2711) --- admob/app/build.gradle.kts | 2 +- analytics/app/build.gradle.kts | 2 +- appdistribution/app/build.gradle.kts | 2 +- auth/app/build.gradle.kts | 2 +- build.gradle.kts | 4 ++-- config/app/build.gradle.kts | 2 +- crash/app/build.gradle.kts | 2 +- crash/build.gradle.kts | 2 +- database/app/build.gradle.kts | 2 +- firebase-ai/gradle/libs.versions.toml | 2 +- firestore/app/build.gradle.kts | 2 +- functions/app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- inappmessaging/app/build.gradle.kts | 2 +- messaging/app/build.gradle.kts | 2 +- perf/app/build.gradle.kts | 2 +- perf/build.gradle.kts | 2 +- storage/app/build.gradle.kts | 2 +- 18 files changed, 19 insertions(+), 19 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index 2cea71b221..abdbce4ff7 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation("com.google.android.gms:play-services-ads:23.3.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // For an optimal experience using AdMob, add the Firebase SDK // for Google Analytics. This is recommended, but not required. diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index 2c0a2cc814..fed3e55270 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.2") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // Firebase Analytics implementation("com.google.firebase:firebase-analytics") diff --git a/appdistribution/app/build.gradle.kts b/appdistribution/app/build.gradle.kts index 2b5ccd1ab7..d1bd79064d 100644 --- a/appdistribution/app/build.gradle.kts +++ b/appdistribution/app/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // ADD the SDK to the "prerelease" variant only (example) implementation("com.google.firebase:firebase-appdistribution:16.0.0-beta16") diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index 5c5df34fc9..b69f19a2e3 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -62,7 +62,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.3") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // Firebase Authentication implementation("com.google.firebase:firebase-auth") diff --git a/build.gradle.kts b/build.gradle.kts index 459e3d4f8d..38eb626a4a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,8 +5,8 @@ plugins { id("com.android.library") version "8.12.0" apply false id("org.jetbrains.kotlin.android") version "2.2.0" apply false id("com.google.gms.google-services") version "4.4.3" apply false - id("com.google.firebase.crashlytics") version "3.0.5" apply false - id("com.google.firebase.firebase-perf") version "2.0.0" apply false + id("com.google.firebase.crashlytics") version "3.0.6" apply false + id("com.google.firebase.firebase-perf") version "2.0.1" apply false id("androidx.navigation.safeargs") version "2.9.3" apply false id("com.github.ben-manes.versions") version "0.52.0" apply true id("org.jetbrains.kotlin.plugin.compose") version "2.2.0" apply false diff --git a/config/app/build.gradle.kts b/config/app/build.gradle.kts index 45e404f13f..b2d41eb5b6 100644 --- a/config/app/build.gradle.kts +++ b/config/app/build.gradle.kts @@ -53,7 +53,7 @@ dependencies { implementation("com.google.android.material:material:1.12.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // Firebase Remote Config implementation("com.google.firebase:firebase-config") diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index f8f9e331d4..e971d5cf26 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -60,7 +60,7 @@ dependencies { implementation("androidx.activity:activity-ktx:1.10.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // Firebase Crashlytics implementation("com.google.firebase:firebase-crashlytics") diff --git a/crash/build.gradle.kts b/crash/build.gradle.kts index 977073e657..2ecac7b1c1 100644 --- a/crash/build.gradle.kts +++ b/crash/build.gradle.kts @@ -5,7 +5,7 @@ plugins { id("com.android.library") version "8.12.0" apply false id("org.jetbrains.kotlin.android") version "2.2.0" apply false id("com.google.gms.google-services") version "4.4.3" apply false - id("com.google.firebase.crashlytics") version "3.0.5" apply false + id("com.google.firebase.crashlytics") version "3.0.6" apply false } allprojects { diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index 5ea2a937e4..fc533a6a75 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.3") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // Firebase Realtime Database implementation("com.google.firebase:firebase-database") diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index 100a838ecf..2a59598b0e 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -5,7 +5,7 @@ composeBom = "2024.09.00" composeNavigation = "2.9.3" coreKtx = "1.16.0" espressoCore = "3.7.0" -firebaseBom = "34.0.0" +firebaseBom = "34.1.0" junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.0.21" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index 13c982815c..08817a1e59 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // Firestore implementation("com.google.firebase:firebase-firestore") diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index 9787c28a2a..52e55b98a7 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { implementation("com.google.android.material:material:1.12.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // Cloud Functions for Firebase implementation("com.google.firebase:firebase-functions") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c5a17d3b4e..92eacd3610 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "8.12.0" coilCompose = "2.7.0" -firebaseBom = "34.0.0" +firebaseBom = "34.1.0" kotlin = "2.2.0" coreKtx = "1.16.0" junit = "4.13.2" diff --git a/inappmessaging/app/build.gradle.kts b/inappmessaging/app/build.gradle.kts index b8d45f6058..b0127c55ab 100644 --- a/inappmessaging/app/build.gradle.kts +++ b/inappmessaging/app/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // FIAM implementation("com.google.firebase:firebase-inappmessaging-display") diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index 50c6117874..fb8052f213 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { implementation("com.google.android.material:material:1.12.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // Firebase Cloud Messaging implementation("com.google.firebase:firebase-messaging") diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index 454d6b809c..7b7ee1cf31 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -64,7 +64,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // Firebase Performance Monitoring implementation("com.google.firebase:firebase-perf") diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index 2c50fce819..e9567d9093 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -5,7 +5,7 @@ plugins { id("com.android.library") version "8.12.0" apply false id("org.jetbrains.kotlin.android") version "2.2.0" apply false id("com.google.gms.google-services") version "4.4.3" apply false - id("com.google.firebase.firebase-perf") version "2.0.0" apply false + id("com.google.firebase.firebase-perf") version "2.0.1" apply false } allprojects { diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index 6ffa06ad86..80867be061 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + implementation(platform("com.google.firebase:firebase-bom:34.1.0")) // Cloud Storage for Firebase implementation("com.google.firebase:firebase-storage") From 7f00c575e3c45110cbdde4867e197da81b633e3e Mon Sep 17 00:00:00 2001 From: DPEBot Date: Thu, 14 Aug 2025 07:46:33 -0700 Subject: [PATCH 09/52] Auto-update dependencies. (#2712) --- admob/app/build.gradle.kts | 2 +- firebase-ai/gradle/libs.versions.toml | 2 +- firestore/app/build.gradle.kts | 4 ++-- functions/app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 4 ++-- messaging/app/build.gradle.kts | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index abdbce4ff7..b66a76d1e3 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { // for Google Analytics. This is recommended, but not required. implementation("com.google.firebase:firebase-analytics") - debugImplementation("androidx.fragment:fragment-testing:1.8.8") + debugImplementation("androidx.fragment:fragment-testing:1.8.9") androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") androidTestImplementation("androidx.test:rules:1.7.0") androidTestImplementation("androidx.test:runner:1.7.0") diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index 2a59598b0e..31875911a7 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -3,7 +3,7 @@ activityCompose = "1.10.1" agp = "8.9.2" composeBom = "2024.09.00" composeNavigation = "2.9.3" -coreKtx = "1.16.0" +coreKtx = "1.17.0" espressoCore = "3.7.0" firebaseBom = "34.1.0" junit = "4.13.2" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index 08817a1e59..c618a7af0b 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -76,12 +76,12 @@ dependencies { // Support Libs implementation("androidx.activity:activity-ktx:1.10.1") implementation("androidx.appcompat:appcompat:1.7.1") - implementation("androidx.core:core-ktx:1.16.0") + implementation("androidx.core:core-ktx:1.17.0") implementation("androidx.vectordrawable:vectordrawable-animated:1.2.0") implementation("androidx.cardview:cardview:1.0.0") implementation("androidx.browser:browser:1.5.0") implementation("com.google.android.material:material:1.12.0") - implementation("androidx.media:media:1.7.0") + implementation("androidx.media:media:1.7.1") implementation("androidx.recyclerview:recyclerview:1.4.0") implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.navigation:navigation-fragment-ktx:2.9.3") diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index 52e55b98a7..c803c33efd 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -50,7 +50,7 @@ dependencies { implementation(project(":internal:chooserx")) implementation("androidx.activity:activity-ktx:1.10.1") - implementation("androidx.fragment:fragment-ktx:1.8.8") + implementation("androidx.fragment:fragment-ktx:1.8.9") implementation("androidx.appcompat:appcompat:1.7.1") implementation("com.google.android.material:material:1.12.0") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 92eacd3610..cc03c74006 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,14 +3,14 @@ agp = "8.12.0" coilCompose = "2.7.0" firebaseBom = "34.1.0" kotlin = "2.2.0" -coreKtx = "1.16.0" +coreKtx = "1.17.0" junit = "4.13.2" junitVersion = "1.3.0" espressoCore = "3.7.0" kotlinxSerializationCore = "1.9.0" lifecycle = "2.9.2" activityCompose = "1.10.1" -composeBom = "2025.07.00" +composeBom = "2025.08.00" googleServices = "4.4.3" composeNavigation = "2.9.3" material = "1.12.0" diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index fb8052f213..621a538468 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -58,11 +58,11 @@ dependencies { implementation(project(":internal:chooserx")) implementation("androidx.annotation:annotation:1.9.1") implementation("androidx.vectordrawable:vectordrawable-animated:1.2.0") - implementation("androidx.core:core-ktx:1.16.0") + implementation("androidx.core:core-ktx:1.17.0") // Required when asking for permission to post notifications (starting in Android 13) implementation("androidx.activity:activity-ktx:1.10.1") - implementation("androidx.fragment:fragment-ktx:1.8.8") + implementation("androidx.fragment:fragment-ktx:1.8.9") implementation("com.google.android.material:material:1.12.0") From f8225b26358afa6f77f8d18c4f4bbff6598cd5f9 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Date: Mon, 18 Aug 2025 23:00:24 -0400 Subject: [PATCH 10/52] Add missing deps to firebase-ai catalog (#2714) When importing the project directly in AndroidStudio, the dependencies are not found and it fails to build. Additionally, bump some deps. --- firebase-ai/gradle/libs.versions.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index 31875911a7..1fa28c4f7d 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -12,6 +12,8 @@ kotlin = "2.0.21" kotlinxSerializationCore = "1.9.0" lifecycle = "2.9.2" lifecycleRuntimeKtx = "2.8.7" +material = "1.12.0" +webkit = "1.14.0" [libraries] androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } @@ -32,11 +34,13 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"} firebase-ai = { module = "com.google.firebase:firebase-ai" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } junit = { group = "junit", name = "junit", version.ref = "junit" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationCore" } +material = { module = "com.google.android.material:material", version.ref = "material" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 9dffec20b81440429c2743087a46bb296ab4620e Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Tue, 26 Aug 2025 12:03:25 -0400 Subject: [PATCH 11/52] Add imagen editing cases to the Firebase AI quickstart (#2702) * add imagen editing cases to the quickstart * add outpainting example * fixes for comments, plus adding subject references and style customization * Add atachment list fo imagen3, also specify that image editing is vertex only * typo * Added options to inpainting and outpainting, also added downscaling to attached images to speed up generation time * format * checkpoint * change radio buttons to dropdown, and fix minor typos * add wiring * make the extra image for style transfer more obvious, and fix all the UI issues that this caused * adding label to style transfer and formating all files * fixes for comments * rename list * fix inpainting description * centralize all image genration code in ImagenViewModel.kt * clean up imports --------- Co-authored-by: David Motsonashvili --- .../quickstart/ai/FirebaseAISamples.kt | 59 ++++++- .../firebase/quickstart/ai/MainActivity.kt | 7 + .../ai/feature/media/imagen/EditingMode.kt | 9 + .../ai/feature/media/imagen/ImagenScreen.kt | 167 ++++++++++++++++-- .../feature/media/imagen/ImagenViewModel.kt | 149 +++++++++++++++- .../quickstart/ai/feature/text/ChatScreen.kt | 77 ++++---- .../quickstart/ai/ui/navigation/Sample.kt | 15 +- .../app/src/main/res/drawable/cat.jpeg | Bin 0 -> 144342 bytes .../res/drawable/round_arrow_drop_down_24.xml | 5 + .../gradle/wrapper/gradle-wrapper.properties | 4 +- 10 files changed, 428 insertions(+), 64 deletions(-) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt create mode 100644 firebase-ai/app/src/main/res/drawable/cat.jpeg create mode 100644 firebase-ai/app/src/main/res/drawable/round_arrow_drop_down_24.xml diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index f8a68c1a02..49bd5dc181 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -2,14 +2,17 @@ package com.google.firebase.quickstart.ai import com.google.firebase.ai.type.FunctionDeclaration import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.PublicPreviewAPI import com.google.firebase.ai.type.ResponseModality import com.google.firebase.ai.type.Schema import com.google.firebase.ai.type.Tool import com.google.firebase.ai.type.content import com.google.firebase.ai.type.generationConfig +import com.google.firebase.quickstart.ai.feature.media.imagen.EditingMode import com.google.firebase.quickstart.ai.ui.navigation.Category import com.google.firebase.quickstart.ai.ui.navigation.Sample +@OptIn(PublicPreviewAPI::class) val FIREBASE_AI_SAMPLES = listOf( Sample( title = "Travel tips", @@ -131,7 +134,61 @@ val FIREBASE_AI_SAMPLES = listOf( text( "A photo of a modern building with water in the background" ) - } + }, + allowEmptyPrompt = false, + editingMode = EditingMode.GENERATE, + ), + Sample( + title = "Imagen 3 - Inpainting (Vertex AI)", + description = "Replace part of an image using Imagen 3", + modelName = "imagen-3.0-capability-001", + backend = GenerativeBackend.vertexAI(), + navRoute = "imagen", + categories = listOf(Category.IMAGE), + initialPrompt = content { text("A sunny beach") }, + includeAttach = true, + allowEmptyPrompt = true, + selectionOptions = listOf("Mask", "Background", "Foreground"), + editingMode = EditingMode.INPAINTING, + ), + Sample( + title = "Imagen 3 - Outpainting (Vertex AI)", + description = "Expand an image by drawing in more background", + modelName = "imagen-3.0-capability-001", + backend = GenerativeBackend.vertexAI(), + navRoute = "imagen", + categories = listOf(Category.IMAGE), + initialPrompt = content { text("") }, + includeAttach = true, + allowEmptyPrompt = true, + selectionOptions = listOf("Image Alignment", "Center", "Top", "Bottom", "Left", "Right"), + editingMode = EditingMode.OUTPAINTING, + ), + Sample( + title = "Imagen 3 - Subject Reference (Vertex AI)", + description = "Generate an image using a referenced subject (must be an animal)", + modelName = "imagen-3.0-capability-001", + backend = GenerativeBackend.vertexAI(), + navRoute = "imagen", + categories = listOf(Category.IMAGE), + initialPrompt = content { text(" flying through space") }, + includeAttach = true, + allowEmptyPrompt = false, + editingMode = EditingMode.SUBJECT_REFERENCE, + ), + Sample( + title = "Imagen 3 - Style Transfer (Vertex AI)", + description = "Change the art style of a cat picture using a reference", + modelName = "imagen-3.0-capability-001", + backend = GenerativeBackend.vertexAI(), + navRoute = "imagen", + categories = listOf(Category.IMAGE), + initialPrompt = content { text("A picture of a cat") }, + includeAttach = true, + allowEmptyPrompt = true, + additionalImage = MainActivity.catImage, + imageLabels = listOf("Style Target", "Style Source"), + editingMode = EditingMode.STYLE_TRANSFER ), Sample( title = "Gemini 2.0 Flash - image generation", diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt index 998e4612a7..a1508d0d2d 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt @@ -1,5 +1,7 @@ package com.google.firebase.quickstart.ai +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -22,6 +24,7 @@ import androidx.navigation.NavDestination import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import com.google.firebase.ai.type.toImagenInlineImage import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeRoute import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeScreen import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenRoute @@ -36,6 +39,7 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() + catImage = BitmapFactory.decodeResource(applicationContext.resources, R.drawable.cat) setContent { val navController = rememberNavController() @@ -110,4 +114,7 @@ class MainActivity : ComponentActivity() { }) } } + companion object{ + lateinit var catImage: Bitmap + } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt new file mode 100644 index 0000000000..6e997092d6 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt @@ -0,0 +1,9 @@ +package com.google.firebase.quickstart.ai.feature.media.imagen + +enum class EditingMode { + GENERATE, + INPAINTING, + OUTPAINTING, + SUBJECT_REFERENCE, + STYLE_TRANSFER, +} \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenScreen.kt index e5d654a895..451ebe77e4 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenScreen.kt @@ -1,16 +1,29 @@ package com.google.firebase.quickstart.ai.feature.media.imagen +import android.net.Uri +import android.provider.OpenableColumns +import android.text.format.Formatter +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ElevatedCard import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField @@ -18,15 +31,24 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.firebase.quickstart.ai.R +import com.google.firebase.quickstart.ai.feature.text.Attachment +import com.google.firebase.quickstart.ai.feature.text.AttachmentsList +import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @Serializable @@ -40,9 +62,35 @@ fun ImagenScreen( val errorMessage by imagenViewModel.errorMessage.collectAsStateWithLifecycle() val isLoading by imagenViewModel.isLoading.collectAsStateWithLifecycle() val generatedImages by imagenViewModel.generatedBitmaps.collectAsStateWithLifecycle() + val attachedImage by imagenViewModel.attachedImage.collectAsStateWithLifecycle() + val context = LocalContext.current + val contentResolver = context.contentResolver + val scope = rememberCoroutineScope() + val openDocument = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { optionalUri: Uri? -> + optionalUri?.let { uri -> + var fileName: String? = null + // Fetch file name and size + contentResolver.query(uri, null, null, null, null)?.use { cursor -> + val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) + cursor.moveToFirst() + val humanReadableSize = Formatter.formatShortFileSize( + context, cursor.getLong(sizeIndex) + ) + fileName = "${cursor.getString(nameIndex)} ($humanReadableSize)" + } + + contentResolver.openInputStream(uri)?.use { stream -> + val bytes = stream.readBytes() + scope.launch { + imagenViewModel.attachImage(bytes) + } + } + } + } Column( - modifier = Modifier + modifier = Modifier.verticalScroll(rememberScrollState()) ) { ElevatedCard( modifier = Modifier @@ -59,18 +107,49 @@ fun ImagenScreen( .padding(16.dp) .fillMaxWidth() ) - TextButton( - onClick = { - if (imagenPrompt.isNotBlank()) { - imagenViewModel.generateImages(imagenPrompt) - } - }, - modifier = Modifier - .padding(end = 16.dp, bottom = 16.dp) - .align(Alignment.End) - ) { - Text("Generate") + if (imagenViewModel.selectionOptions.isNotEmpty()) { + DropDownMenu(imagenViewModel.selectionOptions) { imagenViewModel.selectOption(it) } } + val attachmentsList = buildList { + if (imagenViewModel.additionalImage != null) { + add( + Attachment( + imagenViewModel.imageLabels.getOrElse(0) { "" }, + imagenViewModel.additionalImage + ) + ) + } + if (attachedImage != null) { + add(Attachment(imagenViewModel.imageLabels.getOrElse(1) { "" }, attachedImage)) + } + } + + if (imagenViewModel.includeAttach && attachmentsList.isNotEmpty()) { + AttachmentsList(attachmentsList) + } + Row() { + if (imagenViewModel.includeAttach) { + TextButton( + onClick = { + openDocument.launch(arrayOf("image/*")) + }, + modifier = Modifier + .padding(end = 16.dp, bottom = 16.dp) + ) { Text("Attach") } + } + TextButton( + onClick = { + if (imagenViewModel.allowEmptyPrompt || imagenPrompt.isNotBlank()) { + imagenViewModel.generateImages(imagenPrompt) + } + }, + modifier = Modifier + .padding(end = 16.dp, bottom = 16.dp) + ) { + Text("Generate") + } + } + } if (isLoading) { @@ -100,9 +179,11 @@ fun ImagenScreen( ) } } - LazyVerticalGrid( - columns = GridCells.Fixed(2), - modifier = Modifier.padding(16.dp) + LazyHorizontalGrid( + rows = GridCells.Fixed(2), + modifier = Modifier + .padding(16.dp) + .height(500.dp) ) { items(generatedImages) { image -> Card( @@ -120,3 +201,57 @@ fun ImagenScreen( } } } + +@Composable +fun DropDownMenu(items: List, onClick: (String) -> Unit) { + + val isDropDownExpanded = remember { + mutableStateOf(false) + } + + val itemPosition = remember { + mutableIntStateOf(0) + } + + + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top, + modifier = Modifier.padding(horizontal = 10.dp) + ) { + + Box { + Row( + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.Top, + modifier = Modifier.clickable { + isDropDownExpanded.value = true + } + ) { + Text(text = items[itemPosition.intValue]) + Image( + painter = painterResource(id = R.drawable.round_arrow_drop_down_24), + contentDescription = "Dropdown Icon" + ) + } + DropdownMenu( + expanded = isDropDownExpanded.value, + onDismissRequest = { + isDropDownExpanded.value = false + }) { + items.forEachIndexed { index, item -> + DropdownMenuItem( + text = { + Text(text = item) + }, + onClick = { + isDropDownExpanded.value = false + itemPosition.intValue = index + onClick(item) + }) + } + } + } + + } +} \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt index bd1b58b018..8b6f223a6f 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt @@ -1,6 +1,7 @@ package com.google.firebase.quickstart.ai.feature.media.imagen import android.graphics.Bitmap +import android.graphics.BitmapFactory import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -8,8 +9,6 @@ import androidx.navigation.toRoute import com.google.firebase.Firebase import com.google.firebase.ai.ImagenModel import com.google.firebase.ai.ai -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.ImagenAspectRatio import com.google.firebase.ai.type.ImagenImageFormat import com.google.firebase.ai.type.ImagenPersonFilterLevel import com.google.firebase.ai.type.ImagenSafetyFilterLevel @@ -20,7 +19,27 @@ import com.google.firebase.ai.type.imagenGenerationConfig import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import androidx.core.graphics.scale +import com.google.firebase.ai.type.Dimensions +import com.google.firebase.ai.type.ImagenBackgroundMask +import com.google.firebase.ai.type.ImagenEditMode +import com.google.firebase.ai.type.ImagenEditingConfig +import com.google.firebase.ai.type.ImagenForegroundMask +import com.google.firebase.ai.type.ImagenGenerationResponse +import com.google.firebase.ai.type.ImagenImagePlacement +import com.google.firebase.ai.type.ImagenInlineImage +import com.google.firebase.ai.type.ImagenMaskReference +import com.google.firebase.ai.type.ImagenRawImage +import com.google.firebase.ai.type.ImagenRawMask +import com.google.firebase.ai.type.ImagenStyleReference +import com.google.firebase.ai.type.ImagenSubjectReference +import com.google.firebase.ai.type.ImagenSubjectReferenceType +import com.google.firebase.ai.type.toImagenInlineImage +import com.google.firebase.quickstart.ai.MainActivity +import kotlin.collections.component1 +import kotlin.collections.component2 @OptIn(PublicPreviewAPI::class) class ImagenViewModel( @@ -29,6 +48,7 @@ class ImagenViewModel( private val sampleId = savedStateHandle.toRoute().sampleId private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } val initialPrompt = sample.initialPrompt?.parts?.first()?.asTextOrNull().orEmpty() + val imageLabels = sample.imageLabels private val _errorMessage: MutableStateFlow = MutableStateFlow(null) val errorMessage: StateFlow = _errorMessage @@ -36,6 +56,20 @@ class ImagenViewModel( private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow = _isLoading + val includeAttach = sample.includeAttach + + val selectionOptions = sample.selectionOptions + + private val _selectedOption = MutableStateFlow(null) + val selectedOption: StateFlow = _selectedOption + + val allowEmptyPrompt = sample.allowEmptyPrompt + + val additionalImage = sample.additionalImage + + private val _attachedImage = MutableStateFlow(null) + val attachedImage: StateFlow = _attachedImage + private val _generatedBitmaps = MutableStateFlow(listOf()) val generatedBitmaps: StateFlow> = _generatedBitmaps @@ -45,7 +79,6 @@ class ImagenViewModel( init { val config = imagenGenerationConfig { numberOfImages = 4 - aspectRatio = ImagenAspectRatio.SQUARE_1x1 imageFormat = ImagenImageFormat.png() } val settings = ImagenSafetySettings( @@ -53,7 +86,7 @@ class ImagenViewModel( personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL ) imagenModel = Firebase.ai( - backend = GenerativeBackend.googleAI() + backend = sample.backend ).imagenModel( modelName = sample.modelName ?: "imagen-3.0-generate-002", generationConfig = config, @@ -65,9 +98,13 @@ class ImagenViewModel( viewModelScope.launch { _isLoading.value = true try { - val imageResponse = imagenModel.generateImages( - inputText - ) + val imageResponse = when(sample.editingMode) { + EditingMode.INPAINTING -> inpaint(imagenModel, inputText) + EditingMode.OUTPAINTING -> outpaint(imagenModel, inputText) + EditingMode.SUBJECT_REFERENCE -> drawReferenceSubject(imagenModel, inputText) + EditingMode.STYLE_TRANSFER -> transferStyle(imagenModel, inputText) + else -> generate(imagenModel, inputText) + } _generatedBitmaps.value = imageResponse.images.map { it.asBitmap() } _errorMessage.value = null // clear error message } catch (e: Exception) { @@ -77,4 +114,102 @@ class ImagenViewModel( } } } + + suspend fun attachImage( + fileInBytes: ByteArray, + ) { + val originalBitmap = BitmapFactory.decodeByteArray(fileInBytes, 0, fileInBytes.size) + val resizedBitmap = originalBitmap.scale( + 512, + (originalBitmap.height * (512.0 / originalBitmap.width)).toInt() + ) + _attachedImage.emit(resizedBitmap) + } + + fun selectOption(selection: String) { + viewModelScope.launch { + _selectedOption.emit(selection) + } + } + + suspend fun transferStyle( + model: ImagenModel, + inputText: String, + ): ImagenGenerationResponse { + return model.editImage( + listOf( + ImagenRawImage(MainActivity.catImage.toImagenInlineImage()), + ImagenStyleReference(attachedImage.first()!!.toImagenInlineImage(), 1, "an art style") + ), + "Generate an image in an art style [1] based on the following caption: $inputText", + ) + } + + suspend fun drawReferenceSubject( + model: ImagenModel, + inputText: String, + ): ImagenGenerationResponse { + return model.editImage( + listOf( + ImagenSubjectReference( + referenceId = 1, + image = attachedImage.first()!!.toImagenInlineImage(), + subjectType = ImagenSubjectReferenceType.ANIMAL, + description = "An animal" + ) + ), + "Create an image about An animal [1] to match the description: " + + inputText.replace("", "An animal [1]"), + ) + } + + suspend fun outpaint( + model: ImagenModel, + inputText: String, + ): ImagenGenerationResponse { + val bitmap = attachedImage.first()!! + val position = when (selectedOption.first()) { + "Top" -> ImagenImagePlacement.TOP_CENTER + "Bottom" -> ImagenImagePlacement.BOTTOM_CENTER + "Left" -> ImagenImagePlacement.LEFT_CENTER + "Right" -> ImagenImagePlacement.RIGHT_CENTER + else -> ImagenImagePlacement.CENTER + } + val dimensions = Dimensions(bitmap.width * 2, bitmap.height * 2) + val (sourceImage, mask) = ImagenMaskReference.generateMaskAndPadForOutpainting( + bitmap.toImagenInlineImage(), + dimensions, + position + ) + return model.editImage( + listOf(sourceImage, ImagenRawMask(mask.image!!, 0.05)), + inputText, + ImagenEditingConfig(ImagenEditMode.OUTPAINT) + ) + } + + suspend fun inpaint( + model: ImagenModel, + inputText: String, + ): ImagenGenerationResponse { + val bitmap = attachedImage.first()!! + val mask = when (selectedOption.first()) { + "Foreground" -> ImagenForegroundMask() + else -> ImagenBackgroundMask() + } + return model.editImage( + listOfNotNull(ImagenRawImage(bitmap.toImagenInlineImage()), mask), + inputText, + ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION) + ) + } + + suspend fun generate( + model: ImagenModel, + inputText: String, + ): ImagenGenerationResponse { + return model.generateImages( + inputText + ) + } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt index 768841d892..276024c27f 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt @@ -1,5 +1,6 @@ package com.google.firebase.quickstart.ai.feature.text +import android.annotation.SuppressLint import android.content.Intent import android.graphics.Bitmap import android.net.Uri @@ -174,6 +175,7 @@ fun ChatScreen( } } +@SuppressLint("UnusedBoxWithConstraintsScope") @Composable fun ChatBubbleItem( message: UiChatMessage @@ -294,32 +296,33 @@ fun ChatBubbleItem( // Search Entry Point (WebView) metadata.searchEntryPoint?.let { searchEntryPoint -> val context = LocalContext.current - AndroidView(factory = { - WebView(it).apply { - webViewClient = object : WebViewClient() { - override fun shouldOverrideUrlLoading( - view: WebView?, - request: WebResourceRequest? - ): Boolean { - request?.url?.let { uri -> - val intent = Intent(Intent.ACTION_VIEW, uri) - context.startActivity(intent) + AndroidView( + factory = { + WebView(it).apply { + webViewClient = object : WebViewClient() { + override fun shouldOverrideUrlLoading( + view: WebView?, + request: WebResourceRequest? + ): Boolean { + request?.url?.let { uri -> + val intent = Intent(Intent.ACTION_VIEW, uri) + context.startActivity(intent) + } + // Return true to indicate we handled the URL loading + return true } - // Return true to indicate we handled the URL loading - return true } - } - setBackgroundColor(android.graphics.Color.TRANSPARENT) - loadDataWithBaseURL( - null, - searchEntryPoint.renderedContent, - "text/html", - "UTF-8", - null - ) - } - }, + setBackgroundColor(android.graphics.Color.TRANSPARENT) + loadDataWithBaseURL( + null, + searchEntryPoint.renderedContent, + "text/html", + "UTF-8", + null + ) + } + }, modifier = Modifier .clip(RoundedCornerShape(22.dp)) .fillMaxHeight() @@ -534,10 +537,10 @@ data class Attachment( fun AttachmentsList( attachments: List ) { - LazyColumn( + Column( modifier = Modifier.fillMaxWidth() ) { - items(attachments) { attachment -> + attachments.forEach { attachment -> Row( modifier = Modifier .fillMaxWidth() @@ -550,21 +553,21 @@ fun AttachmentsList( .padding(4.dp) .align(Alignment.CenterVertically) ) - attachment.image?.let { - Image( - bitmap = it.asImageBitmap(), - contentDescription = attachment.fileName, + Column(modifier = Modifier.align (Alignment.CenterVertically)) { + attachment.image?.let { + Image( + bitmap = it.asImageBitmap(), + contentDescription = attachment.fileName, + modifier = Modifier + ) + } + Text( + text = attachment.fileName, + style = MaterialTheme.typography.bodySmall, modifier = Modifier - .align(Alignment.CenterVertically) + .padding(horizontal = 4.dp) ) } - Text( - text = attachment.fileName, - style = MaterialTheme.typography.bodySmall, - modifier = Modifier - .align(Alignment.CenterVertically) - .padding(horizontal = 4.dp) - ) } } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt index 903f6a1114..7d8ef5117b 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt @@ -1,9 +1,15 @@ package com.google.firebase.quickstart.ai.ui.navigation +import android.graphics.Bitmap +import com.google.firebase.ai.ImagenModel import com.google.firebase.ai.type.Content import com.google.firebase.ai.type.GenerationConfig import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.ImagenGenerationResponse +import com.google.firebase.ai.type.ImagenInlineImage +import com.google.firebase.ai.type.PublicPreviewAPI import com.google.firebase.ai.type.Tool +import com.google.firebase.quickstart.ai.feature.media.imagen.EditingMode import java.util.UUID enum class Category( @@ -17,6 +23,7 @@ enum class Category( FUNCTION_CALLING("Function calling"), } +@OptIn(PublicPreviewAPI::class) data class Sample( val id: String = UUID.randomUUID().toString(), // used for navigation val title: String, @@ -30,5 +37,11 @@ data class Sample( val systemInstructions: Content? = null, val generationConfig: GenerationConfig? = null, val chatHistory: List = emptyList(), - val tools: List? = null + val tools: List? = null, + val includeAttach: Boolean = false, + val allowEmptyPrompt: Boolean = false, + val additionalImage: Bitmap? = null, + val imageLabels: List = emptyList(), + val selectionOptions: List = emptyList(), + val editingMode: EditingMode? = null, ) diff --git a/firebase-ai/app/src/main/res/drawable/cat.jpeg b/firebase-ai/app/src/main/res/drawable/cat.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..28bacdcbec8427afcbc5745b994a12a08c121a95 GIT binary patch literal 144342 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 + + + + diff --git a/firebase-ai/gradle/wrapper/gradle-wrapper.properties b/firebase-ai/gradle/wrapper/gradle-wrapper.properties index 06f5495091..8b5c206224 100644 --- a/firebase-ai/gradle/wrapper/gradle-wrapper.properties +++ b/firebase-ai/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 30 20:17:52 BST 2025 +#Tue Aug 19 11:04:48 PDT 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 0b3621103c47102fa0d26c8d528594ea65d468f1 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Fri, 29 Aug 2025 09:30:27 -0700 Subject: [PATCH 12/52] Auto-update dependencies. (#2713) --- admob/app/build.gradle.kts | 2 +- admob/build.gradle.kts | 6 +++--- analytics/app/build.gradle.kts | 4 ++-- analytics/build.gradle.kts | 6 +++--- appdistribution/app/build.gradle.kts | 2 +- appdistribution/build.gradle.kts | 6 +++--- auth/app/build.gradle.kts | 2 +- auth/build.gradle.kts | 6 +++--- build.gradle.kts | 8 ++++---- config/app/build.gradle.kts | 2 +- config/build.gradle.kts | 6 +++--- crash/app/build.gradle.kts | 2 +- crash/build.gradle.kts | 6 +++--- database/app/build.gradle.kts | 2 +- database/build.gradle.kts | 6 +++--- firebase-ai/build.gradle.kts | 10 +++++----- firebase-ai/gradle/libs.versions.toml | 4 ++-- firestore/app/build.gradle.kts | 6 +++--- firestore/build.gradle.kts | 6 +++--- functions/app/build.gradle.kts | 2 +- functions/build.gradle.kts | 6 +++--- gradle/libs.versions.toml | 10 +++++----- inappmessaging/app/build.gradle.kts | 2 +- inappmessaging/build.gradle.kts | 6 +++--- internal/lint/build.gradle.kts | 6 +++--- messaging/app/build.gradle.kts | 2 +- messaging/build.gradle.kts | 6 +++--- perf/app/build.gradle.kts | 4 ++-- perf/build.gradle.kts | 6 +++--- storage/app/build.gradle.kts | 2 +- storage/build.gradle.kts | 6 +++--- 31 files changed, 75 insertions(+), 75 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index b66a76d1e3..f3706faa87 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation("com.google.android.gms:play-services-ads:23.3.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // For an optimal experience using AdMob, add the Firebase SDK // for Google Analytics. This is recommended, but not required. diff --git a/admob/build.gradle.kts b/admob/build.gradle.kts index f30ef4057a..7542dbb21d 100644 --- a/admob/build.gradle.kts +++ b/admob/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index fed3e55270..df9d830232 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -56,10 +56,10 @@ dependencies { implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.preference:preference-ktx:1.2.1") // Needed to override the version used by preference-ktx - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.2") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.3") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // Firebase Analytics implementation("com.google.firebase:firebase-analytics") diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts index 829c7eb05f..10deacff94 100644 --- a/analytics/build.gradle.kts +++ b/analytics/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/appdistribution/app/build.gradle.kts b/appdistribution/app/build.gradle.kts index d1bd79064d..b2994fb21b 100644 --- a/appdistribution/app/build.gradle.kts +++ b/appdistribution/app/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // ADD the SDK to the "prerelease" variant only (example) implementation("com.google.firebase:firebase-appdistribution:16.0.0-beta16") diff --git a/appdistribution/build.gradle.kts b/appdistribution/build.gradle.kts index 62709b0df1..9adec6562f 100644 --- a/appdistribution/build.gradle.kts +++ b/appdistribution/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index b69f19a2e3..2a98c9e4eb 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -62,7 +62,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.3") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // Firebase Authentication implementation("com.google.firebase:firebase-auth") diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 62709b0df1..9adec6562f 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/build.gradle.kts b/build.gradle.kts index 38eb626a4a..c0283aa843 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,15 +1,15 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false id("com.google.firebase.firebase-perf") version "2.0.1" apply false id("androidx.navigation.safeargs") version "2.9.3" apply false id("com.github.ben-manes.versions") version "0.52.0" apply true - id("org.jetbrains.kotlin.plugin.compose") version "2.2.0" apply false + id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false } allprojects { diff --git a/config/app/build.gradle.kts b/config/app/build.gradle.kts index b2d41eb5b6..a1d48c2925 100644 --- a/config/app/build.gradle.kts +++ b/config/app/build.gradle.kts @@ -53,7 +53,7 @@ dependencies { implementation("com.google.android.material:material:1.12.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // Firebase Remote Config implementation("com.google.firebase:firebase-config") diff --git a/config/build.gradle.kts b/config/build.gradle.kts index f30ef4057a..7542dbb21d 100644 --- a/config/build.gradle.kts +++ b/config/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index e971d5cf26..0afd72e3ed 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -60,7 +60,7 @@ dependencies { implementation("androidx.activity:activity-ktx:1.10.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // Firebase Crashlytics implementation("com.google.firebase:firebase-crashlytics") diff --git a/crash/build.gradle.kts b/crash/build.gradle.kts index 2ecac7b1c1..64ba05284b 100644 --- a/crash/build.gradle.kts +++ b/crash/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false } diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index fc533a6a75..40433c872b 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.3") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // Firebase Realtime Database implementation("com.google.firebase:firebase-database") diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 62709b0df1..9adec6562f 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/firebase-ai/build.gradle.kts b/firebase-ai/build.gradle.kts index a8caf90972..1e287ac914 100644 --- a/firebase-ai/build.gradle.kts +++ b/firebase-ai/build.gradle.kts @@ -1,10 +1,10 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false - id("org.jetbrains.kotlin.plugin.compose") version "2.2.0" apply false - id("org.jetbrains.kotlin.plugin.serialization") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false + id("org.jetbrains.kotlin.plugin.serialization") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index 1fa28c4f7d..bf4dd2b58b 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -5,12 +5,12 @@ composeBom = "2024.09.00" composeNavigation = "2.9.3" coreKtx = "1.17.0" espressoCore = "3.7.0" -firebaseBom = "34.1.0" +firebaseBom = "34.2.0" junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.0.21" kotlinxSerializationCore = "1.9.0" -lifecycle = "2.9.2" +lifecycle = "2.9.3" lifecycleRuntimeKtx = "2.8.7" material = "1.12.0" webkit = "1.14.0" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index c618a7af0b..af2c9f6469 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // Firestore implementation("com.google.firebase:firebase-firestore") @@ -88,9 +88,9 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.3") // Android architecture components - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.2") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.3") implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") - annotationProcessor("androidx.lifecycle:lifecycle-compiler:2.9.2") + annotationProcessor("androidx.lifecycle:lifecycle-compiler:2.9.3") // Third-party libraries implementation("me.zhanghai.android.materialratingbar:library:1.4.0") diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index 9554878cf9..ebd6e1fe82 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("androidx.navigation.safeargs") version "2.9.3" apply false } diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index c803c33efd..51ea9d77f4 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { implementation("com.google.android.material:material:1.12.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // Cloud Functions for Firebase implementation("com.google.firebase:firebase-functions") diff --git a/functions/build.gradle.kts b/functions/build.gradle.kts index 62709b0df1..9adec6562f 100644 --- a/functions/build.gradle.kts +++ b/functions/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cc03c74006..ac58f8af11 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,16 @@ [versions] -agp = "8.12.0" +agp = "8.12.2" coilCompose = "2.7.0" -firebaseBom = "34.1.0" -kotlin = "2.2.0" +firebaseBom = "34.2.0" +kotlin = "2.2.10" coreKtx = "1.17.0" junit = "4.13.2" junitVersion = "1.3.0" espressoCore = "3.7.0" kotlinxSerializationCore = "1.9.0" -lifecycle = "2.9.2" +lifecycle = "2.9.3" activityCompose = "1.10.1" -composeBom = "2025.08.00" +composeBom = "2025.08.01" googleServices = "4.4.3" composeNavigation = "2.9.3" material = "1.12.0" diff --git a/inappmessaging/app/build.gradle.kts b/inappmessaging/app/build.gradle.kts index b0127c55ab..2695fd1bff 100644 --- a/inappmessaging/app/build.gradle.kts +++ b/inappmessaging/app/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // FIAM implementation("com.google.firebase:firebase-inappmessaging-display") diff --git a/inappmessaging/build.gradle.kts b/inappmessaging/build.gradle.kts index 62709b0df1..9adec6562f 100644 --- a/inappmessaging/build.gradle.kts +++ b/inappmessaging/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/internal/lint/build.gradle.kts b/internal/lint/build.gradle.kts index 3e46394682..48c3d5dfe2 100755 --- a/internal/lint/build.gradle.kts +++ b/internal/lint/build.gradle.kts @@ -9,8 +9,8 @@ java { } dependencies { - compileOnly("com.android.tools.lint:lint-api:31.12.0") - testImplementation("com.android.tools.lint:lint:31.12.0") - testImplementation("com.android.tools.lint:lint-tests:31.12.0") + compileOnly("com.android.tools.lint:lint-api:31.12.2") + testImplementation("com.android.tools.lint:lint:31.12.2") + testImplementation("com.android.tools.lint:lint-tests:31.12.2") testImplementation("junit:junit:4.13.2") } diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index 621a538468..6c4eff9d58 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { implementation("com.google.android.material:material:1.12.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // Firebase Cloud Messaging implementation("com.google.firebase:firebase-messaging") diff --git a/messaging/build.gradle.kts b/messaging/build.gradle.kts index f30ef4057a..7542dbb21d 100644 --- a/messaging/build.gradle.kts +++ b/messaging/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index 7b7ee1cf31..7fc369236a 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -64,14 +64,14 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // Firebase Performance Monitoring implementation("com.google.firebase:firebase-perf") implementation("com.google.android.material:material:1.12.0") implementation("androidx.constraintlayout:constraintlayout:2.2.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.2") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.3") implementation("com.github.bumptech.glide:glide:4.12.0") diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index e9567d9093..a28193661e 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.firebase-perf") version "2.0.1" apply false } diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index 80867be061..88fd21ae43 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.1.0")) + implementation(platform("com.google.firebase:firebase-bom:34.2.0")) // Cloud Storage for Firebase implementation("com.google.firebase:firebase-storage") diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index 62709b0df1..9adec6562f 100644 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.0" apply false - id("com.android.library") version "8.12.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("com.android.application") version "8.12.2" apply false + id("com.android.library") version "8.12.2" apply false + id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } From c8767a271cb161ee8dbd443656c4985e39f39bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Tue, 2 Sep 2025 21:08:46 +0100 Subject: [PATCH 13/52] update image generation sample to use gemini-2.5-flash-image-preview (#2715) --- .../com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index 49bd5dc181..3ebb9532bd 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -191,11 +191,11 @@ val FIREBASE_AI_SAMPLES = listOf( editingMode = EditingMode.STYLE_TRANSFER ), Sample( - title = "Gemini 2.0 Flash - image generation", - description = "Generate and/or edit images using Gemini 2.0 Flash", + title = "Gemini 2.5 Flash - image generation (preview)", + description = "Generate and/or edit images using Gemini 2.5 Flash Image Preview", navRoute = "chat", categories = listOf(Category.IMAGE), - modelName = "gemini-2.0-flash-preview-image-generation", + modelName = "gemini-2.5-flash-image-preview", initialPrompt = content { text( "Hi, can you create a 3d rendered image of a pig " + From 283f42943983ceb972f5a5aa185c08ca1ac45bfc Mon Sep 17 00:00:00 2001 From: Vinay Guthal Date: Wed, 3 Sep 2025 13:14:16 -0400 Subject: [PATCH 14/52] Add sample app for bidirectional streaming (#2716) Adds a sample app for bidirectional streaming. The sample app also has inbuilt function calling allowing users to use function calling along with bidi. --- firebase-ai/app/src/main/AndroidManifest.xml | 4 + .../quickstart/ai/FirebaseAISamples.kt | 28 ++++ .../firebase/quickstart/ai/MainActivity.kt | 8 ++ .../ai/feature/live/BidiViewModel.kt | 103 +++++++++++++ .../ai/feature/live/StreamRealtimeScreen.kt | 136 +++++++++++++++++- .../quickstart/ai/ui/navigation/Sample.kt | 1 + 6 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt diff --git a/firebase-ai/app/src/main/AndroidManifest.xml b/firebase-ai/app/src/main/AndroidManifest.xml index 0ca9215899..699c61714c 100644 --- a/firebase-ai/app/src/main/AndroidManifest.xml +++ b/firebase-ai/app/src/main/AndroidManifest.xml @@ -2,6 +2,10 @@ + + + + ().sampleId + private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } + + // Firebase AI Logic + private var liveSession: LiveSession + + init { + val liveGenerationConfig = liveGenerationConfig { + speechConfig = SpeechConfig(voice = Voice("CHARON")) + // Change this to ContentModality.TEXT if you want text output. + responseModality = ResponseModality.AUDIO + } + @OptIn(PublicPreviewAPI::class) + val liveModel = FirebaseAI.getInstance(Firebase.app, sample.backend).liveModel( + "gemini-live-2.5-flash", + generationConfig = liveGenerationConfig, + tools = sample.tools + ) + runBlocking { + liveSession = liveModel.connect() + } + } + + fun handler(fetchWeatherCall: FunctionCallPart) : FunctionResponsePart { + val response:JsonObject + fetchWeatherCall.let { + val city = it.args["city"]?.jsonPrimitive?.content + val state = it.args["state"]?.jsonPrimitive?.content + val date = it.args["date"]?.jsonPrimitive?.content + runBlocking { + response = if(!city.isNullOrEmpty() and !state.isNullOrEmpty() and date.isNullOrEmpty()) { + fetchWeather(city!!, state!!, date!!) + } else { + JsonObject(emptyMap()) + } + } + } + return FunctionResponsePart("fetchWeather", response, fetchWeatherCall.id) + } + @RequiresPermission(Manifest.permission.RECORD_AUDIO) + suspend fun startConversation() { + liveSession.startAudioConversation(::handler) + } + + fun endConversation() { + liveSession.stopAudioConversation() + } + + +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt index 8cbdd721f6..f088cda23b 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt @@ -1,20 +1,146 @@ package com.google.firebase.quickstart.ai.feature.live -import androidx.compose.foundation.layout.Box +import android.Manifest +import androidx.annotation.RequiresPermission +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CallEnd +import androidx.compose.material.icons.filled.Mic +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.runtime.remember +import androidx.compose.runtime.mutableStateOf + +import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.firebase.quickstart.ai.feature.media.imagen.BidiViewModel +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @Serializable class StreamRealtimeRoute(val sampleId: String) +@RequiresPermission(Manifest.permission.RECORD_AUDIO) @Composable -fun StreamRealtimeScreen() { - Box( - modifier = Modifier.fillMaxSize() +fun StreamRealtimeScreen(bidiView: BidiViewModel = viewModel()) { + val isConversationActive = remember { mutableStateOf(false) } + val backgroundColor = + MaterialTheme.colorScheme.background + Surface( + modifier = Modifier.fillMaxSize(), + color = backgroundColor ) { - Text("Coming soon") + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + // The content will animate its size when it changes + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.animateContentSize() + ) { + if (isConversationActive.value) { + // Active state UI + Text( + text = "Conversation Active", + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Tap the end button to stop", + fontSize = 18.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } else { + // Idle state UI + Text( + text = "Start Conversation", + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Tap the microphone to begin", + fontSize = 18.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + + Spacer(modifier = Modifier.height(80.dp)) + + // The main button with pulsing animation + if (isConversationActive.value) { + // Button to end the conversation + IconButton( + onClick = { + bidiView.endConversation() + isConversationActive.value = false }, + modifier = Modifier + .size(90.dp) + .clip(CircleShape), + colors = IconButtonDefaults.iconButtonColors( + containerColor = Color(0xFFE63946), // A nice red color + contentColor = Color.White + ) + ) { + Icon( + imageVector = Icons.Default.CallEnd, + contentDescription = "End Conversation", + modifier = Modifier.size(48.dp) + ) + } + } else { + // Button to start the conversation + IconButton( + onClick = { + CoroutineScope(Dispatchers.IO).launch { + bidiView.startConversation() + } + isConversationActive.value = true }, + modifier = Modifier + .size(90.dp) + .clip(CircleShape), + colors = IconButtonDefaults.iconButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = Color.White + ) + ) { + Icon( + imageVector = Icons.Default.Mic, + contentDescription = "Start Conversation", + modifier = Modifier.size(48.dp) + ) + } + } + } } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt index 7d8ef5117b..ad0cccbecf 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt @@ -21,6 +21,7 @@ enum class Category( AUDIO("Audio"), DOCUMENT("Document"), FUNCTION_CALLING("Function calling"), + LIVE_API("LiveAPI Streaming") } @OptIn(PublicPreviewAPI::class) From dbecdbb6ab4caabdbb074b442e3659eca34246ea Mon Sep 17 00:00:00 2001 From: DPEBot Date: Wed, 3 Sep 2025 15:26:21 -0700 Subject: [PATCH 15/52] Auto-update dependencies. (#2717) --- admob/build.gradle.kts | 4 ++-- analytics/build.gradle.kts | 4 ++-- appdistribution/build.gradle.kts | 4 ++-- auth/build.gradle.kts | 4 ++-- build.gradle.kts | 4 ++-- config/build.gradle.kts | 4 ++-- crash/build.gradle.kts | 4 ++-- database/build.gradle.kts | 4 ++-- firebase-ai/build.gradle.kts | 4 ++-- firestore/build.gradle.kts | 4 ++-- functions/build.gradle.kts | 4 ++-- gradle/libs.versions.toml | 2 +- inappmessaging/build.gradle.kts | 4 ++-- internal/lint/build.gradle.kts | 6 +++--- messaging/build.gradle.kts | 4 ++-- perf/build.gradle.kts | 4 ++-- storage/build.gradle.kts | 4 ++-- 17 files changed, 34 insertions(+), 34 deletions(-) diff --git a/admob/build.gradle.kts b/admob/build.gradle.kts index 7542dbb21d..410f4cc55a 100644 --- a/admob/build.gradle.kts +++ b/admob/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts index 10deacff94..d9449556b4 100644 --- a/analytics/build.gradle.kts +++ b/analytics/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/appdistribution/build.gradle.kts b/appdistribution/build.gradle.kts index 9adec6562f..70b5f91a18 100644 --- a/appdistribution/build.gradle.kts +++ b/appdistribution/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 9adec6562f..70b5f91a18 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/build.gradle.kts b/build.gradle.kts index c0283aa843..b6818616b8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false diff --git a/config/build.gradle.kts b/config/build.gradle.kts index 7542dbb21d..410f4cc55a 100644 --- a/config/build.gradle.kts +++ b/config/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/crash/build.gradle.kts b/crash/build.gradle.kts index 64ba05284b..54f3c4dfc6 100644 --- a/crash/build.gradle.kts +++ b/crash/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 9adec6562f..70b5f91a18 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/firebase-ai/build.gradle.kts b/firebase-ai/build.gradle.kts index 1e287ac914..d892475922 100644 --- a/firebase-ai/build.gradle.kts +++ b/firebase-ai/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false id("org.jetbrains.kotlin.plugin.serialization") version "2.2.10" apply false diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index ebd6e1fe82..5f8d1c18d3 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("androidx.navigation.safeargs") version "2.9.3" apply false diff --git a/functions/build.gradle.kts b/functions/build.gradle.kts index 9adec6562f..70b5f91a18 100644 --- a/functions/build.gradle.kts +++ b/functions/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ac58f8af11..879d1f24ca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.12.2" +agp = "8.13.0" coilCompose = "2.7.0" firebaseBom = "34.2.0" kotlin = "2.2.10" diff --git a/inappmessaging/build.gradle.kts b/inappmessaging/build.gradle.kts index 9adec6562f..70b5f91a18 100644 --- a/inappmessaging/build.gradle.kts +++ b/inappmessaging/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/internal/lint/build.gradle.kts b/internal/lint/build.gradle.kts index 48c3d5dfe2..1b154e9659 100755 --- a/internal/lint/build.gradle.kts +++ b/internal/lint/build.gradle.kts @@ -9,8 +9,8 @@ java { } dependencies { - compileOnly("com.android.tools.lint:lint-api:31.12.2") - testImplementation("com.android.tools.lint:lint:31.12.2") - testImplementation("com.android.tools.lint:lint-tests:31.12.2") + compileOnly("com.android.tools.lint:lint-api:31.13.0") + testImplementation("com.android.tools.lint:lint:31.13.0") + testImplementation("com.android.tools.lint:lint-tests:31.13.0") testImplementation("junit:junit:4.13.2") } diff --git a/messaging/build.gradle.kts b/messaging/build.gradle.kts index 7542dbb21d..410f4cc55a 100644 --- a/messaging/build.gradle.kts +++ b/messaging/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index a28193661e..1a812761f8 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.firebase-perf") version "2.0.1" apply false diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index 9adec6562f..70b5f91a18 100644 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.12.2" apply false - id("com.android.library") version "8.12.2" apply false + id("com.android.application") version "8.13.0" apply false + id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.10" apply false id("com.google.gms.google-services") version "4.4.3" apply false } From beec27ba6f50886c2f420049e894164267d8b4c7 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Thu, 18 Sep 2025 09:37:57 -0700 Subject: [PATCH 16/52] Auto-update dependencies. (#2718) --- admob/app/build.gradle.kts | 6 +++--- admob/build.gradle.kts | 2 +- analytics/app/build.gradle.kts | 4 ++-- analytics/build.gradle.kts | 2 +- appdistribution/app/build.gradle.kts | 2 +- appdistribution/build.gradle.kts | 2 +- auth/app/build.gradle.kts | 8 ++++---- auth/build.gradle.kts | 2 +- build.gradle.kts | 6 +++--- config/app/build.gradle.kts | 2 +- config/build.gradle.kts | 2 +- crash/app/build.gradle.kts | 4 ++-- crash/build.gradle.kts | 2 +- database/app/build.gradle.kts | 6 +++--- database/build.gradle.kts | 2 +- firebase-ai/build.gradle.kts | 6 +++--- firebase-ai/gradle/libs.versions.toml | 8 ++++---- firestore/app/build.gradle.kts | 12 ++++++------ firestore/build.gradle.kts | 4 ++-- functions/app/build.gradle.kts | 4 ++-- functions/build.gradle.kts | 2 +- gradle/libs.versions.toml | 12 ++++++------ inappmessaging/app/build.gradle.kts | 2 +- inappmessaging/build.gradle.kts | 2 +- internal/chooserx/build.gradle.kts | 2 +- messaging/app/build.gradle.kts | 6 +++--- messaging/build.gradle.kts | 2 +- perf/app/build.gradle.kts | 4 ++-- perf/build.gradle.kts | 2 +- storage/app/build.gradle.kts | 4 ++-- storage/build.gradle.kts | 2 +- 31 files changed, 63 insertions(+), 63 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index f3706faa87..f8a5ba5806 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -53,10 +53,10 @@ dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) implementation("androidx.appcompat:appcompat:1.7.1") - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") implementation("androidx.browser:browser:1.5.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.3") - implementation("androidx.navigation:navigation-ui-ktx:2.9.3") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.4") + implementation("androidx.navigation:navigation-ui-ktx:2.9.4") implementation("com.google.android.gms:play-services-ads:23.3.0") diff --git a/admob/build.gradle.kts b/admob/build.gradle.kts index 410f4cc55a..a1f2a0761e 100644 --- a/admob/build.gradle.kts +++ b/admob/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index df9d830232..4c1d42633c 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -52,11 +52,11 @@ dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.preference:preference-ktx:1.2.1") // Needed to override the version used by preference-ktx - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.3") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.2.0")) diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts index d9449556b4..676b8c4644 100644 --- a/analytics/build.gradle.kts +++ b/analytics/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/appdistribution/app/build.gradle.kts b/appdistribution/app/build.gradle.kts index b2994fb21b..cd490858b2 100644 --- a/appdistribution/app/build.gradle.kts +++ b/appdistribution/app/build.gradle.kts @@ -52,7 +52,7 @@ dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") implementation("androidx.constraintlayout:constraintlayout:2.2.1") implementation("androidx.multidex:multidex:2.0.1") diff --git a/appdistribution/build.gradle.kts b/appdistribution/build.gradle.kts index 70b5f91a18..94c5aa8042 100644 --- a/appdistribution/build.gradle.kts +++ b/appdistribution/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index 2a98c9e4eb..909716512f 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -54,12 +54,12 @@ dependencies { implementation(project(":internal:lintchecks")) implementation("androidx.multidex:multidex:2.0.1") - implementation("androidx.activity:activity-ktx:1.10.1") + implementation("androidx.activity:activity-ktx:1.11.0") implementation("androidx.constraintlayout:constraintlayout:2.2.1") implementation("androidx.vectordrawable:vectordrawable-animated:1.2.0") - implementation("com.google.android.material:material:1.12.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.3") - implementation("androidx.navigation:navigation-ui-ktx:2.9.3") + implementation("com.google.android.material:material:1.13.0") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.4") + implementation("androidx.navigation:navigation-ui-ktx:2.9.4") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.2.0")) diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 70b5f91a18..94c5aa8042 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/build.gradle.kts b/build.gradle.kts index b6818616b8..7e43e31c29 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,13 +3,13 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false id("com.google.firebase.firebase-perf") version "2.0.1" apply false - id("androidx.navigation.safeargs") version "2.9.3" apply false + id("androidx.navigation.safeargs") version "2.9.4" apply false id("com.github.ben-manes.versions") version "0.52.0" apply true - id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false + id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" apply false } allprojects { diff --git a/config/app/build.gradle.kts b/config/app/build.gradle.kts index a1d48c2925..dc9fd16aec 100644 --- a/config/app/build.gradle.kts +++ b/config/app/build.gradle.kts @@ -50,7 +50,7 @@ dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.2.0")) diff --git a/config/build.gradle.kts b/config/build.gradle.kts index 410f4cc55a..a1f2a0761e 100644 --- a/config/build.gradle.kts +++ b/config/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index 0afd72e3ed..dea28bb4e0 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -56,8 +56,8 @@ android { dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) - implementation("com.google.android.material:material:1.12.0") - implementation("androidx.activity:activity-ktx:1.10.1") + implementation("com.google.android.material:material:1.13.0") + implementation("androidx.activity:activity-ktx:1.11.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.2.0")) diff --git a/crash/build.gradle.kts b/crash/build.gradle.kts index 54f3c4dfc6..ae169108dc 100644 --- a/crash/build.gradle.kts +++ b/crash/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false } diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index 40433c872b..6b06e2de66 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -53,9 +53,9 @@ dependencies { implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.recyclerview:recyclerview:1.4.0") - implementation("com.google.android.material:material:1.12.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.3") - implementation("androidx.navigation:navigation-ui-ktx:2.9.3") + implementation("com.google.android.material:material:1.13.0") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.4") + implementation("androidx.navigation:navigation-ui-ktx:2.9.4") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.2.0")) diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 70b5f91a18..94c5aa8042 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/firebase-ai/build.gradle.kts b/firebase-ai/build.gradle.kts index d892475922..80c84e2ea9 100644 --- a/firebase-ai/build.gradle.kts +++ b/firebase-ai/build.gradle.kts @@ -3,8 +3,8 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false - id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false - id("org.jetbrains.kotlin.plugin.serialization") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" apply false + id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index bf4dd2b58b..38f4a1cda9 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] -activityCompose = "1.10.1" +activityCompose = "1.11.0" agp = "8.9.2" composeBom = "2024.09.00" -composeNavigation = "2.9.3" +composeNavigation = "2.9.4" coreKtx = "1.17.0" espressoCore = "3.7.0" firebaseBom = "34.2.0" @@ -10,9 +10,9 @@ junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.0.21" kotlinxSerializationCore = "1.9.0" -lifecycle = "2.9.3" +lifecycle = "2.9.4" lifecycleRuntimeKtx = "2.8.7" -material = "1.12.0" +material = "1.13.0" webkit = "1.14.0" [libraries] diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index af2c9f6469..93ca7b03ac 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -74,23 +74,23 @@ dependencies { implementation("com.firebaseui:firebase-ui-auth:9.0.0") // Support Libs - implementation("androidx.activity:activity-ktx:1.10.1") + implementation("androidx.activity:activity-ktx:1.11.0") implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.core:core-ktx:1.17.0") implementation("androidx.vectordrawable:vectordrawable-animated:1.2.0") implementation("androidx.cardview:cardview:1.0.0") implementation("androidx.browser:browser:1.5.0") - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") implementation("androidx.media:media:1.7.1") implementation("androidx.recyclerview:recyclerview:1.4.0") implementation("androidx.multidex:multidex:2.0.1") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.3") - implementation("androidx.navigation:navigation-ui-ktx:2.9.3") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.4") + implementation("androidx.navigation:navigation-ui-ktx:2.9.4") // Android architecture components - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.3") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.4") implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") - annotationProcessor("androidx.lifecycle:lifecycle-compiler:2.9.3") + annotationProcessor("androidx.lifecycle:lifecycle-compiler:2.9.4") // Third-party libraries implementation("me.zhanghai.android.materialratingbar:library:1.4.0") diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index 5f8d1c18d3..f68043af0b 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -3,9 +3,9 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false - id("androidx.navigation.safeargs") version "2.9.3" apply false + id("androidx.navigation.safeargs") version "2.9.4" apply false } allprojects { diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index 51ea9d77f4..55f7a92aa5 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -49,10 +49,10 @@ dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) - implementation("androidx.activity:activity-ktx:1.10.1") + implementation("androidx.activity:activity-ktx:1.11.0") implementation("androidx.fragment:fragment-ktx:1.8.9") implementation("androidx.appcompat:appcompat:1.7.1") - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.2.0")) diff --git a/functions/build.gradle.kts b/functions/build.gradle.kts index 70b5f91a18..94c5aa8042 100644 --- a/functions/build.gradle.kts +++ b/functions/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 879d1f24ca..82a82f1b71 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,18 +2,18 @@ agp = "8.13.0" coilCompose = "2.7.0" firebaseBom = "34.2.0" -kotlin = "2.2.10" +kotlin = "2.2.20" coreKtx = "1.17.0" junit = "4.13.2" junitVersion = "1.3.0" espressoCore = "3.7.0" kotlinxSerializationCore = "1.9.0" -lifecycle = "2.9.3" -activityCompose = "1.10.1" -composeBom = "2025.08.01" +lifecycle = "2.9.4" +activityCompose = "1.11.0" +composeBom = "2025.09.00" googleServices = "4.4.3" -composeNavigation = "2.9.3" -material = "1.12.0" +composeNavigation = "2.9.4" +material = "1.13.0" webkit = "1.14.0" [libraries] diff --git a/inappmessaging/app/build.gradle.kts b/inappmessaging/app/build.gradle.kts index 2695fd1bff..b806493c6b 100644 --- a/inappmessaging/app/build.gradle.kts +++ b/inappmessaging/app/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") implementation("androidx.constraintlayout:constraintlayout:2.2.1") implementation("androidx.multidex:multidex:2.0.1") diff --git a/inappmessaging/build.gradle.kts b/inappmessaging/build.gradle.kts index 70b5f91a18..94c5aa8042 100644 --- a/inappmessaging/build.gradle.kts +++ b/inappmessaging/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/internal/chooserx/build.gradle.kts b/internal/chooserx/build.gradle.kts index 3ae5fd7308..6e21a9ad95 100755 --- a/internal/chooserx/build.gradle.kts +++ b/internal/chooserx/build.gradle.kts @@ -26,7 +26,7 @@ android { } dependencies { - api("com.google.android.material:material:1.12.0") + api("com.google.android.material:material:1.13.0") api("androidx.recyclerview:recyclerview:1.4.0") api("androidx.constraintlayout:constraintlayout:2.2.1") } diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index 6c4eff9d58..2e9bdc0468 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -61,10 +61,10 @@ dependencies { implementation("androidx.core:core-ktx:1.17.0") // Required when asking for permission to post notifications (starting in Android 13) - implementation("androidx.activity:activity-ktx:1.10.1") + implementation("androidx.activity:activity-ktx:1.11.0") implementation("androidx.fragment:fragment-ktx:1.8.9") - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.2.0")) @@ -78,7 +78,7 @@ dependencies { implementation("com.google.firebase:firebase-installations:19.0.0") - implementation("androidx.work:work-runtime:2.10.3") + implementation("androidx.work:work-runtime:2.10.4") // Testing dependencies androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") diff --git a/messaging/build.gradle.kts b/messaging/build.gradle.kts index 410f4cc55a..a1f2a0761e 100644 --- a/messaging/build.gradle.kts +++ b/messaging/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index 7fc369236a..8d526b6e2c 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -69,9 +69,9 @@ dependencies { // Firebase Performance Monitoring implementation("com.google.firebase:firebase-perf") - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") implementation("androidx.constraintlayout:constraintlayout:2.2.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.3") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.4") implementation("com.github.bumptech.glide:glide:4.12.0") diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index 1a812761f8..bbefa26b4c 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.firebase-perf") version "2.0.1" apply false } diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index 88fd21ae43..5684fcf89f 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -59,9 +59,9 @@ dependencies { // Firebase Authentication implementation("com.google.firebase:firebase-auth") - implementation("androidx.activity:activity-ktx:1.10.1") + implementation("androidx.activity:activity-ktx:1.11.0") implementation("androidx.appcompat:appcompat:1.7.1") - implementation("com.google.android.material:material:1.12.0") + implementation("com.google.android.material:material:1.13.0") androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") androidTestImplementation("androidx.test.espresso:espresso-intents:3.7.0") androidTestImplementation("androidx.test:rules:1.7.0") diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index 70b5f91a18..94c5aa8042 100644 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.10" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false } From df73ed86ff438dcf2e4a0138ca1a83481aac74d6 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Fri, 19 Sep 2025 06:09:40 -0700 Subject: [PATCH 17/52] Auto-update dependencies. (#2720) --- admob/app/build.gradle.kts | 2 +- analytics/app/build.gradle.kts | 2 +- appdistribution/app/build.gradle.kts | 4 ++-- auth/app/build.gradle.kts | 2 +- config/app/build.gradle.kts | 2 +- crash/app/build.gradle.kts | 2 +- database/app/build.gradle.kts | 2 +- firebase-ai/gradle/libs.versions.toml | 2 +- firestore/app/build.gradle.kts | 2 +- functions/app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- inappmessaging/app/build.gradle.kts | 4 ++-- messaging/app/build.gradle.kts | 4 ++-- perf/app/build.gradle.kts | 2 +- storage/app/build.gradle.kts | 2 +- 15 files changed, 18 insertions(+), 18 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index f8a5ba5806..356f465a27 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation("com.google.android.gms:play-services-ads:23.3.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // For an optimal experience using AdMob, add the Firebase SDK // for Google Analytics. This is recommended, but not required. diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index 4c1d42633c..5c5afd2390 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // Firebase Analytics implementation("com.google.firebase:firebase-analytics") diff --git a/appdistribution/app/build.gradle.kts b/appdistribution/app/build.gradle.kts index cd490858b2..4b1893f902 100644 --- a/appdistribution/app/build.gradle.kts +++ b/appdistribution/app/build.gradle.kts @@ -57,10 +57,10 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // ADD the SDK to the "prerelease" variant only (example) - implementation("com.google.firebase:firebase-appdistribution:16.0.0-beta16") + implementation("com.google.firebase:firebase-appdistribution:16.0.0-beta17") // For an optimal experience using App Distribution, add the Firebase SDK // for Google Analytics. This is recommended, but not required. diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index 909716512f..740cf18a32 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -62,7 +62,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.4") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // Firebase Authentication implementation("com.google.firebase:firebase-auth") diff --git a/config/app/build.gradle.kts b/config/app/build.gradle.kts index dc9fd16aec..53cdd7261b 100644 --- a/config/app/build.gradle.kts +++ b/config/app/build.gradle.kts @@ -53,7 +53,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // Firebase Remote Config implementation("com.google.firebase:firebase-config") diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index dea28bb4e0..db80b1fb10 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -60,7 +60,7 @@ dependencies { implementation("androidx.activity:activity-ktx:1.11.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // Firebase Crashlytics implementation("com.google.firebase:firebase-crashlytics") diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index 6b06e2de66..60eb34e091 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.4") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // Firebase Realtime Database implementation("com.google.firebase:firebase-database") diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index 38f4a1cda9..94bcae04bf 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -5,7 +5,7 @@ composeBom = "2024.09.00" composeNavigation = "2.9.4" coreKtx = "1.17.0" espressoCore = "3.7.0" -firebaseBom = "34.2.0" +firebaseBom = "34.3.0" junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.0.21" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index 93ca7b03ac..a51b79d310 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // Firestore implementation("com.google.firebase:firebase-firestore") diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index 55f7a92aa5..5062d1b3eb 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // Cloud Functions for Firebase implementation("com.google.firebase:firebase-functions") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 82a82f1b71..18e532a006 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "8.13.0" coilCompose = "2.7.0" -firebaseBom = "34.2.0" +firebaseBom = "34.3.0" kotlin = "2.2.20" coreKtx = "1.17.0" junit = "4.13.2" diff --git a/inappmessaging/app/build.gradle.kts b/inappmessaging/app/build.gradle.kts index b806493c6b..9a41b0f18d 100644 --- a/inappmessaging/app/build.gradle.kts +++ b/inappmessaging/app/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // FIAM implementation("com.google.firebase:firebase-inappmessaging-display") @@ -65,7 +65,7 @@ dependencies { // Analytics implementation("com.google.firebase:firebase-analytics") - implementation("com.google.firebase:firebase-installations:19.0.0") + implementation("com.google.firebase:firebase-installations:19.0.1") androidTestImplementation("androidx.test:runner:1.7.0") androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index 2e9bdc0468..9d2852dfe9 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // Firebase Cloud Messaging implementation("com.google.firebase:firebase-messaging") @@ -76,7 +76,7 @@ dependencies { // for Google Analytics. This is recommended, but not required. implementation("com.google.firebase:firebase-analytics") - implementation("com.google.firebase:firebase-installations:19.0.0") + implementation("com.google.firebase:firebase-installations:19.0.1") implementation("androidx.work:work-runtime:2.10.4") diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index 8d526b6e2c..ec064f0f6b 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -64,7 +64,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // Firebase Performance Monitoring implementation("com.google.firebase:firebase-perf") diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index 5684fcf89f..e38f410f85 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.2.0")) + implementation(platform("com.google.firebase:firebase-bom:34.3.0")) // Cloud Storage for Firebase implementation("com.google.firebase:firebase-storage") From 243d9fb16482bb0867cb0d0f95cea9e72907179c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Fri, 3 Oct 2025 16:15:24 +0100 Subject: [PATCH 18/52] remove preview suffix for nanobanana (#2722) --- .../com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index 065d97a82f..ad3a5cd435 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -191,11 +191,11 @@ val FIREBASE_AI_SAMPLES = listOf( editingMode = EditingMode.STYLE_TRANSFER ), Sample( - title = "Gemini 2.5 Flash - image generation (preview)", - description = "Generate and/or edit images using Gemini 2.5 Flash Image Preview", + title = "Gemini 2.5 Flash Image (aka nanobanana)", + description = "Generate and/or edit images using Gemini 2.5 Flash Image aka nanobanana", navRoute = "chat", categories = listOf(Category.IMAGE), - modelName = "gemini-2.5-flash-image-preview", + modelName = "gemini-2.5-flash-image", initialPrompt = content { text( "Hi, can you create a 3d rendered image of a pig " + From 71defdc2c5366d64e8526b6d326bf96143f559b5 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Fri, 3 Oct 2025 08:17:38 -0700 Subject: [PATCH 19/52] Auto-update dependencies. (#2721) --- admob/app/build.gradle.kts | 4 ++-- auth/app/build.gradle.kts | 4 ++-- build.gradle.kts | 4 ++-- database/app/build.gradle.kts | 4 ++-- firebase-ai/gradle/libs.versions.toml | 2 +- firestore/app/build.gradle.kts | 4 ++-- firestore/build.gradle.kts | 2 +- gradle/libs.versions.toml | 4 ++-- messaging/app/build.gradle.kts | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index 356f465a27..bdba18d756 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -55,8 +55,8 @@ dependencies { implementation("androidx.appcompat:appcompat:1.7.1") implementation("com.google.android.material:material:1.13.0") implementation("androidx.browser:browser:1.5.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.4") - implementation("androidx.navigation:navigation-ui-ktx:2.9.4") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.5") + implementation("androidx.navigation:navigation-ui-ktx:2.9.5") implementation("com.google.android.gms:play-services-ads:23.3.0") diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index 740cf18a32..7b2358ac68 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -58,8 +58,8 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout:2.2.1") implementation("androidx.vectordrawable:vectordrawable-animated:1.2.0") implementation("com.google.android.material:material:1.13.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.4") - implementation("androidx.navigation:navigation-ui-ktx:2.9.4") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.5") + implementation("androidx.navigation:navigation-ui-ktx:2.9.5") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.3.0")) diff --git a/build.gradle.kts b/build.gradle.kts index 7e43e31c29..a547be2dd6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,8 +7,8 @@ plugins { id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false id("com.google.firebase.firebase-perf") version "2.0.1" apply false - id("androidx.navigation.safeargs") version "2.9.4" apply false - id("com.github.ben-manes.versions") version "0.52.0" apply true + id("androidx.navigation.safeargs") version "2.9.5" apply false + id("com.github.ben-manes.versions") version "0.53.0" apply true id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" apply false } diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index 60eb34e091..5e4c98db34 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -54,8 +54,8 @@ dependencies { implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.recyclerview:recyclerview:1.4.0") implementation("com.google.android.material:material:1.13.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.4") - implementation("androidx.navigation:navigation-ui-ktx:2.9.4") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.5") + implementation("androidx.navigation:navigation-ui-ktx:2.9.5") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.3.0")) diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index 94bcae04bf..37bdb54c7b 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -2,7 +2,7 @@ activityCompose = "1.11.0" agp = "8.9.2" composeBom = "2024.09.00" -composeNavigation = "2.9.4" +composeNavigation = "2.9.5" coreKtx = "1.17.0" espressoCore = "3.7.0" firebaseBom = "34.3.0" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index a51b79d310..8834c2ffd1 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -84,8 +84,8 @@ dependencies { implementation("androidx.media:media:1.7.1") implementation("androidx.recyclerview:recyclerview:1.4.0") implementation("androidx.multidex:multidex:2.0.1") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.4") - implementation("androidx.navigation:navigation-ui-ktx:2.9.4") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.5") + implementation("androidx.navigation:navigation-ui-ktx:2.9.5") // Android architecture components implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.4") diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index f68043af0b..f68b944642 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -5,7 +5,7 @@ plugins { id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false - id("androidx.navigation.safeargs") version "2.9.4" apply false + id("androidx.navigation.safeargs") version "2.9.5" apply false } allprojects { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 18e532a006..b659ae1a88 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,9 +10,9 @@ espressoCore = "3.7.0" kotlinxSerializationCore = "1.9.0" lifecycle = "2.9.4" activityCompose = "1.11.0" -composeBom = "2025.09.00" +composeBom = "2025.09.01" googleServices = "4.4.3" -composeNavigation = "2.9.4" +composeNavigation = "2.9.5" material = "1.13.0" webkit = "1.14.0" diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index 9d2852dfe9..9540876715 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -78,7 +78,7 @@ dependencies { implementation("com.google.firebase:firebase-installations:19.0.1") - implementation("androidx.work:work-runtime:2.10.4") + implementation("androidx.work:work-runtime:2.10.5") // Testing dependencies androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") From 8425ab58ff40dd464e37e857e6d448e0de92b074 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Fri, 10 Oct 2025 10:31:45 -0700 Subject: [PATCH 20/52] Auto-update dependencies. (#2723) --- admob/app/build.gradle.kts | 2 +- admob/build.gradle.kts | 2 +- analytics/app/build.gradle.kts | 2 +- analytics/build.gradle.kts | 2 +- appdistribution/app/build.gradle.kts | 2 +- appdistribution/build.gradle.kts | 2 +- auth/app/build.gradle.kts | 2 +- auth/build.gradle.kts | 2 +- build.gradle.kts | 2 +- config/app/build.gradle.kts | 2 +- config/build.gradle.kts | 2 +- crash/app/build.gradle.kts | 2 +- crash/build.gradle.kts | 2 +- database/app/build.gradle.kts | 2 +- database/build.gradle.kts | 2 +- firebase-ai/build.gradle.kts | 2 +- firebase-ai/gradle/libs.versions.toml | 2 +- firestore/app/build.gradle.kts | 2 +- firestore/build.gradle.kts | 2 +- functions/app/build.gradle.kts | 2 +- functions/build.gradle.kts | 2 +- gradle/libs.versions.toml | 6 +++--- inappmessaging/app/build.gradle.kts | 2 +- inappmessaging/build.gradle.kts | 2 +- messaging/app/build.gradle.kts | 2 +- messaging/build.gradle.kts | 2 +- perf/app/build.gradle.kts | 2 +- perf/build.gradle.kts | 2 +- storage/app/build.gradle.kts | 2 +- storage/build.gradle.kts | 2 +- 30 files changed, 32 insertions(+), 32 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index bdba18d756..6bf9146de9 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation("com.google.android.gms:play-services-ads:23.3.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // For an optimal experience using AdMob, add the Firebase SDK // for Google Analytics. This is recommended, but not required. diff --git a/admob/build.gradle.kts b/admob/build.gradle.kts index a1f2a0761e..c07891342a 100644 --- a/admob/build.gradle.kts +++ b/admob/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } allprojects { diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index 5c5afd2390..160159b63a 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // Firebase Analytics implementation("com.google.firebase:firebase-analytics") diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts index 676b8c4644..5e59ee2b4f 100644 --- a/analytics/build.gradle.kts +++ b/analytics/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } allprojects { diff --git a/appdistribution/app/build.gradle.kts b/appdistribution/app/build.gradle.kts index 4b1893f902..943a1d5083 100644 --- a/appdistribution/app/build.gradle.kts +++ b/appdistribution/app/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // ADD the SDK to the "prerelease" variant only (example) implementation("com.google.firebase:firebase-appdistribution:16.0.0-beta17") diff --git a/appdistribution/build.gradle.kts b/appdistribution/build.gradle.kts index 94c5aa8042..8eb799a20a 100644 --- a/appdistribution/build.gradle.kts +++ b/appdistribution/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } allprojects { diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index 7b2358ac68..2bd1161127 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -62,7 +62,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.5") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // Firebase Authentication implementation("com.google.firebase:firebase-auth") diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 94c5aa8042..8eb799a20a 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } allprojects { diff --git a/build.gradle.kts b/build.gradle.kts index a547be2dd6..f548ae3e58 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false id("com.google.firebase.firebase-perf") version "2.0.1" apply false id("androidx.navigation.safeargs") version "2.9.5" apply false diff --git a/config/app/build.gradle.kts b/config/app/build.gradle.kts index 53cdd7261b..f4a9924500 100644 --- a/config/app/build.gradle.kts +++ b/config/app/build.gradle.kts @@ -53,7 +53,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // Firebase Remote Config implementation("com.google.firebase:firebase-config") diff --git a/config/build.gradle.kts b/config/build.gradle.kts index a1f2a0761e..c07891342a 100644 --- a/config/build.gradle.kts +++ b/config/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } allprojects { diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index db80b1fb10..ce7cc1c890 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -60,7 +60,7 @@ dependencies { implementation("androidx.activity:activity-ktx:1.11.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // Firebase Crashlytics implementation("com.google.firebase:firebase-crashlytics") diff --git a/crash/build.gradle.kts b/crash/build.gradle.kts index ae169108dc..128042bfff 100644 --- a/crash/build.gradle.kts +++ b/crash/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false } diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index 5e4c98db34..7b1b963d1e 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.5") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // Firebase Realtime Database implementation("com.google.firebase:firebase-database") diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 94c5aa8042..8eb799a20a 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } allprojects { diff --git a/firebase-ai/build.gradle.kts b/firebase-ai/build.gradle.kts index 80c84e2ea9..aa7d6f1011 100644 --- a/firebase-ai/build.gradle.kts +++ b/firebase-ai/build.gradle.kts @@ -6,5 +6,5 @@ plugins { id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" apply false id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index 37bdb54c7b..49abf9fe73 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -5,7 +5,7 @@ composeBom = "2024.09.00" composeNavigation = "2.9.5" coreKtx = "1.17.0" espressoCore = "3.7.0" -firebaseBom = "34.3.0" +firebaseBom = "34.4.0" junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.0.21" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index 8834c2ffd1..eab5daa7e9 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // Firestore implementation("com.google.firebase:firebase-firestore") diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index f68b944642..607aed6e7c 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false id("androidx.navigation.safeargs") version "2.9.5" apply false } diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index 5062d1b3eb..26b425ed47 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // Cloud Functions for Firebase implementation("com.google.firebase:firebase-functions") diff --git a/functions/build.gradle.kts b/functions/build.gradle.kts index 94c5aa8042..8eb799a20a 100644 --- a/functions/build.gradle.kts +++ b/functions/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } allprojects { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b659ae1a88..ec8d0947ac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "8.13.0" coilCompose = "2.7.0" -firebaseBom = "34.3.0" +firebaseBom = "34.4.0" kotlin = "2.2.20" coreKtx = "1.17.0" junit = "4.13.2" @@ -10,8 +10,8 @@ espressoCore = "3.7.0" kotlinxSerializationCore = "1.9.0" lifecycle = "2.9.4" activityCompose = "1.11.0" -composeBom = "2025.09.01" -googleServices = "4.4.3" +composeBom = "2025.10.00" +googleServices = "4.4.4" composeNavigation = "2.9.5" material = "1.13.0" webkit = "1.14.0" diff --git a/inappmessaging/app/build.gradle.kts b/inappmessaging/app/build.gradle.kts index 9a41b0f18d..a574d5e535 100644 --- a/inappmessaging/app/build.gradle.kts +++ b/inappmessaging/app/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // FIAM implementation("com.google.firebase:firebase-inappmessaging-display") diff --git a/inappmessaging/build.gradle.kts b/inappmessaging/build.gradle.kts index 94c5aa8042..8eb799a20a 100644 --- a/inappmessaging/build.gradle.kts +++ b/inappmessaging/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } allprojects { diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index 9540876715..8e1553f2fa 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // Firebase Cloud Messaging implementation("com.google.firebase:firebase-messaging") diff --git a/messaging/build.gradle.kts b/messaging/build.gradle.kts index a1f2a0761e..c07891342a 100644 --- a/messaging/build.gradle.kts +++ b/messaging/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } allprojects { diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index ec064f0f6b..54e4d83678 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -64,7 +64,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // Firebase Performance Monitoring implementation("com.google.firebase:firebase-perf") diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index bbefa26b4c..3b86946a3c 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.firebase-perf") version "2.0.1" apply false } diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index e38f410f85..3b2c99a62a 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.3.0")) + implementation(platform("com.google.firebase:firebase-bom:34.4.0")) // Cloud Storage for Firebase implementation("com.google.firebase:firebase-storage") diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index 94c5aa8042..8eb799a20a 100644 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("com.google.gms.google-services") version "4.4.3" apply false + id("com.google.gms.google-services") version "4.4.4" apply false } allprojects { From 729356b9111f97489d2de0a4c68fac450169e010 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Thu, 23 Oct 2025 04:28:48 -0700 Subject: [PATCH 21/52] Auto-update dependencies. (#2725) --- gradle/libs.versions.toml | 2 +- messaging/app/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec8d0947ac..ebeda0ad79 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ espressoCore = "3.7.0" kotlinxSerializationCore = "1.9.0" lifecycle = "2.9.4" activityCompose = "1.11.0" -composeBom = "2025.10.00" +composeBom = "2025.10.01" googleServices = "4.4.4" composeNavigation = "2.9.5" material = "1.13.0" diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index 8e1553f2fa..cf3ce5b67c 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -78,7 +78,7 @@ dependencies { implementation("com.google.firebase:firebase-installations:19.0.1") - implementation("androidx.work:work-runtime:2.10.5") + implementation("androidx.work:work-runtime:2.11.0") // Testing dependencies androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") From 33016ac6da8427e29d7811d4bc5b029f2afe754b Mon Sep 17 00:00:00 2001 From: DPEBot Date: Fri, 24 Oct 2025 07:52:18 -0700 Subject: [PATCH 22/52] Auto-update dependencies. (#2726) --- admob/build.gradle.kts | 2 +- analytics/build.gradle.kts | 2 +- appdistribution/build.gradle.kts | 2 +- auth/app/build.gradle.kts | 2 +- auth/build.gradle.kts | 2 +- build.gradle.kts | 4 ++-- config/build.gradle.kts | 2 +- crash/build.gradle.kts | 2 +- database/app/build.gradle.kts | 2 +- database/build.gradle.kts | 2 +- firebase-ai/build.gradle.kts | 6 +++--- firestore/app/build.gradle.kts | 2 +- firestore/build.gradle.kts | 2 +- functions/app/build.gradle.kts | 2 +- functions/build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- inappmessaging/build.gradle.kts | 2 +- messaging/build.gradle.kts | 2 +- perf/build.gradle.kts | 2 +- storage/build.gradle.kts | 2 +- 20 files changed, 23 insertions(+), 23 deletions(-) diff --git a/admob/build.gradle.kts b/admob/build.gradle.kts index c07891342a..1d80d309f2 100644 --- a/admob/build.gradle.kts +++ b/admob/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts index 5e59ee2b4f..02adfaa7f2 100644 --- a/analytics/build.gradle.kts +++ b/analytics/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/appdistribution/build.gradle.kts b/appdistribution/build.gradle.kts index 8eb799a20a..565eae3b34 100644 --- a/appdistribution/build.gradle.kts +++ b/appdistribution/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index 2bd1161127..2af4460f52 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -74,7 +74,7 @@ dependencies { // Firebase UI // Used in FirebaseUIActivity. - implementation("com.firebaseui:firebase-ui-auth:9.0.0") + implementation("com.firebaseui:firebase-ui-auth:9.1.1") // Facebook Android SDK (only required for Facebook Login) // Used in FacebookLoginActivity. diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 8eb799a20a..565eae3b34 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/build.gradle.kts b/build.gradle.kts index f548ae3e58..760078b895 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,13 +3,13 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false id("com.google.firebase.firebase-perf") version "2.0.1" apply false id("androidx.navigation.safeargs") version "2.9.5" apply false id("com.github.ben-manes.versions") version "0.53.0" apply true - id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" apply false + id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false } allprojects { diff --git a/config/build.gradle.kts b/config/build.gradle.kts index c07891342a..1d80d309f2 100644 --- a/config/build.gradle.kts +++ b/config/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/crash/build.gradle.kts b/crash/build.gradle.kts index 128042bfff..5e95340521 100644 --- a/crash/build.gradle.kts +++ b/crash/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false } diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index 7b1b963d1e..7a32f3d8e5 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -66,7 +66,7 @@ dependencies { // Firebase Authentication implementation("com.google.firebase:firebase-auth") - implementation("com.firebaseui:firebase-ui-database:9.0.0") + implementation("com.firebaseui:firebase-ui-database:9.1.1") // Needed to fix a dependency conflict with FirebaseUI' implementation("androidx.arch.core:core-runtime:2.2.0") diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 8eb799a20a..565eae3b34 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/firebase-ai/build.gradle.kts b/firebase-ai/build.gradle.kts index aa7d6f1011..d16da171a7 100644 --- a/firebase-ai/build.gradle.kts +++ b/firebase-ai/build.gradle.kts @@ -3,8 +3,8 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false - id("org.jetbrains.kotlin.plugin.compose") version "2.2.20" apply false - id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false + id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index eab5daa7e9..fb06063e08 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -71,7 +71,7 @@ dependencies { implementation("com.google.android.gms:play-services-auth:20.7.0") // FirebaseUI (for authentication) - implementation("com.firebaseui:firebase-ui-auth:9.0.0") + implementation("com.firebaseui:firebase-ui-auth:9.1.1") // Support Libs implementation("androidx.activity:activity-ktx:1.11.0") diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index 607aed6e7c..4a3348513d 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("androidx.navigation.safeargs") version "2.9.5" apply false } diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index 26b425ed47..f1c75af509 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { implementation("com.google.firebase:firebase-messaging") // Firebase UI - implementation("com.firebaseui:firebase-ui-auth:9.0.0") + implementation("com.firebaseui:firebase-ui-auth:9.1.1") // Google Play services implementation("com.google.android.gms:play-services-auth:21.2.0") diff --git a/functions/build.gradle.kts b/functions/build.gradle.kts index 8eb799a20a..565eae3b34 100644 --- a/functions/build.gradle.kts +++ b/functions/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ebeda0ad79..c60c47da91 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ agp = "8.13.0" coilCompose = "2.7.0" firebaseBom = "34.4.0" -kotlin = "2.2.20" +kotlin = "2.2.21" coreKtx = "1.17.0" junit = "4.13.2" junitVersion = "1.3.0" diff --git a/inappmessaging/build.gradle.kts b/inappmessaging/build.gradle.kts index 8eb799a20a..565eae3b34 100644 --- a/inappmessaging/build.gradle.kts +++ b/inappmessaging/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/messaging/build.gradle.kts b/messaging/build.gradle.kts index c07891342a..1d80d309f2 100644 --- a/messaging/build.gradle.kts +++ b/messaging/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index 3b86946a3c..e548750002 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.firebase-perf") version "2.0.1" apply false } diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index 8eb799a20a..565eae3b34 100644 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.0" apply false id("com.android.library") version "8.13.0" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } From d61f3ac12a9b684fa285730f316495c24c8fc25a Mon Sep 17 00:00:00 2001 From: DPEBot Date: Mon, 3 Nov 2025 09:49:41 -0800 Subject: [PATCH 23/52] Auto-update dependencies. (#2727) --- admob/app/build.gradle.kts | 2 +- analytics/app/build.gradle.kts | 2 +- appdistribution/app/build.gradle.kts | 2 +- auth/app/build.gradle.kts | 2 +- build.gradle.kts | 2 +- config/app/build.gradle.kts | 2 +- crash/app/build.gradle.kts | 2 +- database/app/build.gradle.kts | 2 +- firebase-ai/gradle/libs.versions.toml | 2 +- firestore/app/build.gradle.kts | 2 +- functions/app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- inappmessaging/app/build.gradle.kts | 2 +- messaging/app/build.gradle.kts | 2 +- perf/app/build.gradle.kts | 2 +- perf/build.gradle.kts | 2 +- storage/app/build.gradle.kts | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index 6bf9146de9..c4f71a3900 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation("com.google.android.gms:play-services-ads:23.3.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // For an optimal experience using AdMob, add the Firebase SDK // for Google Analytics. This is recommended, but not required. diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index 160159b63a..1bf04ad7ad 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // Firebase Analytics implementation("com.google.firebase:firebase-analytics") diff --git a/appdistribution/app/build.gradle.kts b/appdistribution/app/build.gradle.kts index 943a1d5083..0cfa89025c 100644 --- a/appdistribution/app/build.gradle.kts +++ b/appdistribution/app/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // ADD the SDK to the "prerelease" variant only (example) implementation("com.google.firebase:firebase-appdistribution:16.0.0-beta17") diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index 2af4460f52..bc75b1f2d7 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -62,7 +62,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.5") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // Firebase Authentication implementation("com.google.firebase:firebase-auth") diff --git a/build.gradle.kts b/build.gradle.kts index 760078b895..1057e662e2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false - id("com.google.firebase.firebase-perf") version "2.0.1" apply false + id("com.google.firebase.firebase-perf") version "2.0.2" apply false id("androidx.navigation.safeargs") version "2.9.5" apply false id("com.github.ben-manes.versions") version "0.53.0" apply true id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false diff --git a/config/app/build.gradle.kts b/config/app/build.gradle.kts index f4a9924500..9c9a508895 100644 --- a/config/app/build.gradle.kts +++ b/config/app/build.gradle.kts @@ -53,7 +53,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // Firebase Remote Config implementation("com.google.firebase:firebase-config") diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index ce7cc1c890..f0dd061b81 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -60,7 +60,7 @@ dependencies { implementation("androidx.activity:activity-ktx:1.11.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // Firebase Crashlytics implementation("com.google.firebase:firebase-crashlytics") diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index 7a32f3d8e5..bac8a6b803 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.5") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // Firebase Realtime Database implementation("com.google.firebase:firebase-database") diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index 49abf9fe73..ead7793300 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -5,7 +5,7 @@ composeBom = "2024.09.00" composeNavigation = "2.9.5" coreKtx = "1.17.0" espressoCore = "3.7.0" -firebaseBom = "34.4.0" +firebaseBom = "34.5.0" junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.0.21" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index fb06063e08..e5cabe14e2 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // Firestore implementation("com.google.firebase:firebase-firestore") diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index f1c75af509..b0eabf3a05 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // Cloud Functions for Firebase implementation("com.google.firebase:firebase-functions") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c60c47da91..3f0b0153a1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "8.13.0" coilCompose = "2.7.0" -firebaseBom = "34.4.0" +firebaseBom = "34.5.0" kotlin = "2.2.21" coreKtx = "1.17.0" junit = "4.13.2" diff --git a/inappmessaging/app/build.gradle.kts b/inappmessaging/app/build.gradle.kts index a574d5e535..7633d00f47 100644 --- a/inappmessaging/app/build.gradle.kts +++ b/inappmessaging/app/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // FIAM implementation("com.google.firebase:firebase-inappmessaging-display") diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index cf3ce5b67c..abd7e6930c 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // Firebase Cloud Messaging implementation("com.google.firebase:firebase-messaging") diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index 54e4d83678..f62e4d6ab6 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -64,7 +64,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // Firebase Performance Monitoring implementation("com.google.firebase:firebase-perf") diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index e548750002..57cc7e63d8 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -5,7 +5,7 @@ plugins { id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false - id("com.google.firebase.firebase-perf") version "2.0.1" apply false + id("com.google.firebase.firebase-perf") version "2.0.2" apply false } allprojects { diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index 3b2c99a62a..3cbd2a5020 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.4.0")) + implementation(platform("com.google.firebase:firebase-bom:34.5.0")) // Cloud Storage for Firebase implementation("com.google.firebase:firebase-storage") From afaca251320e30ed58eb61ecc19544920b303a40 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Mon, 10 Nov 2025 12:05:59 -0800 Subject: [PATCH 24/52] Auto-update dependencies. (#2728) --- admob/app/build.gradle.kts | 4 ++-- auth/app/build.gradle.kts | 4 ++-- build.gradle.kts | 2 +- database/app/build.gradle.kts | 4 ++-- firebase-ai/gradle/libs.versions.toml | 2 +- firestore/app/build.gradle.kts | 4 ++-- firestore/build.gradle.kts | 2 +- gradle/libs.versions.toml | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index c4f71a3900..78aac6ef5b 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -55,8 +55,8 @@ dependencies { implementation("androidx.appcompat:appcompat:1.7.1") implementation("com.google.android.material:material:1.13.0") implementation("androidx.browser:browser:1.5.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.5") - implementation("androidx.navigation:navigation-ui-ktx:2.9.5") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.6") + implementation("androidx.navigation:navigation-ui-ktx:2.9.6") implementation("com.google.android.gms:play-services-ads:23.3.0") diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index bc75b1f2d7..2de0cf37e9 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -58,8 +58,8 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout:2.2.1") implementation("androidx.vectordrawable:vectordrawable-animated:1.2.0") implementation("com.google.android.material:material:1.13.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.5") - implementation("androidx.navigation:navigation-ui-ktx:2.9.5") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.6") + implementation("androidx.navigation:navigation-ui-ktx:2.9.6") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.5.0")) diff --git a/build.gradle.kts b/build.gradle.kts index 1057e662e2..0acffa341b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false id("com.google.firebase.firebase-perf") version "2.0.2" apply false - id("androidx.navigation.safeargs") version "2.9.5" apply false + id("androidx.navigation.safeargs") version "2.9.6" apply false id("com.github.ben-manes.versions") version "0.53.0" apply true id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false } diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index bac8a6b803..51bc02e0d4 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -54,8 +54,8 @@ dependencies { implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.recyclerview:recyclerview:1.4.0") implementation("com.google.android.material:material:1.13.0") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.5") - implementation("androidx.navigation:navigation-ui-ktx:2.9.5") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.6") + implementation("androidx.navigation:navigation-ui-ktx:2.9.6") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.5.0")) diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index ead7793300..d1e4e966ec 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -2,7 +2,7 @@ activityCompose = "1.11.0" agp = "8.9.2" composeBom = "2024.09.00" -composeNavigation = "2.9.5" +composeNavigation = "2.9.6" coreKtx = "1.17.0" espressoCore = "3.7.0" firebaseBom = "34.5.0" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index e5cabe14e2..c2824bf86b 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -84,8 +84,8 @@ dependencies { implementation("androidx.media:media:1.7.1") implementation("androidx.recyclerview:recyclerview:1.4.0") implementation("androidx.multidex:multidex:2.0.1") - implementation("androidx.navigation:navigation-fragment-ktx:2.9.5") - implementation("androidx.navigation:navigation-ui-ktx:2.9.5") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.6") + implementation("androidx.navigation:navigation-ui-ktx:2.9.6") // Android architecture components implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.4") diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index 4a3348513d..141d63d1d4 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -5,7 +5,7 @@ plugins { id("com.android.library") version "8.13.0" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false - id("androidx.navigation.safeargs") version "2.9.5" apply false + id("androidx.navigation.safeargs") version "2.9.6" apply false } allprojects { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3f0b0153a1..850339b716 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,9 +10,9 @@ espressoCore = "3.7.0" kotlinxSerializationCore = "1.9.0" lifecycle = "2.9.4" activityCompose = "1.11.0" -composeBom = "2025.10.01" +composeBom = "2025.11.00" googleServices = "4.4.4" -composeNavigation = "2.9.5" +composeNavigation = "2.9.6" material = "1.13.0" webkit = "1.14.0" From eee8bc3ac83446915a8d7001f5683f67eefd18c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Mon, 17 Nov 2025 09:48:23 +0000 Subject: [PATCH 25/52] refactor(ai-logic): upgrade to Imagen 4 (#2730) --- .../com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 4 ++-- .../quickstart/ai/feature/media/imagen/ImagenViewModel.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index ad3a5cd435..b5732b7281 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -126,8 +126,8 @@ val FIREBASE_AI_SAMPLES = listOf( } ), Sample( - title = "Imagen 3 - image generation", - description = "Generate images using Imagen 3", + title = "Imagen 4 - image generation", + description = "Generate images using Imagen 4", navRoute = "imagen", categories = listOf(Category.IMAGE), initialPrompt = content { diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt index 8b6f223a6f..9a47b6eca2 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt @@ -88,7 +88,7 @@ class ImagenViewModel( imagenModel = Firebase.ai( backend = sample.backend ).imagenModel( - modelName = sample.modelName ?: "imagen-3.0-generate-002", + modelName = sample.modelName ?: "imagen-4.0-generate-001", generationConfig = config, safetySettings = settings ) From 21aee0d4186d12d3a79270b614180e1efd828673 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Tue, 18 Nov 2025 14:40:08 -0800 Subject: [PATCH 26/52] Auto-update dependencies. (#2731) --- admob/app/build.gradle.kts | 2 +- admob/build.gradle.kts | 4 ++-- analytics/app/build.gradle.kts | 2 +- analytics/build.gradle.kts | 4 ++-- appdistribution/app/build.gradle.kts | 2 +- appdistribution/build.gradle.kts | 4 ++-- auth/app/build.gradle.kts | 2 +- auth/build.gradle.kts | 4 ++-- build.gradle.kts | 4 ++-- config/app/build.gradle.kts | 2 +- config/build.gradle.kts | 4 ++-- crash/app/build.gradle.kts | 2 +- crash/build.gradle.kts | 4 ++-- database/app/build.gradle.kts | 2 +- database/build.gradle.kts | 4 ++-- firebase-ai/build.gradle.kts | 4 ++-- firebase-ai/gradle/libs.versions.toml | 2 +- firestore/app/build.gradle.kts | 2 +- firestore/build.gradle.kts | 4 ++-- functions/app/build.gradle.kts | 2 +- functions/build.gradle.kts | 4 ++-- gradle/libs.versions.toml | 4 ++-- inappmessaging/app/build.gradle.kts | 2 +- inappmessaging/build.gradle.kts | 4 ++-- internal/lint/build.gradle.kts | 6 +++--- messaging/app/build.gradle.kts | 2 +- messaging/build.gradle.kts | 4 ++-- perf/app/build.gradle.kts | 2 +- perf/build.gradle.kts | 4 ++-- storage/app/build.gradle.kts | 2 +- storage/build.gradle.kts | 4 ++-- 31 files changed, 49 insertions(+), 49 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index 78aac6ef5b..d4eb324a90 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation("com.google.android.gms:play-services-ads:23.3.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // For an optimal experience using AdMob, add the Firebase SDK // for Google Analytics. This is recommended, but not required. diff --git a/admob/build.gradle.kts b/admob/build.gradle.kts index 1d80d309f2..4dace862a8 100644 --- a/admob/build.gradle.kts +++ b/admob/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index 1bf04ad7ad..f53efbfaa4 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // Firebase Analytics implementation("com.google.firebase:firebase-analytics") diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts index 02adfaa7f2..334b0c71a5 100644 --- a/analytics/build.gradle.kts +++ b/analytics/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/appdistribution/app/build.gradle.kts b/appdistribution/app/build.gradle.kts index 0cfa89025c..94f81fc24d 100644 --- a/appdistribution/app/build.gradle.kts +++ b/appdistribution/app/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // ADD the SDK to the "prerelease" variant only (example) implementation("com.google.firebase:firebase-appdistribution:16.0.0-beta17") diff --git a/appdistribution/build.gradle.kts b/appdistribution/build.gradle.kts index 565eae3b34..1da0dfd29d 100644 --- a/appdistribution/build.gradle.kts +++ b/appdistribution/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index 2de0cf37e9..d989a17069 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -62,7 +62,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.6") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // Firebase Authentication implementation("com.google.firebase:firebase-auth") diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 565eae3b34..1da0dfd29d 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/build.gradle.kts b/build.gradle.kts index 0acffa341b..76a563a55f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false diff --git a/config/app/build.gradle.kts b/config/app/build.gradle.kts index 9c9a508895..f010eb48e1 100644 --- a/config/app/build.gradle.kts +++ b/config/app/build.gradle.kts @@ -53,7 +53,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // Firebase Remote Config implementation("com.google.firebase:firebase-config") diff --git a/config/build.gradle.kts b/config/build.gradle.kts index 1d80d309f2..4dace862a8 100644 --- a/config/build.gradle.kts +++ b/config/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index f0dd061b81..d1e6a385a8 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -60,7 +60,7 @@ dependencies { implementation("androidx.activity:activity-ktx:1.11.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // Firebase Crashlytics implementation("com.google.firebase:firebase-crashlytics") diff --git a/crash/build.gradle.kts b/crash/build.gradle.kts index 5e95340521..1ddce162ae 100644 --- a/crash/build.gradle.kts +++ b/crash/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index 51bc02e0d4..2059770db8 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.6") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // Firebase Realtime Database implementation("com.google.firebase:firebase-database") diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 565eae3b34..1da0dfd29d 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/firebase-ai/build.gradle.kts b/firebase-ai/build.gradle.kts index d16da171a7..cabdaab156 100644 --- a/firebase-ai/build.gradle.kts +++ b/firebase-ai/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21" apply false diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index d1e4e966ec..17c2d2c837 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -5,7 +5,7 @@ composeBom = "2024.09.00" composeNavigation = "2.9.6" coreKtx = "1.17.0" espressoCore = "3.7.0" -firebaseBom = "34.5.0" +firebaseBom = "34.6.0" junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.0.21" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index c2824bf86b..842d291b05 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // Firestore implementation("com.google.firebase:firebase-firestore") diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index 141d63d1d4..ff44b46b6f 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("androidx.navigation.safeargs") version "2.9.6" apply false diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index b0eabf3a05..f6de2fc7b3 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // Cloud Functions for Firebase implementation("com.google.firebase:firebase-functions") diff --git a/functions/build.gradle.kts b/functions/build.gradle.kts index 565eae3b34..1da0dfd29d 100644 --- a/functions/build.gradle.kts +++ b/functions/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 850339b716..0492a78514 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] -agp = "8.13.0" +agp = "8.13.1" coilCompose = "2.7.0" -firebaseBom = "34.5.0" +firebaseBom = "34.6.0" kotlin = "2.2.21" coreKtx = "1.17.0" junit = "4.13.2" diff --git a/inappmessaging/app/build.gradle.kts b/inappmessaging/app/build.gradle.kts index 7633d00f47..3c971653e2 100644 --- a/inappmessaging/app/build.gradle.kts +++ b/inappmessaging/app/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // FIAM implementation("com.google.firebase:firebase-inappmessaging-display") diff --git a/inappmessaging/build.gradle.kts b/inappmessaging/build.gradle.kts index 565eae3b34..1da0dfd29d 100644 --- a/inappmessaging/build.gradle.kts +++ b/inappmessaging/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/internal/lint/build.gradle.kts b/internal/lint/build.gradle.kts index 1b154e9659..bb16eb5ab5 100755 --- a/internal/lint/build.gradle.kts +++ b/internal/lint/build.gradle.kts @@ -9,8 +9,8 @@ java { } dependencies { - compileOnly("com.android.tools.lint:lint-api:31.13.0") - testImplementation("com.android.tools.lint:lint:31.13.0") - testImplementation("com.android.tools.lint:lint-tests:31.13.0") + compileOnly("com.android.tools.lint:lint-api:31.13.1") + testImplementation("com.android.tools.lint:lint:31.13.1") + testImplementation("com.android.tools.lint:lint-tests:31.13.1") testImplementation("junit:junit:4.13.2") } diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index abd7e6930c..71733f3c7a 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // Firebase Cloud Messaging implementation("com.google.firebase:firebase-messaging") diff --git a/messaging/build.gradle.kts b/messaging/build.gradle.kts index 1d80d309f2..4dace862a8 100644 --- a/messaging/build.gradle.kts +++ b/messaging/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index f62e4d6ab6..a7ba050152 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -64,7 +64,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // Firebase Performance Monitoring implementation("com.google.firebase:firebase-perf") diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index 57cc7e63d8..1111f2bb6a 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.firebase-perf") version "2.0.2" apply false diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index 3cbd2a5020..e3edc64527 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.5.0")) + implementation(platform("com.google.firebase:firebase-bom:34.6.0")) // Cloud Storage for Firebase implementation("com.google.firebase:firebase-storage") diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index 565eae3b34..1da0dfd29d 100644 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.0" apply false - id("com.android.library") version "8.13.0" apply false + id("com.android.application") version "8.13.1" apply false + id("com.android.library") version "8.13.1" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } From 4018b71077234e5d0ecf16add93bfd02437d825d Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Wed, 19 Nov 2025 19:26:24 -0800 Subject: [PATCH 27/52] Server Prompt Templates (#2732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Server Prompt Templates * fixes for comments * clean up samples * Update FirebaseAISamples.kt * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes * update BoM version * format * Auto-update dependencies. (#2728) * refactor(ai-logic): upgrade to Imagen 4 (#2730) * Auto-update dependencies. (#2731) --------- Co-authored-by: David Motsonashvili Co-authored-by: Rosário P. Fernandes Co-authored-by: DPEBot --- .../quickstart/ai/FirebaseAISamples.kt | 29 +++- .../firebase/quickstart/ai/MainActivity.kt | 8 + .../ai/feature/media/imagen/EditingMode.kt | 1 + .../feature/media/imagen/ImagenViewModel.kt | 29 +++- .../ai/feature/text/TextGenScreen.kt | 142 ++++++++++++++++++ .../ai/feature/text/TextGenViewModel.kt | 83 ++++++++++ .../quickstart/ai/ui/navigation/Sample.kt | 2 + 7 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index b5732b7281..d38776bdee 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -328,7 +328,8 @@ val FIREBASE_AI_SAMPLES = listOf( ), Sample( title = "Grounding with Google Search", - description = "Use Grounding with Google Search to get responses based on up-to-date information from the web.", + description = "Use Grounding with Google Search to get responses based on up-to-date information from the" + + " web.", navRoute = "chat", categories = listOf(Category.TEXT, Category.DOCUMENT), modelName = "gemini-2.5-flash", @@ -339,4 +340,30 @@ val FIREBASE_AI_SAMPLES = listOf( ) }, ), + Sample( + title = "Server Prompt Template - Imagen", + description = "Generate an image using a server prompt template. Note that you need to setup the template in" + + "the Firebase console before running this demo.", + navRoute = "imagen", + categories = listOf(Category.IMAGE), + initialPrompt = content { text("List of things that should be in the image") }, + allowEmptyPrompt = false, + editingMode = EditingMode.TEMPLATE, + // To make this work, create an "Imagen (Basic)" server prompt template in your Firebase project with this name + templateId = "imagen-basic", + templateKey = "prompt" + ), + Sample( + title = "Server Prompt Templates - Gemini", + description = "Generate an invoice using server prompt templates. Note that you need to setup the template" + + " in the Firebase console before running this demo.", + navRoute = "text", + categories = listOf(Category.TEXT), + initialPrompt = content { text("Jane Doe") }, + allowEmptyPrompt = false, + // To make this work, create an `Input + System Instructions` template in your Firebase project with this name + templateId = "input-system-instructions", + templateKey = "customerName" + ), + ) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt index 54eaff6543..8c9a5188cc 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt @@ -35,6 +35,8 @@ import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenRoute import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenScreen import com.google.firebase.quickstart.ai.feature.text.ChatRoute import com.google.firebase.quickstart.ai.feature.text.ChatScreen +import com.google.firebase.quickstart.ai.feature.text.TextGenRoute +import com.google.firebase.quickstart.ai.feature.text.TextGenScreen import com.google.firebase.quickstart.ai.ui.navigation.MainMenuScreen import com.google.firebase.quickstart.ai.ui.theme.FirebaseAILogicTheme @@ -90,6 +92,9 @@ class MainActivity : ComponentActivity() { "stream" -> { navController.navigate(StreamRealtimeRoute(it.id)) } + "text" -> { + navController.navigate(TextGenRoute(it.id)) + } } } ) @@ -106,6 +111,9 @@ class MainActivity : ComponentActivity() { composable { StreamRealtimeScreen() } + composable { + TextGenScreen() + } } } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt index 6e997092d6..10093e7350 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt @@ -6,4 +6,5 @@ enum class EditingMode { OUTPAINTING, SUBJECT_REFERENCE, STYLE_TRANSFER, + TEMPLATE, } \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt index 9a47b6eca2..cf794a0368 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import androidx.core.graphics.scale +import com.google.firebase.ai.TemplateImagenModel import com.google.firebase.ai.type.Dimensions import com.google.firebase.ai.type.ImagenBackgroundMask import com.google.firebase.ai.type.ImagenEditMode @@ -67,6 +68,10 @@ class ImagenViewModel( val additionalImage = sample.additionalImage + val templateId = sample.templateId + + val templateKey = sample.templateKey + private val _attachedImage = MutableStateFlow(null) val attachedImage: StateFlow = _attachedImage @@ -75,6 +80,7 @@ class ImagenViewModel( // Firebase AI Logic private val imagenModel: ImagenModel + private val templateImagenModel: TemplateImagenModel init { val config = imagenGenerationConfig { @@ -92,23 +98,34 @@ class ImagenViewModel( generationConfig = config, safetySettings = settings ) + templateImagenModel = Firebase.ai.templateImagenModel() } fun generateImages(inputText: String) { viewModelScope.launch { _isLoading.value = true + _errorMessage.value = null // clear error message try { val imageResponse = when(sample.editingMode) { EditingMode.INPAINTING -> inpaint(imagenModel, inputText) EditingMode.OUTPAINTING -> outpaint(imagenModel, inputText) EditingMode.SUBJECT_REFERENCE -> drawReferenceSubject(imagenModel, inputText) EditingMode.STYLE_TRANSFER -> transferStyle(imagenModel, inputText) + EditingMode.TEMPLATE -> + generateWithTemplate(templateImagenModel, templateId!!, mapOf(templateKey!! to inputText)) else -> generate(imagenModel, inputText) } _generatedBitmaps.value = imageResponse.images.map { it.asBitmap() } - _errorMessage.value = null // clear error message } catch (e: Exception) { - _errorMessage.value = e.localizedMessage + val errorMessage = + if ((e.localizedMessage?.contains("not found") == true) && + sample.editingMode == EditingMode.TEMPLATE) { + "Template was not found, please verify that your project contains a" + + " template named \"$templateId\"." + } else { + e.localizedMessage + } + _errorMessage.value = errorMessage } finally { _isLoading.value = false } @@ -212,4 +229,12 @@ class ImagenViewModel( inputText ) } + + suspend fun generateWithTemplate( + model: TemplateImagenModel, + templateId: String, + inputMap: Map + ): ImagenGenerationResponse { + return model.generateImages(templateId, inputMap) + } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt new file mode 100644 index 0000000000..26331333bf --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt @@ -0,0 +1,142 @@ +package com.google.firebase.quickstart.ai.feature.text + +import android.net.Uri +import android.provider.OpenableColumns +import android.text.format.Formatter +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.firebase.quickstart.ai.R +import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable + +@Serializable +class TextGenRoute(val sampleId: String) + +@Composable +fun TextGenScreen( + textGenViewModel: TextGenViewModel = viewModel() +) { + var textPrompt by rememberSaveable { mutableStateOf(textGenViewModel.initialPrompt) } + val errorMessage by textGenViewModel.errorMessage.collectAsStateWithLifecycle() + val isLoading by textGenViewModel.isLoading.collectAsStateWithLifecycle() + val generatedText by textGenViewModel.generatedText.collectAsStateWithLifecycle() + + Column( + modifier = Modifier.verticalScroll(rememberScrollState()) + ) { + ElevatedCard( + modifier = Modifier + .padding(all = 16.dp) + .fillMaxWidth(), + shape = MaterialTheme.shapes.large + ) { + OutlinedTextField( + value = textPrompt, + label = { Text("Prompt") }, + placeholder = { Text("Enter text to generate") }, + onValueChange = { textPrompt = it }, + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + ) + Row() { + TextButton( + onClick = { + if (textGenViewModel.allowEmptyPrompt || textPrompt.isNotBlank()) { + textGenViewModel.generate(textPrompt) + } + }, + modifier = Modifier.padding(end = 16.dp, bottom = 16.dp) + ) { + Text("Generate") + } + } + + } + + if (isLoading) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .padding(all = 8.dp) + .align(Alignment.CenterHorizontally) + ) { + CircularProgressIndicator() + } + } + errorMessage?.let { + Card( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + shape = MaterialTheme.shapes.large, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer + ) + ) { + Text( + text = it, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(all = 16.dp) + ) + } + } + generatedText?.let { + Card( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + shape = MaterialTheme.shapes.large, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ) + ) { + Text( + text = it, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(all = 16.dp) + ) + } + } + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt new file mode 100644 index 0000000000..52532daef2 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt @@ -0,0 +1,83 @@ +package com.google.firebase.quickstart.ai.feature.text + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import com.google.firebase.Firebase +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.ai.type.asTextOrNull +import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import com.google.firebase.ai.GenerativeModel +import com.google.firebase.ai.TemplateGenerativeModel + +@OptIn(PublicPreviewAPI::class) +class TextGenViewModel( + savedStateHandle: SavedStateHandle +) : ViewModel() { + private val sampleId = savedStateHandle.toRoute().sampleId + private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } + val initialPrompt = sample.initialPrompt?.parts?.first()?.asTextOrNull().orEmpty() + + private val _errorMessage: MutableStateFlow = MutableStateFlow(null) + val errorMessage: StateFlow = _errorMessage + + private val _isLoading = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading + + val allowEmptyPrompt = sample.allowEmptyPrompt + + val templateId = sample.templateId + + val templateKey = sample.templateKey + + private val _generatedText = MutableStateFlow(null) + val generatedText: StateFlow = _generatedText + + // Firebase AI Logic + private val generativeModel: GenerativeModel + private val templateGenerativeModel: TemplateGenerativeModel + + init { + generativeModel = Firebase.ai( + backend = sample.backend // GenerativeBackend.googleAI() by default + ).generativeModel( + modelName = sample.modelName ?: "gemini-2.5-flash", + systemInstruction = sample.systemInstructions, + generationConfig = sample.generationConfig, + tools = sample.tools + ) + templateGenerativeModel = Firebase.ai.templateGenerativeModel() + } + + fun generate(inputText: String) { + viewModelScope.launch { + _isLoading.value = true + _errorMessage.value = null // clear error message + try { + val generativeResponse = if (templateId != null) { + templateGenerativeModel + .generateContent(templateId, mapOf(templateKey!! to inputText)) + } else { + generativeModel.generateContent(inputText) + } + _generatedText.value = generativeResponse.text + } catch (e: Exception) { + val errorMessage = + if ((e.localizedMessage?.contains("not found") == true) && (templateId != null)) { + "Template was not found, please verify that your project contains a" + + " template named \"$templateId\"." + } else { + e.localizedMessage + } + _errorMessage.value = errorMessage + } finally { + _isLoading.value = false + } + } + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt index ad0cccbecf..3704b2b449 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt @@ -45,4 +45,6 @@ data class Sample( val imageLabels: List = emptyList(), val selectionOptions: List = emptyList(), val editingMode: EditingMode? = null, + val templateId: String? = null, + val templateKey: String? = null, ) From 92d6572810bb1165565e6c94fb7d4c109e87d610 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Date: Wed, 26 Nov 2025 17:21:25 -0500 Subject: [PATCH 28/52] [ALF] Add LiveAPI video sample (#2724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ALF] Add LiveAPI video sample * Actually runs this time * Point to the right screen * feat: Implement camera frame capture and logging - Modified StreamRealtimeVideoScreen to reduce the camera view to half the screen. - Implemented a frame analyzer in CameraView to capture frames once per second. - Added logging for the size of the captured frame's byte array. * Additional changes not captured by the previous commit * Capture audio * Reorg code * Permissions correctly requested * Format new files using ktfmt * Better naming * Suppress unnecessary lints * bump versions * additional lint fixes * Add entries to top level libs.versions.toml * Update libs.versions.toml Use the newest firebase-bom * Update firebaseBom and firebase-ai version references * Update firebase-ai/app/build.gradle.kts Co-authored-by: Rosário P. Fernandes * Fix manifest * Update firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt Co-authored-by: Rosário P. Fernandes * Remove duplicated entries Updated versions for firebaseBom and kotlin. * Fixed toml file * Yet another fix to the toml file * Fix error introduced during merge * Missing closing bracket --------- Co-authored-by: Rosário P. Fernandes --- firebase-ai/app/build.gradle.kts | 7 ++ firebase-ai/app/src/main/AndroidManifest.xml | 7 +- .../quickstart/ai/FirebaseAISamples.kt | 32 ++++++ .../firebase/quickstart/ai/MainActivity.kt | 21 +++- .../ai/feature/live/BidiViewModel.kt | 82 +++++++--------- .../quickstart/ai/feature/live/CameraView.kt | 98 +++++++++++++++++++ .../ai/feature/live/StreamRealtimeScreen.kt | 2 - .../feature/live/StreamRealtimeVideoScreen.kt | 82 ++++++++++++++++ .../quickstart/ai/feature/text/ChatScreen.kt | 3 +- .../app/src/main/res/values/colors.xml | 7 -- firebase-ai/gradle/libs.versions.toml | 14 ++- firebase-ai/settings.gradle.kts | 1 + gradle/libs.versions.toml | 46 +++++---- 13 files changed, 316 insertions(+), 86 deletions(-) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt diff --git a/firebase-ai/app/build.gradle.kts b/firebase-ai/app/build.gradle.kts index f350bc2ad4..a3e1a55086 100644 --- a/firebase-ai/app/build.gradle.kts +++ b/firebase-ai/app/build.gradle.kts @@ -67,6 +67,13 @@ dependencies { // Webkit implementation(libs.androidx.webkit) + // CameraX (for video with the Gemini Live API) + implementation(libs.androidx.camera.core) + implementation(libs.androidx.camera.camera2) + implementation(libs.androidx.camera.lifecycle) + implementation(libs.androidx.camera.view) + implementation(libs.androidx.camera.extensions) + // Material for XML-based theme implementation(libs.material) diff --git a/firebase-ai/app/src/main/AndroidManifest.xml b/firebase-ai/app/src/main/AndroidManifest.xml index 699c61714c..bad5b1aa6b 100644 --- a/firebase-ai/app/src/main/AndroidManifest.xml +++ b/firebase-ai/app/src/main/AndroidManifest.xml @@ -6,6 +6,11 @@ + + + + + - \ No newline at end of file + diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index d38776bdee..3f6ceea9b2 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -275,6 +275,8 @@ val FIREBASE_AI_SAMPLES = listOf( description = "Use bidirectional streaming to get information about" + " weather conditions for a specific US city on a specific date", navRoute = "stream", + backend = GenerativeBackend.vertexAI(), + modelName = "gemini-2.0-flash-live-preview-04-09", categories = listOf(Category.LIVE_API, Category.AUDIO, Category.FUNCTION_CALLING), tools = listOf( Tool.functionDeclarations( @@ -298,6 +300,36 @@ val FIREBASE_AI_SAMPLES = listOf( text("What was the weather in Boston, MA on October 17, 2024?") } ), + Sample( + title = "Gemini Live (Video input)", + description = "Use bidirectional streaming to chat with Gemini using your" + + " phone's camera", + navRoute = "streamVideo", + backend = GenerativeBackend.vertexAI(), + modelName = "gemini-2.0-flash-live-preview-04-09", + categories = listOf(Category.LIVE_API, Category.VIDEO, Category.FUNCTION_CALLING), + tools = listOf( + Tool.functionDeclarations( + listOf( + FunctionDeclaration( + "fetchWeather", + "Get the weather conditions for a specific US city on a specific date.", + mapOf( + "city" to Schema.string("The US city of the location."), + "state" to Schema.string("The US state of the location."), + "date" to Schema.string( + "The date for which to get the weather." + + " Date must be in the format: YYYY-MM-DD." + ), + ), + ) + ) + ) + ), + initialPrompt = content { + text("What was the weather in Boston, MA on October 17, 2024?") + } + ), Sample( title = "Weather Chat", description = "Use function calling to get the weather conditions" + diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt index 8c9a5188cc..6848d08291 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt @@ -1,6 +1,7 @@ package com.google.firebase.quickstart.ai import android.Manifest +import android.annotation.SuppressLint import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.BitmapFactory @@ -31,6 +32,8 @@ import androidx.navigation.compose.rememberNavController import com.google.firebase.ai.type.toImagenInlineImage import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeRoute import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeScreen +import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoRoute +import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoScreen import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenRoute import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenScreen import com.google.firebase.quickstart.ai.feature.text.ChatRoute @@ -44,10 +47,7 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if(ContextCompat.checkSelfPermission(this, - Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 1) - } + enableEdgeToEdge() catImage = BitmapFactory.decodeResource(applicationContext.resources, R.drawable.cat) setContent { @@ -92,6 +92,9 @@ class MainActivity : ComponentActivity() { "stream" -> { navController.navigate(StreamRealtimeRoute(it.id)) } + "streamVideo" -> { + navController.navigate(StreamRealtimeVideoRoute(it.id)) + } "text" -> { navController.navigate(TextGenRoute(it.id)) } @@ -107,10 +110,18 @@ class MainActivity : ComponentActivity() { composable { ImagenScreen() } - // Stream Realtime Samples + // The permission is checked by the @RequiresPermission annotation on the + // StreamRealtimeScreen composable. + @SuppressLint("MissingPermission") composable { StreamRealtimeScreen() } + // The permission is checked by the @RequiresPermission annotation on the + // StreamRealtimeVideoScreen composable. + @SuppressLint("MissingPermission") + composable { + StreamRealtimeVideoScreen() + } composable { TextGenScreen() } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt index a7f183704f..3f36b81c3f 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt @@ -1,56 +1,33 @@ -package com.google.firebase.quickstart.ai.feature.media.imagen +package com.google.firebase.quickstart.ai.feature.live -import android.Manifest +import android.annotation.SuppressLint import android.graphics.Bitmap -import androidx.annotation.RequiresPermission import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute import com.google.firebase.Firebase import com.google.firebase.ai.FirebaseAI -import com.google.firebase.ai.ImagenModel -import com.google.firebase.ai.LiveGenerativeModel -import com.google.firebase.ai.ai import com.google.firebase.ai.type.FunctionCallPart import com.google.firebase.ai.type.FunctionResponsePart -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.ImagenAspectRatio -import com.google.firebase.ai.type.ImagenImageFormat -import com.google.firebase.ai.type.ImagenPersonFilterLevel -import com.google.firebase.ai.type.ImagenSafetyFilterLevel -import com.google.firebase.ai.type.ImagenSafetySettings -import com.google.firebase.ai.type.InlineDataPart -import com.google.firebase.ai.type.LiveServerContent -import com.google.firebase.ai.type.LiveServerMessage +import com.google.firebase.ai.type.InlineData import com.google.firebase.ai.type.LiveSession import com.google.firebase.ai.type.PublicPreviewAPI import com.google.firebase.ai.type.ResponseModality import com.google.firebase.ai.type.SpeechConfig -import com.google.firebase.ai.type.TextPart -import com.google.firebase.ai.type.Tool import com.google.firebase.ai.type.Voice -import com.google.firebase.ai.type.asTextOrNull -import com.google.firebase.ai.type.imagenGenerationConfig import com.google.firebase.ai.type.liveGenerationConfig import com.google.firebase.app import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES -import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeRoute -import com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository import com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository.Companion.fetchWeather -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import java.io.ByteArrayOutputStream import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonPrimitive @OptIn(PublicPreviewAPI::class) -class BidiViewModel( - savedStateHandle: SavedStateHandle -) : ViewModel() { +class BidiViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private val sampleId = savedStateHandle.toRoute().sampleId private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } @@ -63,41 +40,54 @@ class BidiViewModel( // Change this to ContentModality.TEXT if you want text output. responseModality = ResponseModality.AUDIO } + @OptIn(PublicPreviewAPI::class) - val liveModel = FirebaseAI.getInstance(Firebase.app, sample.backend).liveModel( - "gemini-live-2.5-flash", - generationConfig = liveGenerationConfig, - tools = sample.tools - ) - runBlocking { - liveSession = liveModel.connect() - } + val liveModel = + FirebaseAI.getInstance(Firebase.app, sample.backend) + .liveModel( + modelName = sample.modelName ?: "gemini-live-2.5-flash", + generationConfig = liveGenerationConfig, + tools = sample.tools, + ) + runBlocking { liveSession = liveModel.connect() } } - fun handler(fetchWeatherCall: FunctionCallPart) : FunctionResponsePart { - val response:JsonObject + fun handler(fetchWeatherCall: FunctionCallPart): FunctionResponsePart { + val response: JsonObject fetchWeatherCall.let { val city = it.args["city"]?.jsonPrimitive?.content val state = it.args["state"]?.jsonPrimitive?.content val date = it.args["date"]?.jsonPrimitive?.content runBlocking { - response = if(!city.isNullOrEmpty() and !state.isNullOrEmpty() and date.isNullOrEmpty()) { - fetchWeather(city!!, state!!, date!!) - } else { - JsonObject(emptyMap()) - } + response = + if (!city.isNullOrEmpty() and !state.isNullOrEmpty() and date.isNullOrEmpty()) { + fetchWeather(city!!, state!!, date!!) + } else { + JsonObject(emptyMap()) + } } } - return FunctionResponsePart("fetchWeather", response, fetchWeatherCall.id) + return FunctionResponsePart("fetchWeather", response, fetchWeatherCall.id) } - @RequiresPermission(Manifest.permission.RECORD_AUDIO) + + // The permission check is handled by the view that calls this function. + @SuppressLint("MissingPermission") suspend fun startConversation() { - liveSession.startAudioConversation(::handler) + liveSession.startAudioConversation(::handler) } fun endConversation() { liveSession.stopAudioConversation() } + fun sendVideoFrame(frame: Bitmap) { + viewModelScope.launch { + // Directly compress the Bitmap to a ByteArray + val byteArrayOutputStream = ByteArrayOutputStream() + frame.compress(Bitmap.CompressFormat.JPEG, 80, byteArrayOutputStream) + val jpegBytes = byteArrayOutputStream.toByteArray() + liveSession.sendVideoRealtime(InlineData(jpegBytes, "image/jpeg")) + } + } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt new file mode 100644 index 0000000000..8fcf0258ba --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt @@ -0,0 +1,98 @@ +package com.google.firebase.quickstart.ai.feature.live + +import android.annotation.SuppressLint +import android.graphics.Bitmap +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat +import androidx.lifecycle.LifecycleOwner +import kotlin.time.Duration.Companion.seconds + +@Composable +fun CameraView( + modifier: Modifier = Modifier, + cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA, + onFrameCaptured: (Bitmap) -> Unit, +) { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) } + + AndroidView( + factory = { ctx -> + val previewView = PreviewView(ctx) + val executor = ContextCompat.getMainExecutor(ctx) + cameraProviderFuture.addListener( + { + val cameraProvider = cameraProviderFuture.get() + bindPreview( + lifecycleOwner, + previewView, + cameraProvider, + cameraSelector, + onFrameCaptured, + ) + }, + executor, + ) + previewView + }, + modifier = modifier, + ) +} + +private fun bindPreview( + lifecycleOwner: LifecycleOwner, + previewView: PreviewView, + cameraProvider: ProcessCameraProvider, + cameraSelector: CameraSelector, + onFrameCaptured: (Bitmap) -> Unit, +) { + val preview = + Preview.Builder().build().also { it.setSurfaceProvider(previewView.surfaceProvider) } + + val imageAnalysis = + ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + .also { + it.setAnalyzer( + ContextCompat.getMainExecutor(previewView.context), + SnapshotFrameAnalyzer(onFrameCaptured), + ) + } + + cameraProvider.unbindAll() + cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis) +} + +// Calls the [onFrameCaptured] callback with the captured frame every second. +private class SnapshotFrameAnalyzer(private val onFrameCaptured: (Bitmap) -> Unit) : + ImageAnalysis.Analyzer { + private var lastFrameTimestamp = 0L + private val interval = 1.seconds // 1 second + + @SuppressLint("UnsafeOptInUsageError") + override fun analyze(image: ImageProxy) { + val currentTimestamp = System.currentTimeMillis() + if (lastFrameTimestamp == 0L) { + lastFrameTimestamp = currentTimestamp + } + + if (currentTimestamp - lastFrameTimestamp >= interval.inWholeMilliseconds) { + onFrameCaptured(image.toBitmap()) + lastFrameTimestamp = currentTimestamp + } + image.close() + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt index f088cda23b..194b04023f 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt @@ -32,8 +32,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.viewmodel.compose.viewModel -import com.google.firebase.quickstart.ai.feature.media.imagen.BidiViewModel -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt new file mode 100644 index 0000000000..a30c93980c --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt @@ -0,0 +1,82 @@ +package com.google.firebase.quickstart.ai.feature.live + +import android.Manifest +import android.content.pm.PackageManager +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.RequiresPermission +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import androidx.lifecycle.viewmodel.compose.viewModel +import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable + +@Serializable class StreamRealtimeVideoRoute(val sampleId: String) + +@RequiresPermission(allOf = [Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA]) +@Composable +fun StreamRealtimeVideoScreen(bidiView: BidiViewModel = viewModel()) { + val backgroundColor = MaterialTheme.colorScheme.background + + val scope = rememberCoroutineScope() + + val context = LocalContext.current + var hasPermissions by remember { + mutableStateOf( + ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == + PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == + PackageManager.PERMISSION_GRANTED + ) + } + + val launcher = + rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { + permissions -> + hasPermissions = permissions.values.all { it } + } + + LaunchedEffect(Unit) { + if (!hasPermissions) { + launcher.launch(arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)) + } + } + + DisposableEffect(hasPermissions) { + if (hasPermissions) { + scope.launch { bidiView.startConversation() } + } + onDispose { bidiView.endConversation() } + } + + Surface(modifier = Modifier.fillMaxSize(), color = backgroundColor) { + Column(modifier = Modifier.fillMaxSize()) { + if (hasPermissions) { + Box(modifier = Modifier.fillMaxSize()) { + CameraView( + modifier = Modifier.fillMaxHeight(0.5f), + onFrameCaptured = { bitmap -> bidiView.sendVideoFrame(bitmap) }, + ) + } + } else { + Text("Camera and audio permissions are required to use this feature.") + } + } + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt index 276024c27f..f89bcba17a 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.Intent import android.graphics.Bitmap import android.net.Uri +import androidx.core.net.toUri import android.provider.OpenableColumns import android.text.format.Formatter import android.webkit.WebResourceRequest @@ -374,7 +375,7 @@ fun SourceLinkView( ClickableText(text = annotatedString, onClick = { offset -> annotatedString.getStringAnnotations(tag = "URL", start = offset, end = offset) .firstOrNull()?.let { annotation -> - context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(annotation.item))) + context.startActivity(Intent(Intent.ACTION_VIEW, annotation.item.toUri())) } }) } diff --git a/firebase-ai/app/src/main/res/values/colors.xml b/firebase-ai/app/src/main/res/values/colors.xml index f8c6127d32..55344e5192 100644 --- a/firebase-ai/app/src/main/res/values/colors.xml +++ b/firebase-ai/app/src/main/res/values/colors.xml @@ -1,10 +1,3 @@ - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF \ No newline at end of file diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index 17c2d2c837..ee78452feb 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -1,19 +1,20 @@ [versions] activityCompose = "1.11.0" -agp = "8.9.2" -composeBom = "2024.09.00" +agp = "8.13.0" +composeBom = "2025.10.00" composeNavigation = "2.9.6" coreKtx = "1.17.0" espressoCore = "3.7.0" firebaseBom = "34.6.0" junit = "4.13.2" junitVersion = "1.3.0" -kotlin = "2.0.21" +kotlin = "2.2.20" kotlinxSerializationCore = "1.9.0" lifecycle = "2.9.4" -lifecycleRuntimeKtx = "2.8.7" +lifecycleRuntimeKtx = "2.9.4" material = "1.13.0" webkit = "1.14.0" +camerax = "1.5.1" [libraries] androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } @@ -41,6 +42,11 @@ firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "fir junit = { group = "junit", name = "junit", version.ref = "junit" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationCore" } material = { module = "com.google.android.material:material", version.ref = "material" } +androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" } +androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "camerax" } +androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "camerax" } +androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" } +androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "camerax" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/firebase-ai/settings.gradle.kts b/firebase-ai/settings.gradle.kts index f1cb15d710..26668b9361 100644 --- a/firebase-ai/settings.gradle.kts +++ b/firebase-ai/settings.gradle.kts @@ -14,6 +14,7 @@ pluginManagement { dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { + mavenLocal() google() mavenCentral() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0492a78514..b6007ae845 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,49 +1,55 @@ [versions] +activityCompose = "1.11.0" agp = "8.13.1" +camerax = "1.5.1" coilCompose = "2.7.0" -firebaseBom = "34.6.0" -kotlin = "2.2.21" +composeBom = "2025.11.00" +composeNavigation = "2.9.6" coreKtx = "1.17.0" +espressoCore = "3.7.0" +firebaseBom = "34.6.0" +googleServices = "4.4.4" junit = "4.13.2" junitVersion = "1.3.0" -espressoCore = "3.7.0" +kotlin = "2.2.21" kotlinxSerializationCore = "1.9.0" lifecycle = "2.9.4" -activityCompose = "1.11.0" -composeBom = "2025.11.00" -googleServices = "4.4.4" -composeNavigation = "2.9.6" material = "1.13.0" webkit = "1.14.0" [libraries] +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" } +androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "camerax" } +androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "camerax" } +androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" } +androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "camerax" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-lifecycle-runtime-compose-android = { module = "androidx.lifecycle:lifecycle-runtime-compose-android", version.ref = "lifecycle" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" } androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" } androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite" } -coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" } -firebase-ai = { module = "com.google.firebase:firebase-ai" } -firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } -junit = { group = "junit", name = "junit", version.ref = "junit" } -androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } -androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } -androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } -androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } -androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } -androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } -androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } +coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" } compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"} +firebase-ai = { module = "com.google.firebase:firebase-ai" } +firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } +junit = { group = "junit", name = "junit", version.ref = "junit" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationCore" } -androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } material = { module = "com.google.android.material:material", version.ref = "material" } [plugins] From 1e57d4848c820b535f029e8c04e04ff7b9ae20c5 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Thu, 27 Nov 2025 03:54:03 -0800 Subject: [PATCH 29/52] Auto-update dependencies. (#2735) --- analytics/app/build.gradle.kts | 2 +- auth/app/build.gradle.kts | 2 +- crash/app/build.gradle.kts | 2 +- firebase-ai/gradle/libs.versions.toml | 4 ++-- firestore/app/build.gradle.kts | 6 +++--- functions/app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 6 +++--- messaging/app/build.gradle.kts | 2 +- perf/app/build.gradle.kts | 2 +- storage/app/build.gradle.kts | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index f53efbfaa4..e22d1afd4e 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.preference:preference-ktx:1.2.1") // Needed to override the version used by preference-ktx - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.6.0")) diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index d989a17069..0339aa0d5f 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -54,7 +54,7 @@ dependencies { implementation(project(":internal:lintchecks")) implementation("androidx.multidex:multidex:2.0.1") - implementation("androidx.activity:activity-ktx:1.11.0") + implementation("androidx.activity:activity-ktx:1.12.0") implementation("androidx.constraintlayout:constraintlayout:2.2.1") implementation("androidx.vectordrawable:vectordrawable-animated:1.2.0") implementation("com.google.android.material:material:1.13.0") diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index d1e6a385a8..81f4c019fe 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) implementation("com.google.android.material:material:1.13.0") - implementation("androidx.activity:activity-ktx:1.11.0") + implementation("androidx.activity:activity-ktx:1.12.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.6.0")) diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index ee78452feb..a9d083ad8a 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -activityCompose = "1.11.0" +activityCompose = "1.12.0" agp = "8.13.0" composeBom = "2025.10.00" composeNavigation = "2.9.6" @@ -10,7 +10,7 @@ junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.2.20" kotlinxSerializationCore = "1.9.0" -lifecycle = "2.9.4" +lifecycle = "2.10.0" lifecycleRuntimeKtx = "2.9.4" material = "1.13.0" webkit = "1.14.0" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index 842d291b05..8e44a0f3c6 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -74,7 +74,7 @@ dependencies { implementation("com.firebaseui:firebase-ui-auth:9.1.1") // Support Libs - implementation("androidx.activity:activity-ktx:1.11.0") + implementation("androidx.activity:activity-ktx:1.12.0") implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.core:core-ktx:1.17.0") implementation("androidx.vectordrawable:vectordrawable-animated:1.2.0") @@ -88,9 +88,9 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.6") // Android architecture components - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.4") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0") implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") - annotationProcessor("androidx.lifecycle:lifecycle-compiler:2.9.4") + annotationProcessor("androidx.lifecycle:lifecycle-compiler:2.10.0") // Third-party libraries implementation("me.zhanghai.android.materialratingbar:library:1.4.0") diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index f6de2fc7b3..81992db5a8 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -49,7 +49,7 @@ dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) - implementation("androidx.activity:activity-ktx:1.11.0") + implementation("androidx.activity:activity-ktx:1.12.0") implementation("androidx.fragment:fragment-ktx:1.8.9") implementation("androidx.appcompat:appcompat:1.7.1") implementation("com.google.android.material:material:1.13.0") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b6007ae845..2b4d2f3240 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -activityCompose = "1.11.0" +activityCompose = "1.12.0" agp = "8.13.1" camerax = "1.5.1" coilCompose = "2.7.0" -composeBom = "2025.11.00" +composeBom = "2025.11.01" composeNavigation = "2.9.6" coreKtx = "1.17.0" espressoCore = "3.7.0" @@ -13,7 +13,7 @@ junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.2.21" kotlinxSerializationCore = "1.9.0" -lifecycle = "2.9.4" +lifecycle = "2.10.0" material = "1.13.0" webkit = "1.14.0" diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index 71733f3c7a..d2f5046078 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation("androidx.core:core-ktx:1.17.0") // Required when asking for permission to post notifications (starting in Android 13) - implementation("androidx.activity:activity-ktx:1.11.0") + implementation("androidx.activity:activity-ktx:1.12.0") implementation("androidx.fragment:fragment-ktx:1.8.9") implementation("com.google.android.material:material:1.13.0") diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index a7ba050152..61e85131a4 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -71,7 +71,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") implementation("androidx.constraintlayout:constraintlayout:2.2.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.4") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0") implementation("com.github.bumptech.glide:glide:4.12.0") diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index e3edc64527..15b3c4a12c 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { // Firebase Authentication implementation("com.google.firebase:firebase-auth") - implementation("androidx.activity:activity-ktx:1.11.0") + implementation("androidx.activity:activity-ktx:1.12.0") implementation("androidx.appcompat:appcompat:1.7.1") implementation("com.google.android.material:material:1.13.0") androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") From 886ba060f096de715ed90a73b07b3370f8706ac7 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Thu, 4 Dec 2025 12:02:06 -0800 Subject: [PATCH 30/52] Auto-update dependencies. (#2737) --- auth/app/build.gradle.kts | 2 +- crash/app/build.gradle.kts | 2 +- firebase-ai/gradle/libs.versions.toml | 2 +- firestore/app/build.gradle.kts | 2 +- functions/app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 4 ++-- messaging/app/build.gradle.kts | 2 +- storage/app/build.gradle.kts | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index 0339aa0d5f..c6f59a859e 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -54,7 +54,7 @@ dependencies { implementation(project(":internal:lintchecks")) implementation("androidx.multidex:multidex:2.0.1") - implementation("androidx.activity:activity-ktx:1.12.0") + implementation("androidx.activity:activity-ktx:1.12.1") implementation("androidx.constraintlayout:constraintlayout:2.2.1") implementation("androidx.vectordrawable:vectordrawable-animated:1.2.0") implementation("com.google.android.material:material:1.13.0") diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index 81f4c019fe..bafa9ea7ce 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) implementation("com.google.android.material:material:1.13.0") - implementation("androidx.activity:activity-ktx:1.12.0") + implementation("androidx.activity:activity-ktx:1.12.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.6.0")) diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index a9d083ad8a..3f864821f8 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -activityCompose = "1.12.0" +activityCompose = "1.12.1" agp = "8.13.0" composeBom = "2025.10.00" composeNavigation = "2.9.6" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index 8e44a0f3c6..883713807a 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -74,7 +74,7 @@ dependencies { implementation("com.firebaseui:firebase-ui-auth:9.1.1") // Support Libs - implementation("androidx.activity:activity-ktx:1.12.0") + implementation("androidx.activity:activity-ktx:1.12.1") implementation("androidx.appcompat:appcompat:1.7.1") implementation("androidx.core:core-ktx:1.17.0") implementation("androidx.vectordrawable:vectordrawable-animated:1.2.0") diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index 81992db5a8..0d5382dfb1 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -49,7 +49,7 @@ dependencies { implementation(project(":internal:lintchecks")) implementation(project(":internal:chooserx")) - implementation("androidx.activity:activity-ktx:1.12.0") + implementation("androidx.activity:activity-ktx:1.12.1") implementation("androidx.fragment:fragment-ktx:1.8.9") implementation("androidx.appcompat:appcompat:1.7.1") implementation("com.google.android.material:material:1.13.0") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2b4d2f3240..dcc451e122 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -activityCompose = "1.12.0" +activityCompose = "1.12.1" agp = "8.13.1" camerax = "1.5.1" coilCompose = "2.7.0" -composeBom = "2025.11.01" +composeBom = "2025.12.00" composeNavigation = "2.9.6" coreKtx = "1.17.0" espressoCore = "3.7.0" diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index d2f5046078..2ac334512b 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation("androidx.core:core-ktx:1.17.0") // Required when asking for permission to post notifications (starting in Android 13) - implementation("androidx.activity:activity-ktx:1.12.0") + implementation("androidx.activity:activity-ktx:1.12.1") implementation("androidx.fragment:fragment-ktx:1.8.9") implementation("com.google.android.material:material:1.13.0") diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index 15b3c4a12c..5265790fa7 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { // Firebase Authentication implementation("com.google.firebase:firebase-auth") - implementation("androidx.activity:activity-ktx:1.12.0") + implementation("androidx.activity:activity-ktx:1.12.1") implementation("androidx.appcompat:appcompat:1.7.1") implementation("com.google.android.material:material:1.13.0") androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") From 0d7743203a78c0a0f2a67b1c147a3428067cc6ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:35:38 +0000 Subject: [PATCH 31/52] Bump jws in /functions/functions (#2738) Bumps and [jws](https://github.com/brianloveswords/node-jws). These dependencies needed to be updated together. Updates `jws` from 3.2.2 to 3.2.3 - [Release notes](https://github.com/brianloveswords/node-jws/releases) - [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianloveswords/node-jws/compare/v3.2.2...v3.2.3) Updates `jws` from 4.0.0 to 4.0.1 - [Release notes](https://github.com/brianloveswords/node-jws/releases) - [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianloveswords/node-jws/compare/v3.2.2...v3.2.3) --- updated-dependencies: - dependency-name: jws dependency-version: 3.2.3 dependency-type: indirect - dependency-name: jws dependency-version: 4.0.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- functions/functions/package-lock.json | 35 ++++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/functions/functions/package-lock.json b/functions/functions/package-lock.json index 7259e81915..1546e1c537 100644 --- a/functions/functions/package-lock.json +++ b/functions/functions/package-lock.json @@ -12,7 +12,7 @@ "firebase-functions": "^6.1.0" }, "engines": { - "node": "^22" + "node": "20" } }, "node_modules/@fastify/busboy": { @@ -1109,6 +1109,7 @@ "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.7.0.tgz", "integrity": "sha512-raFIrOyTqREbyXsNkSHyciQLfv8AUZazehPaQS1lZBSCDYW74FYXU0nQZa3qHI4K+hawohlDbywZ4+qce9YNxA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "1.0.8", @@ -1576,34 +1577,34 @@ } }, "node_modules/jsonwebtoken/node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jsonwebtoken/node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, "node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "optional": true, "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } @@ -1626,13 +1627,13 @@ } }, "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "optional": true, "dependencies": { - "jwa": "^2.0.0", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, From ba3f7bd16f9034d340764a5c444547bb04b474e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:36:14 +0000 Subject: [PATCH 32/52] Bump node-forge from 1.3.1 to 1.3.2 in /functions/functions (#2736) Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.1 to 1.3.2. - [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.2) --- updated-dependencies: - dependency-name: node-forge dependency-version: 1.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- functions/functions/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/functions/functions/package-lock.json b/functions/functions/package-lock.json index 1546e1c537..da3d2f0bd4 100644 --- a/functions/functions/package-lock.json +++ b/functions/functions/package-lock.json @@ -1822,9 +1822,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", + "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" From 4f5c325ecd00f165f9897d82dd30dd34266113ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gu=C3=A9rin?= Date: Mon, 8 Dec 2025 09:39:10 -0800 Subject: [PATCH 33/52] Update BidiViewModel.kt (#2740) Fix for fetchWeather function --- .../google/firebase/quickstart/ai/feature/live/BidiViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt index 3f36b81c3f..c1a632f75b 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt @@ -60,7 +60,7 @@ class BidiViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { val date = it.args["date"]?.jsonPrimitive?.content runBlocking { response = - if (!city.isNullOrEmpty() and !state.isNullOrEmpty() and date.isNullOrEmpty()) { + if (!city.isNullOrEmpty() and !state.isNullOrEmpty() and !date.isNullOrEmpty()) { fetchWeather(city!!, state!!, date!!) } else { JsonObject(emptyMap()) From 1315f9e16c0037f0751400bc6f8b3310ba2a098f Mon Sep 17 00:00:00 2001 From: DPEBot Date: Mon, 8 Dec 2025 14:55:44 -0800 Subject: [PATCH 34/52] Auto-update dependencies. (#2739) --- firebase-ai/gradle/libs.versions.toml | 2 +- gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index 3f864821f8..c2b10e6a38 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -14,7 +14,7 @@ lifecycle = "2.10.0" lifecycleRuntimeKtx = "2.9.4" material = "1.13.0" webkit = "1.14.0" -camerax = "1.5.1" +camerax = "1.5.2" [libraries] androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dcc451e122..081ede3655 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] activityCompose = "1.12.1" agp = "8.13.1" -camerax = "1.5.1" +camerax = "1.5.2" coilCompose = "2.7.0" composeBom = "2025.12.00" composeNavigation = "2.9.6" From 11fa18089899a062e7420494786f4dc711bddeb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Thu, 11 Dec 2025 03:57:11 +0000 Subject: [PATCH 35/52] refactor: use gemini-2.5 models for the Gemini Live quickstart (#2741) * refactor: use gemini-2.5 models for the Gemini Live quickstart * remove the backend check --- .../com/google/firebase/quickstart/ai/FirebaseAISamples.kt | 4 ---- .../firebase/quickstart/ai/feature/live/BidiViewModel.kt | 4 +++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index 3f6ceea9b2..f4661c1033 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -275,8 +275,6 @@ val FIREBASE_AI_SAMPLES = listOf( description = "Use bidirectional streaming to get information about" + " weather conditions for a specific US city on a specific date", navRoute = "stream", - backend = GenerativeBackend.vertexAI(), - modelName = "gemini-2.0-flash-live-preview-04-09", categories = listOf(Category.LIVE_API, Category.AUDIO, Category.FUNCTION_CALLING), tools = listOf( Tool.functionDeclarations( @@ -305,8 +303,6 @@ val FIREBASE_AI_SAMPLES = listOf( description = "Use bidirectional streaming to chat with Gemini using your" + " phone's camera", navRoute = "streamVideo", - backend = GenerativeBackend.vertexAI(), - modelName = "gemini-2.0-flash-live-preview-04-09", categories = listOf(Category.LIVE_API, Category.VIDEO, Category.FUNCTION_CALLING), tools = listOf( Tool.functionDeclarations( diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt index c1a632f75b..80589b18f0 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt @@ -45,7 +45,9 @@ class BidiViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { val liveModel = FirebaseAI.getInstance(Firebase.app, sample.backend) .liveModel( - modelName = sample.modelName ?: "gemini-live-2.5-flash", + // If you are using Vertex AI, change the model name to + // "gemini-live-2.5-flash-preview-native-audio-09-2025" + modelName = sample.modelName ?: "gemini-2.5-flash-native-audio-preview-09-2025", generationConfig = liveGenerationConfig, tools = sample.tools, ) From 60c6ce54a292070fa2b9ef226fefa7d4fd950e86 Mon Sep 17 00:00:00 2001 From: DPEBot Date: Fri, 12 Dec 2025 08:24:18 -0800 Subject: [PATCH 36/52] Auto-update dependencies. (#2742) --- admob/app/build.gradle.kts | 2 +- admob/build.gradle.kts | 4 ++-- analytics/app/build.gradle.kts | 2 +- analytics/build.gradle.kts | 4 ++-- appdistribution/app/build.gradle.kts | 2 +- appdistribution/build.gradle.kts | 4 ++-- auth/app/build.gradle.kts | 2 +- auth/build.gradle.kts | 4 ++-- build.gradle.kts | 4 ++-- config/app/build.gradle.kts | 2 +- config/build.gradle.kts | 4 ++-- crash/app/build.gradle.kts | 2 +- crash/build.gradle.kts | 4 ++-- database/app/build.gradle.kts | 2 +- database/build.gradle.kts | 4 ++-- firebase-ai/build.gradle.kts | 4 ++-- firebase-ai/gradle/libs.versions.toml | 2 +- firestore/app/build.gradle.kts | 2 +- firestore/build.gradle.kts | 4 ++-- functions/app/build.gradle.kts | 2 +- functions/build.gradle.kts | 4 ++-- gradle/libs.versions.toml | 4 ++-- inappmessaging/app/build.gradle.kts | 2 +- inappmessaging/build.gradle.kts | 4 ++-- internal/lint/build.gradle.kts | 6 +++--- messaging/app/build.gradle.kts | 2 +- messaging/build.gradle.kts | 4 ++-- perf/app/build.gradle.kts | 2 +- perf/build.gradle.kts | 4 ++-- storage/app/build.gradle.kts | 2 +- storage/build.gradle.kts | 4 ++-- 31 files changed, 49 insertions(+), 49 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index d4eb324a90..de961b22ed 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -61,7 +61,7 @@ dependencies { implementation("com.google.android.gms:play-services-ads:23.3.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // For an optimal experience using AdMob, add the Firebase SDK // for Google Analytics. This is recommended, but not required. diff --git a/admob/build.gradle.kts b/admob/build.gradle.kts index 4dace862a8..06b429a6b4 100644 --- a/admob/build.gradle.kts +++ b/admob/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index e22d1afd4e..f2e72793c5 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // Firebase Analytics implementation("com.google.firebase:firebase-analytics") diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts index 334b0c71a5..cf3c2c9ca4 100644 --- a/analytics/build.gradle.kts +++ b/analytics/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/appdistribution/app/build.gradle.kts b/appdistribution/app/build.gradle.kts index 94f81fc24d..42e795fbb0 100644 --- a/appdistribution/app/build.gradle.kts +++ b/appdistribution/app/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // ADD the SDK to the "prerelease" variant only (example) implementation("com.google.firebase:firebase-appdistribution:16.0.0-beta17") diff --git a/appdistribution/build.gradle.kts b/appdistribution/build.gradle.kts index 1da0dfd29d..6be8af03ed 100644 --- a/appdistribution/build.gradle.kts +++ b/appdistribution/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index c6f59a859e..f622c91e86 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -62,7 +62,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.6") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // Firebase Authentication implementation("com.google.firebase:firebase-auth") diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 1da0dfd29d..6be8af03ed 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/build.gradle.kts b/build.gradle.kts index 76a563a55f..915208b38b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false diff --git a/config/app/build.gradle.kts b/config/app/build.gradle.kts index f010eb48e1..09ddbca120 100644 --- a/config/app/build.gradle.kts +++ b/config/app/build.gradle.kts @@ -53,7 +53,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // Firebase Remote Config implementation("com.google.firebase:firebase-config") diff --git a/config/build.gradle.kts b/config/build.gradle.kts index 4dace862a8..06b429a6b4 100644 --- a/config/build.gradle.kts +++ b/config/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index bafa9ea7ce..3e3bdc6e01 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -60,7 +60,7 @@ dependencies { implementation("androidx.activity:activity-ktx:1.12.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // Firebase Crashlytics implementation("com.google.firebase:firebase-crashlytics") diff --git a/crash/build.gradle.kts b/crash/build.gradle.kts index 1ddce162ae..a0c5224c67 100644 --- a/crash/build.gradle.kts +++ b/crash/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index 2059770db8..84c8a3725d 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.6") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // Firebase Realtime Database implementation("com.google.firebase:firebase-database") diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 1da0dfd29d..6be8af03ed 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/firebase-ai/build.gradle.kts b/firebase-ai/build.gradle.kts index cabdaab156..5af44bffd0 100644 --- a/firebase-ai/build.gradle.kts +++ b/firebase-ai/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21" apply false diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index c2b10e6a38..db9d5ed20d 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -5,7 +5,7 @@ composeBom = "2025.10.00" composeNavigation = "2.9.6" coreKtx = "1.17.0" espressoCore = "3.7.0" -firebaseBom = "34.6.0" +firebaseBom = "34.7.0" junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.2.20" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index 883713807a..75b4562f90 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // Firestore implementation("com.google.firebase:firebase-firestore") diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index ff44b46b6f..06fed104ce 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("androidx.navigation.safeargs") version "2.9.6" apply false diff --git a/functions/app/build.gradle.kts b/functions/app/build.gradle.kts index 0d5382dfb1..6d09158dc1 100644 --- a/functions/app/build.gradle.kts +++ b/functions/app/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // Cloud Functions for Firebase implementation("com.google.firebase:firebase-functions") diff --git a/functions/build.gradle.kts b/functions/build.gradle.kts index 1da0dfd29d..6be8af03ed 100644 --- a/functions/build.gradle.kts +++ b/functions/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 081ede3655..4037bf73e6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,13 @@ [versions] activityCompose = "1.12.1" -agp = "8.13.1" +agp = "8.13.2" camerax = "1.5.2" coilCompose = "2.7.0" composeBom = "2025.12.00" composeNavigation = "2.9.6" coreKtx = "1.17.0" espressoCore = "3.7.0" -firebaseBom = "34.6.0" +firebaseBom = "34.7.0" googleServices = "4.4.4" junit = "4.13.2" junitVersion = "1.3.0" diff --git a/inappmessaging/app/build.gradle.kts b/inappmessaging/app/build.gradle.kts index 3c971653e2..550b2ee9fe 100644 --- a/inappmessaging/app/build.gradle.kts +++ b/inappmessaging/app/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // FIAM implementation("com.google.firebase:firebase-inappmessaging-display") diff --git a/inappmessaging/build.gradle.kts b/inappmessaging/build.gradle.kts index 1da0dfd29d..6be8af03ed 100644 --- a/inappmessaging/build.gradle.kts +++ b/inappmessaging/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/internal/lint/build.gradle.kts b/internal/lint/build.gradle.kts index bb16eb5ab5..2bdad9dfb0 100755 --- a/internal/lint/build.gradle.kts +++ b/internal/lint/build.gradle.kts @@ -9,8 +9,8 @@ java { } dependencies { - compileOnly("com.android.tools.lint:lint-api:31.13.1") - testImplementation("com.android.tools.lint:lint:31.13.1") - testImplementation("com.android.tools.lint:lint-tests:31.13.1") + compileOnly("com.android.tools.lint:lint-api:31.13.2") + testImplementation("com.android.tools.lint:lint:31.13.2") + testImplementation("com.android.tools.lint:lint-tests:31.13.2") testImplementation("junit:junit:4.13.2") } diff --git a/messaging/app/build.gradle.kts b/messaging/app/build.gradle.kts index 2ac334512b..fb4bcf9c44 100644 --- a/messaging/app/build.gradle.kts +++ b/messaging/app/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { implementation("com.google.android.material:material:1.13.0") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // Firebase Cloud Messaging implementation("com.google.firebase:firebase-messaging") diff --git a/messaging/build.gradle.kts b/messaging/build.gradle.kts index 4dace862a8..06b429a6b4 100644 --- a/messaging/build.gradle.kts +++ b/messaging/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index 61e85131a4..e04827406d 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -64,7 +64,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // Firebase Performance Monitoring implementation("com.google.firebase:firebase-perf") diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index 1111f2bb6a..9d241429eb 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.firebase-perf") version "2.0.2" apply false diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index 5265790fa7..476b42161e 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { implementation(project(":internal:chooserx")) // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) - implementation(platform("com.google.firebase:firebase-bom:34.6.0")) + implementation(platform("com.google.firebase:firebase-bom:34.7.0")) // Cloud Storage for Firebase implementation("com.google.firebase:firebase-storage") diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index 1da0dfd29d..6be8af03ed 100644 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.1" apply false - id("com.android.library") version "8.13.1" apply false + id("com.android.application") version "8.13.2" apply false + id("com.android.library") version "8.13.2" apply false id("org.jetbrains.kotlin.android") version "2.2.21" apply false id("com.google.gms.google-services") version "4.4.4" apply false } From 2cb62f17210a417cb07f005b675e45decc52047d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Mon, 15 Dec 2025 18:46:36 +0000 Subject: [PATCH 37/52] feat(ai): add a sample that demonstrates thinking (#2744) --- .../quickstart/ai/FirebaseAISamples.kt | 17 ++++- .../quickstart/ai/feature/text/ChatScreen.kt | 69 +++++++++++++++++-- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index f4661c1033..6923b359d8 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -8,6 +8,7 @@ import com.google.firebase.ai.type.Schema import com.google.firebase.ai.type.Tool import com.google.firebase.ai.type.content import com.google.firebase.ai.type.generationConfig +import com.google.firebase.ai.type.thinkingConfig import com.google.firebase.quickstart.ai.feature.media.imagen.EditingMode import com.google.firebase.quickstart.ai.ui.navigation.Category import com.google.firebase.quickstart.ai.ui.navigation.Sample @@ -393,5 +394,19 @@ val FIREBASE_AI_SAMPLES = listOf( templateId = "input-system-instructions", templateKey = "customerName" ), - + Sample( + title = "Thinking", + description = "Gemini 2.5 Flash with dynamic thinking", + navRoute = "chat", + categories = listOf(Category.TEXT), + initialPrompt = content { + text("Analogize photosynthesis and growing up.") + }, + generationConfig = generationConfig { + thinkingConfig = thinkingConfig { + includeThoughts = true + thinkingBudget = -1 // Dynamic Thinking + } + } + ) ) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt index f89bcba17a..ee169e1766 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt @@ -36,6 +36,10 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.filled.AttachFile import androidx.compose.material.icons.filled.Attachment +import androidx.compose.material.icons.filled.ExpandLess +import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.DropdownMenu @@ -234,11 +238,15 @@ fun ChatBubbleItem( message.content.parts.forEach { part -> when (part) { is TextPart -> { - Text( - text = part.text.trimIndent(), - modifier = Modifier.fillMaxWidth(), - color = textColor - ) + if (part.isThought) { + ThoughtBubble(part.text) + } else { + Text( + text = part.text.trimIndent(), + modifier = Modifier.fillMaxWidth(), + color = textColor + ) + } } is ImagePart -> { @@ -572,4 +580,55 @@ fun AttachmentsList( } } } +} + +@Composable +fun ThoughtBubble( + text: String +) { + var expanded by remember { mutableStateOf(false) } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .clip(RoundedCornerShape(8.dp)) + .background(MaterialTheme.colorScheme.tertiaryContainer) + .clickable { expanded = !expanded } + .padding(8.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Icon( + imageVector = if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore, + contentDescription = if (expanded) "Collapse thoughts" else "Expand thoughts", + modifier = Modifier.padding(end = 8.dp), + tint = MaterialTheme.colorScheme.onTertiaryContainer + ) + Text( + text = "Thoughts", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onTertiaryContainer + ) + } + + AnimatedVisibility(visible = expanded) { + Column { + HorizontalDivider( + modifier = Modifier.padding(vertical = 8.dp), + color = MaterialTheme.colorScheme.onTertiaryContainer + ) + Text( + text = text.trimIndent(), + style = MaterialTheme.typography.bodySmall.copy( + fontStyle = androidx.compose.ui.text.font.FontStyle.Italic + ), + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.onTertiaryContainer + ) + } + } + } } \ No newline at end of file From b3cc540808d30890db003ee15cab1d158a0c7371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Wed, 17 Dec 2025 19:05:09 +0000 Subject: [PATCH 38/52] feat(ai-logic): add a SVG generator quickstart that uses Gemini 3 Flash Preview (#2746) --- firebase-ai/app/build.gradle.kts | 5 + .../quickstart/ai/FirebaseAISamples.kt | 25 +++- .../firebase/quickstart/ai/MainActivity.kt | 9 +- .../quickstart/ai/feature/svg/SvgScreen.kt | 131 ++++++++++++++++++ .../quickstart/ai/feature/svg/SvgViewModel.kt | 72 ++++++++++ gradle/libs.versions.toml | 4 + 6 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgScreen.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgViewModel.kt diff --git a/firebase-ai/app/build.gradle.kts b/firebase-ai/app/build.gradle.kts index a3e1a55086..b070d8906d 100644 --- a/firebase-ai/app/build.gradle.kts +++ b/firebase-ai/app/build.gradle.kts @@ -81,6 +81,11 @@ dependencies { implementation(platform(libs.firebase.bom)) implementation(libs.firebase.ai) + // Image loading + implementation(libs.coil3.coil.compose) + implementation(libs.coil.network.okhttp) + implementation(libs.coil.svg) + testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt index 6923b359d8..c58ae508f6 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt @@ -408,5 +408,28 @@ val FIREBASE_AI_SAMPLES = listOf( thinkingBudget = -1 // Dynamic Thinking } } - ) + ), + Sample( + title = "SVG Generator", + description = "Use Gemini 3 Flash preview to create SVG illustrations", + navRoute = "svg", + categories = listOf(Category.IMAGE, Category.TEXT), + initialPrompt = content { + text( + "a kitten" + ) + }, + generationConfig = generationConfig { + thinkingConfig { + thinkingBudget = -1 + } + }, + systemInstructions = content { text(""" + You are an expert at turning image prompts into SVG code. When given a prompt, + use your creativity to code a 800x600 SVG rendering of it. + Always add viewBox="0 0 800 600" to the root svg tag. Do + not import external assets, they won't work. Return ONLY the SVG code, nothing else, + no commentary. + """.trimIndent()) } + ), ) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt index 6848d08291..e3e37064bc 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt @@ -36,6 +36,8 @@ import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoRoute import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoScreen import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenRoute import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenScreen +import com.google.firebase.quickstart.ai.feature.svg.SvgRoute +import com.google.firebase.quickstart.ai.feature.svg.SvgScreen import com.google.firebase.quickstart.ai.feature.text.ChatRoute import com.google.firebase.quickstart.ai.feature.text.ChatScreen import com.google.firebase.quickstart.ai.feature.text.TextGenRoute @@ -47,7 +49,6 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - enableEdgeToEdge() catImage = BitmapFactory.decodeResource(applicationContext.resources, R.drawable.cat) setContent { @@ -98,6 +99,9 @@ class MainActivity : ComponentActivity() { "text" -> { navController.navigate(TextGenRoute(it.id)) } + "svg" -> { + navController.navigate(SvgRoute(it.id)) + } } } ) @@ -125,6 +129,9 @@ class MainActivity : ComponentActivity() { composable { TextGenScreen() } + composable { + SvgScreen() + } } } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgScreen.kt new file mode 100644 index 0000000000..be745faec2 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgScreen.kt @@ -0,0 +1,131 @@ +package com.google.firebase.quickstart.ai.feature.svg + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import coil3.compose.SubcomposeAsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade +import coil3.svg.SvgDecoder +import kotlinx.coroutines.Dispatchers +import kotlinx.serialization.Serializable +import java.nio.ByteBuffer + +@Serializable +class SvgRoute(val sampleId: String) + +@Composable +fun SvgScreen( + svgViewModel: SvgViewModel = viewModel() +) { + var prompt by rememberSaveable { mutableStateOf(svgViewModel.initialPrompt) } + val errorMessage by svgViewModel.errorMessage.collectAsStateWithLifecycle() + val isLoading by svgViewModel.isLoading.collectAsStateWithLifecycle() + val generatedSvgs by svgViewModel.generatedSvgs.collectAsStateWithLifecycle() + + Column { + ElevatedCard( + modifier = Modifier + .padding(all = 16.dp) + .fillMaxWidth(), + shape = MaterialTheme.shapes.large + ) { + OutlinedTextField( + value = prompt, + label = { Text("Generate a SVG of") }, + placeholder = { Text("Enter text to generate image") }, + onValueChange = { prompt = it }, + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + ) + TextButton( + onClick = { + svgViewModel.generateSVG(prompt) + }, + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 16.dp) + .align(Alignment.End) + ) { + Text("Generate") + } + } + if (isLoading) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .padding(all = 8.dp) + .align(Alignment.CenterHorizontally) + ) { + CircularProgressIndicator() + } + } + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + items(generatedSvgs) { svg -> + Card( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 8.dp) + .fillMaxWidth(), + shape = MaterialTheme.shapes.large, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.onSecondaryContainer + ) + ) { + SubcomposeAsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(ByteBuffer.wrap(svg.toByteArray())) + .decoderFactory(SvgDecoder.Factory()) + .decoderCoroutineContext(Dispatchers.Main) + .crossfade(true) + .build(), + contentDescription = "Generated SVG", + modifier = Modifier + .fillMaxWidth() + ) + } + } + } + errorMessage?.let { + Card( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + shape = MaterialTheme.shapes.large, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer + ) + ) { + Text( + text = it, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(all = 16.dp) + ) + } + } + } +} \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgViewModel.kt new file mode 100644 index 0000000000..4ad04b3bdf --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgViewModel.kt @@ -0,0 +1,72 @@ +package com.google.firebase.quickstart.ai.feature.svg + +import androidx.compose.runtime.mutableStateListOf +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import com.google.firebase.Firebase +import com.google.firebase.ai.GenerativeModel +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.TextPart +import com.google.firebase.ai.type.asTextOrNull +import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES +import com.google.firebase.quickstart.ai.feature.text.ChatRoute +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class SvgViewModel( + savedStateHandle: SavedStateHandle +) : ViewModel() { + private val sampleId = savedStateHandle.toRoute().sampleId + private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } + val initialPrompt: String = + sample.initialPrompt?.parts + ?.filterIsInstance() + ?.first() + ?.asTextOrNull().orEmpty() + + private val _isLoading = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading + + private val _errorMessage = MutableStateFlow(null) + val errorMessage: StateFlow = _errorMessage + + private val _generatedSvgList = mutableStateListOf() + val generatedSvgs: StateFlow> = + MutableStateFlow>(_generatedSvgList) + + private val generativeModel: GenerativeModel + + init { + generativeModel = Firebase.ai( + backend = sample.backend + ).generativeModel( + modelName = sample.modelName ?: "gemini-3-flash-preview", + systemInstruction = sample.systemInstructions, + generationConfig = sample.generationConfig, + tools = sample.tools + ) + } + + fun generateSVG(prompt: String) { + _isLoading.value = true + viewModelScope.launch(Dispatchers.IO) { + try { + val response = generativeModel.generateContent(prompt) + response.text?.let { + _generatedSvgList.add(0, it) + } + _errorMessage.value = null + } catch (e: Exception) { + _errorMessage.value = e.localizedMessage + } finally { + _isLoading.value = false + } + } + + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4037bf73e6..775ae3fed3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ activityCompose = "1.12.1" agp = "8.13.2" camerax = "1.5.2" coilCompose = "2.7.0" +coil3Compose = "3.3.0" composeBom = "2025.12.00" composeNavigation = "2.9.6" coreKtx = "1.17.0" @@ -44,6 +45,9 @@ androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" } +coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil3Compose" } +coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil3Compose" } +coil3-coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3Compose" } compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"} firebase-ai = { module = "com.google.firebase:firebase-ai" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } From 662b8f3e76f321bffa0ac7f993caf7245759f52c Mon Sep 17 00:00:00 2001 From: DPEBot Date: Wed, 17 Dec 2025 11:05:34 -0800 Subject: [PATCH 39/52] Auto-update dependencies. (#2745) --- admob/build.gradle.kts | 2 +- analytics/build.gradle.kts | 2 +- appdistribution/build.gradle.kts | 2 +- auth/build.gradle.kts | 2 +- build.gradle.kts | 4 ++-- config/build.gradle.kts | 2 +- crash/build.gradle.kts | 2 +- database/build.gradle.kts | 2 +- firebase-ai/build.gradle.kts | 6 +++--- firestore/build.gradle.kts | 2 +- functions/build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- inappmessaging/build.gradle.kts | 2 +- messaging/build.gradle.kts | 2 +- perf/build.gradle.kts | 2 +- storage/build.gradle.kts | 2 +- 16 files changed, 19 insertions(+), 19 deletions(-) diff --git a/admob/build.gradle.kts b/admob/build.gradle.kts index 06b429a6b4..ca75fa0a37 100644 --- a/admob/build.gradle.kts +++ b/admob/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts index cf3c2c9ca4..fbfdd184ae 100644 --- a/analytics/build.gradle.kts +++ b/analytics/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/appdistribution/build.gradle.kts b/appdistribution/build.gradle.kts index 6be8af03ed..5e5a82e697 100644 --- a/appdistribution/build.gradle.kts +++ b/appdistribution/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 6be8af03ed..5e5a82e697 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/build.gradle.kts b/build.gradle.kts index 915208b38b..54f53af0bc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,13 +3,13 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false id("com.google.firebase.firebase-perf") version "2.0.2" apply false id("androidx.navigation.safeargs") version "2.9.6" apply false id("com.github.ben-manes.versions") version "0.53.0" apply true - id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false + id("org.jetbrains.kotlin.plugin.compose") version "2.3.0" apply false } allprojects { diff --git a/config/build.gradle.kts b/config/build.gradle.kts index 06b429a6b4..ca75fa0a37 100644 --- a/config/build.gradle.kts +++ b/config/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/crash/build.gradle.kts b/crash/build.gradle.kts index a0c5224c67..a9402189dc 100644 --- a/crash/build.gradle.kts +++ b/crash/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.crashlytics") version "3.0.6" apply false } diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 6be8af03ed..5e5a82e697 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/firebase-ai/build.gradle.kts b/firebase-ai/build.gradle.kts index 5af44bffd0..9d63a766ba 100644 --- a/firebase-ai/build.gradle.kts +++ b/firebase-ai/build.gradle.kts @@ -3,8 +3,8 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false - id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false - id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false + id("org.jetbrains.kotlin.plugin.compose") version "2.3.0" apply false + id("org.jetbrains.kotlin.plugin.serialization") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index 06fed104ce..d5a3155131 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("androidx.navigation.safeargs") version "2.9.6" apply false } diff --git a/functions/build.gradle.kts b/functions/build.gradle.kts index 6be8af03ed..5e5a82e697 100644 --- a/functions/build.gradle.kts +++ b/functions/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 775ae3fed3..3c04547d46 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ firebaseBom = "34.7.0" googleServices = "4.4.4" junit = "4.13.2" junitVersion = "1.3.0" -kotlin = "2.2.21" +kotlin = "2.3.0" kotlinxSerializationCore = "1.9.0" lifecycle = "2.10.0" material = "1.13.0" diff --git a/inappmessaging/build.gradle.kts b/inappmessaging/build.gradle.kts index 6be8af03ed..5e5a82e697 100644 --- a/inappmessaging/build.gradle.kts +++ b/inappmessaging/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/messaging/build.gradle.kts b/messaging/build.gradle.kts index 06b429a6b4..ca75fa0a37 100644 --- a/messaging/build.gradle.kts +++ b/messaging/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index 9d241429eb..dc54e53405 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.firebase-perf") version "2.0.2" apply false } diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index 6be8af03ed..5e5a82e697 100644 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.2.21" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } From 60c0e671a3291405a4915ef19438a1820fdd097e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Wed, 28 Jan 2026 00:32:46 +0000 Subject: [PATCH 40/52] chore(*): upgrade to AGP 9.0.0 (#2753) --- admob/app/build.gradle.kts | 9 +-------- admob/build.gradle.kts | 1 - admob/gradle/wrapper/gradle-wrapper.properties | 2 +- analytics/app/build.gradle.kts | 10 +--------- analytics/build.gradle.kts | 1 - analytics/gradle/wrapper/gradle-wrapper.properties | 2 +- appdistribution/app/build.gradle.kts | 10 +--------- appdistribution/app/src/main/AndroidManifest.xml | 1 - appdistribution/build.gradle.kts | 1 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- auth/app/build.gradle.kts | 10 +--------- auth/app/src/main/AndroidManifest.xml | 1 - auth/build.gradle.kts | 1 - auth/gradle/wrapper/gradle-wrapper.properties | 2 +- build.gradle.kts | 1 - config/app/build.gradle.kts | 9 +-------- config/build.gradle.kts | 1 - config/gradle/wrapper/gradle-wrapper.properties | 2 +- crash/app/build.gradle.kts | 14 +++----------- crash/build.gradle.kts | 1 - crash/gradle/wrapper/gradle-wrapper.properties | 2 +- database/app/build.gradle.kts | 9 +-------- database/build.gradle.kts | 1 - database/gradle/wrapper/gradle-wrapper.properties | 2 +- dataconnect/app/build.gradle.kts | 4 ---- dataconnect/build.gradle.kts | 1 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- firebase-ai/app/build.gradle.kts | 7 ------- firebase-ai/build.gradle.kts | 1 - firebase-ai/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- firestore/app/build.gradle.kts | 12 ++---------- firestore/app/src/main/AndroidManifest.xml | 3 +-- firestore/build.gradle.kts | 1 - firestore/gradle/wrapper/gradle-wrapper.properties | 2 +- functions/app/build.gradle.kts | 11 ++--------- functions/build.gradle.kts | 1 - functions/gradle/wrapper/gradle-wrapper.properties | 2 +- gradle.properties | 1 - gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- inappmessaging/app/build.gradle.kts | 10 +--------- inappmessaging/app/src/main/AndroidManifest.xml | 1 - inappmessaging/build.gradle.kts | 1 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- internal/chooserx/build.gradle.kts | 2 +- messaging/app/build.gradle.kts | 9 +-------- messaging/build.gradle.kts | 1 - messaging/gradle/wrapper/gradle-wrapper.properties | 2 +- perf/app/build.gradle.kts | 9 +-------- perf/build.gradle.kts | 1 - perf/gradle/wrapper/gradle-wrapper.properties | 2 +- storage/app/build.gradle.kts | 9 +-------- storage/build.gradle.kts | 1 - storage/gradle/wrapper/gradle-wrapper.properties | 2 +- 55 files changed, 37 insertions(+), 166 deletions(-) diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index de961b22ed..c98d99c038 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -1,9 +1,7 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn -import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { id("com.android.application") - id("kotlin-android") id("com.google.gms.google-services") } @@ -29,7 +27,7 @@ android { buildTypes { getByName("release") { isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } packaging { @@ -39,11 +37,6 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_17 - } - } buildFeatures { viewBinding = true } diff --git a/admob/build.gradle.kts b/admob/build.gradle.kts index ca75fa0a37..727b81933c 100644 --- a/admob/build.gradle.kts +++ b/admob/build.gradle.kts @@ -3,7 +3,6 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/admob/gradle/wrapper/gradle-wrapper.properties b/admob/gradle/wrapper/gradle-wrapper.properties index d30212c04b..5dc98dbcf3 100644 --- a/admob/gradle/wrapper/gradle-wrapper.properties +++ b/admob/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index f2e72793c5..cc51ce5263 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -1,9 +1,7 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn -import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { id("com.android.application") - id("kotlin-android") id("com.google.gms.google-services") } @@ -28,7 +26,7 @@ android { buildTypes { getByName("release") { isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } @@ -37,12 +35,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_17 - } - } - buildFeatures { viewBinding = true } diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts index fbfdd184ae..1f20b7ccc0 100644 --- a/analytics/build.gradle.kts +++ b/analytics/build.gradle.kts @@ -3,7 +3,6 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/analytics/gradle/wrapper/gradle-wrapper.properties b/analytics/gradle/wrapper/gradle-wrapper.properties index d30212c04b..5dc98dbcf3 100644 --- a/analytics/gradle/wrapper/gradle-wrapper.properties +++ b/analytics/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/appdistribution/app/build.gradle.kts b/appdistribution/app/build.gradle.kts index 42e795fbb0..9a9632e83c 100644 --- a/appdistribution/app/build.gradle.kts +++ b/appdistribution/app/build.gradle.kts @@ -1,8 +1,6 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { id("com.android.application") - id("kotlin-android") id("com.google.gms.google-services") } @@ -24,18 +22,13 @@ android { buildTypes { getByName("release") { isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_17 - } - } buildFeatures { viewBinding = true } @@ -54,7 +47,6 @@ dependencies { implementation("com.google.android.material:material:1.13.0") implementation("androidx.constraintlayout:constraintlayout:2.2.1") - implementation("androidx.multidex:multidex:2.0.1") // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) implementation(platform("com.google.firebase:firebase-bom:34.7.0")) diff --git a/appdistribution/app/src/main/AndroidManifest.xml b/appdistribution/app/src/main/AndroidManifest.xml index a29b19adf7..a5455f3394 100644 --- a/appdistribution/app/src/main/AndroidManifest.xml +++ b/appdistribution/app/src/main/AndroidManifest.xml @@ -2,7 +2,6 @@ + android:theme="@style/AppTheme"> { @@ -44,11 +42,6 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_17 - } - } buildFeatures { viewBinding = true } diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index dc54e53405..f470cdb6d8 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -3,7 +3,6 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.firebase.firebase-perf") version "2.0.2" apply false } diff --git a/perf/gradle/wrapper/gradle-wrapper.properties b/perf/gradle/wrapper/gradle-wrapper.properties index d30212c04b..5dc98dbcf3 100644 --- a/perf/gradle/wrapper/gradle-wrapper.properties +++ b/perf/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/storage/app/build.gradle.kts b/storage/app/build.gradle.kts index 476b42161e..c003fbf439 100644 --- a/storage/app/build.gradle.kts +++ b/storage/app/build.gradle.kts @@ -1,9 +1,7 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn -import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { id("com.android.application") - id("kotlin-android") id("com.google.gms.google-services") } @@ -28,18 +26,13 @@ android { buildTypes { getByName("release") { isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_17 - } - } buildFeatures { viewBinding = true diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index 5e5a82e697..a042a1fbf0 100644 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -3,7 +3,6 @@ plugins { id("com.android.application") version "8.13.2" apply false id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version "4.4.4" apply false } diff --git a/storage/gradle/wrapper/gradle-wrapper.properties b/storage/gradle/wrapper/gradle-wrapper.properties index d30212c04b..5dc98dbcf3 100644 --- a/storage/gradle/wrapper/gradle-wrapper.properties +++ b/storage/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From b6575995e3c7b1b68f9b4fcbb28f9a1ff3754424 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Date: Mon, 23 Feb 2026 18:43:36 -0500 Subject: [PATCH 41/52] Add Coil image loading dependencies to firebase-ai (#2758) Introduces Coil 2 and Coil 3 dependencies for image loading. This includes `coil-compose` for Coil 2, and `coil-compose`, `coil-network-okhttp`, and `coil-svg` for Coil 3. While these deps exist in the root `gradle/libs.versions.toml`, they do not exist in the `firebase-ai/gradle/libs.versions.toml` file, which is necessary to build the firebase-ai quickstart app. --- firebase-ai/gradle/libs.versions.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml index af3dbc442a..f8ab0d657b 100644 --- a/firebase-ai/gradle/libs.versions.toml +++ b/firebase-ai/gradle/libs.versions.toml @@ -1,6 +1,8 @@ [versions] activityCompose = "1.12.1" agp = "9.0.0" +coilCompose = "2.7.0" +coil3Compose = "3.3.0" composeBom = "2025.10.00" composeNavigation = "2.9.6" coreKtx = "1.17.0" @@ -36,6 +38,10 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } +coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" } +coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil3Compose" } +coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil3Compose" } +coil3-coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3Compose" } compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"} firebase-ai = { module = "com.google.firebase:firebase-ai" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } From d29b1979f37366b618ccf4fe85e5d94be9a55cfb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:55:38 +0000 Subject: [PATCH 42/52] chore(deps): bump fast-xml-parser and @google-cloud/storage (#2757) Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) and [@google-cloud/storage](https://github.com/googleapis/nodejs-storage). These dependencies needed to be updated together. Updates `fast-xml-parser` from 4.5.0 to 5.3.6 - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v4.5.0...v5.3.6) Updates `@google-cloud/storage` from 7.14.0 to 7.19.0 - [Release notes](https://github.com/googleapis/nodejs-storage/releases) - [Changelog](https://github.com/googleapis/nodejs-storage/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/nodejs-storage/compare/v7.14.0...v7.19.0) --- updated-dependencies: - dependency-name: fast-xml-parser dependency-version: 5.3.6 dependency-type: indirect - dependency-name: "@google-cloud/storage" dependency-version: 7.19.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- functions/functions/package-lock.json | 35 ++++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/functions/functions/package-lock.json b/functions/functions/package-lock.json index da3d2f0bd4..89a6e0d684 100644 --- a/functions/functions/package-lock.json +++ b/functions/functions/package-lock.json @@ -158,19 +158,19 @@ } }, "node_modules/@google-cloud/storage": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.14.0.tgz", - "integrity": "sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz", + "integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "@google-cloud/paginator": "^5.0.0", "@google-cloud/projectify": "^4.0.0", - "@google-cloud/promisify": "^4.0.0", + "@google-cloud/promisify": "<4.1.0", "abort-controller": "^3.0.0", "async-retry": "^1.3.3", "duplexify": "^4.1.3", - "fast-xml-parser": "^4.4.1", + "fast-xml-parser": "^5.3.4", "gaxios": "^6.0.2", "google-auth-library": "^9.6.3", "html-entities": "^2.5.2", @@ -1037,23 +1037,19 @@ "optional": true }, "node_modules/fast-xml-parser": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", - "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", + "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" } ], "license": "MIT", "optional": true, "dependencies": { - "strnum": "^1.0.5" + "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" @@ -1109,7 +1105,6 @@ "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.7.0.tgz", "integrity": "sha512-raFIrOyTqREbyXsNkSHyciQLfv8AUZazehPaQS1lZBSCDYW74FYXU0nQZa3qHI4K+hawohlDbywZ4+qce9YNxA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "1.0.8", @@ -2270,9 +2265,15 @@ } }, "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", "optional": true }, From 519fa2686c3eb45309c18a11db27981ce55f6535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Tue, 3 Mar 2026 23:14:03 +0000 Subject: [PATCH 43/52] chore(*): centralize build plugins in a single libs.version.toml file (#2764) This should: - Update the build.gradle.kts files to use plugins from the Gradle Version Catalog (libs.versions.toml) - Delete the libs.version.toml file from the AI Logic quickstart and configure it to use the top-level one --- admob/app/build.gradle.kts | 4 +- admob/build.gradle.kts | 6 +-- analytics/app/build.gradle.kts | 4 +- analytics/build.gradle.kts | 6 +-- appdistribution/build.gradle.kts | 6 +-- auth/app/build.gradle.kts | 4 +- auth/build.gradle.kts | 6 +-- build.gradle.kts | 16 +++---- config/app/build.gradle.kts | 4 +- config/build.gradle.kts | 6 +-- crash/app/build.gradle.kts | 6 +-- crash/build.gradle.kts | 8 ++-- database/app/build.gradle.kts | 4 +- database/build.gradle.kts | 6 +-- firebase-ai/build.gradle.kts | 10 ++--- firebase-ai/gradle/libs.versions.toml | 60 --------------------------- firebase-ai/settings.gradle.kts | 7 ++++ firestore/app/build.gradle.kts | 6 +-- firestore/build.gradle.kts | 8 ++-- functions/build.gradle.kts | 6 +-- gradle/libs.versions.toml | 8 ++++ inappmessaging/build.gradle.kts | 6 +-- messaging/build.gradle.kts | 6 +-- perf/app/build.gradle.kts | 15 +++---- perf/build.gradle.kts | 8 ++-- storage/build.gradle.kts | 6 +-- 26 files changed, 91 insertions(+), 141 deletions(-) delete mode 100644 firebase-ai/gradle/libs.versions.toml diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts index c98d99c038..1a1c031306 100644 --- a/admob/app/build.gradle.kts +++ b/admob/app/build.gradle.kts @@ -1,8 +1,8 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn plugins { - id("com.android.application") - id("com.google.gms.google-services") + alias(libs.plugins.android.application) + alias(libs.plugins.google.services) } tasks { diff --git a/admob/build.gradle.kts b/admob/build.gradle.kts index 727b81933c..09575b4178 100644 --- a/admob/build.gradle.kts +++ b/admob/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false } allprojects { diff --git a/analytics/app/build.gradle.kts b/analytics/app/build.gradle.kts index cc51ce5263..a393f2a39e 100644 --- a/analytics/app/build.gradle.kts +++ b/analytics/app/build.gradle.kts @@ -1,8 +1,8 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn plugins { - id("com.android.application") - id("com.google.gms.google-services") + alias(libs.plugins.android.application) + alias(libs.plugins.google.services) } tasks { diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts index 1f20b7ccc0..013f300b72 100644 --- a/analytics/build.gradle.kts +++ b/analytics/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false } allprojects { diff --git a/appdistribution/build.gradle.kts b/appdistribution/build.gradle.kts index a042a1fbf0..81a82fd0d4 100644 --- a/appdistribution/build.gradle.kts +++ b/appdistribution/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false } allprojects { diff --git a/auth/app/build.gradle.kts b/auth/app/build.gradle.kts index 419d85ae96..94dc586499 100644 --- a/auth/app/build.gradle.kts +++ b/auth/app/build.gradle.kts @@ -1,8 +1,8 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn plugins { - id("com.android.application") - id("com.google.gms.google-services") + alias(libs.plugins.android.application) + alias(libs.plugins.google.services) } tasks { diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index a042a1fbf0..81a82fd0d4 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false } allprojects { diff --git a/build.gradle.kts b/build.gradle.kts index 47c25adc34..0b0bb5f37b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,14 +1,14 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false - id("com.google.firebase.crashlytics") version "3.0.6" apply false - id("com.google.firebase.firebase-perf") version "2.0.2" apply false - id("androidx.navigation.safeargs") version "2.9.6" apply false - id("com.github.ben-manes.versions") version "0.53.0" apply true - id("org.jetbrains.kotlin.plugin.compose") version "2.3.0" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false + alias(libs.plugins.firebase.crashlytics) apply false + alias(libs.plugins.firebase.perf) apply false + alias(libs.plugins.navigation.safeargs) apply false + alias(libs.plugins.gradle.versions) apply true + alias(libs.plugins.compose.compiler) apply false } allprojects { diff --git a/config/app/build.gradle.kts b/config/app/build.gradle.kts index a5b92f0c01..a2a492fe42 100644 --- a/config/app/build.gradle.kts +++ b/config/app/build.gradle.kts @@ -1,8 +1,8 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn plugins { - id("com.android.application") - id("com.google.gms.google-services") + alias(libs.plugins.android.application) + alias(libs.plugins.google.services) } tasks { diff --git a/config/build.gradle.kts b/config/build.gradle.kts index 727b81933c..09575b4178 100644 --- a/config/build.gradle.kts +++ b/config/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false } allprojects { diff --git a/crash/app/build.gradle.kts b/crash/app/build.gradle.kts index a0137d7f10..920dc73dcd 100644 --- a/crash/app/build.gradle.kts +++ b/crash/app/build.gradle.kts @@ -1,9 +1,9 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn plugins { - id("com.android.application") - id("com.google.gms.google-services") - id("com.google.firebase.crashlytics") + alias(libs.plugins.android.application) + alias(libs.plugins.google.services) + alias(libs.plugins.firebase.crashlytics) } tasks { diff --git a/crash/build.gradle.kts b/crash/build.gradle.kts index 07ff6e0dbc..40b3246d65 100644 --- a/crash/build.gradle.kts +++ b/crash/build.gradle.kts @@ -1,10 +1,10 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false - id("com.google.firebase.crashlytics") version "3.0.6" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false + alias(libs.plugins.firebase.crashlytics) apply false } allprojects { diff --git a/database/app/build.gradle.kts b/database/app/build.gradle.kts index 9a7d3c5e62..bd877f9edb 100644 --- a/database/app/build.gradle.kts +++ b/database/app/build.gradle.kts @@ -1,8 +1,8 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn plugins { - id("com.android.application") - id("com.google.gms.google-services") + alias(libs.plugins.android.application) + alias(libs.plugins.google.services) } tasks { diff --git a/database/build.gradle.kts b/database/build.gradle.kts index a042a1fbf0..81a82fd0d4 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false } allprojects { diff --git a/firebase-ai/build.gradle.kts b/firebase-ai/build.gradle.kts index 3256a9f3b2..11ea2d1c62 100644 --- a/firebase-ai/build.gradle.kts +++ b/firebase-ai/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("org.jetbrains.kotlin.plugin.compose") version "2.3.0" apply false - id("org.jetbrains.kotlin.plugin.serialization") version "2.3.0" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.google.services) apply false } diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml deleted file mode 100644 index f8ab0d657b..0000000000 --- a/firebase-ai/gradle/libs.versions.toml +++ /dev/null @@ -1,60 +0,0 @@ -[versions] -activityCompose = "1.12.1" -agp = "9.0.0" -coilCompose = "2.7.0" -coil3Compose = "3.3.0" -composeBom = "2025.10.00" -composeNavigation = "2.9.6" -coreKtx = "1.17.0" -espressoCore = "3.7.0" -firebaseBom = "34.7.0" -junit = "4.13.2" -junitVersion = "1.3.0" -kotlin = "2.2.20" -kotlinxSerializationCore = "1.9.0" -lifecycle = "2.10.0" -lifecycleRuntimeKtx = "2.9.4" -material = "1.13.0" -webkit = "1.14.0" -camerax = "1.5.2" - -[libraries] -androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } -androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } -androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } -androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } -androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } -androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" } -androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } -androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" } -androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } -androidx-material3 = { group = "androidx.compose.material3", name = "material3" } -androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite" } -androidx-ui = { group = "androidx.compose.ui", name = "ui" } -androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } -androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } -androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } -androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } -androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } -androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } -coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" } -coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil3Compose" } -coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil3Compose" } -coil3-coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3Compose" } -compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"} -firebase-ai = { module = "com.google.firebase:firebase-ai" } -firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } -junit = { group = "junit", name = "junit", version.ref = "junit" } -kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationCore" } -material = { module = "com.google.android.material:material", version.ref = "material" } -androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" } -androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "camerax" } -androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "camerax" } -androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" } -androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "camerax" } - -[plugins] -android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/firebase-ai/settings.gradle.kts b/firebase-ai/settings.gradle.kts index 26668b9361..321397594b 100644 --- a/firebase-ai/settings.gradle.kts +++ b/firebase-ai/settings.gradle.kts @@ -18,6 +18,13 @@ dependencyResolutionManagement { google() mavenCentral() } + dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } + } } rootProject.name = "Firebase AI Logic" diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index 52fdc7869d..50c151923a 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -1,8 +1,8 @@ plugins { - id("com.android.application") - id("com.google.gms.google-services") - id("androidx.navigation.safeargs") + alias(libs.plugins.android.application) + alias(libs.plugins.google.services) + alias(libs.plugins.navigation.safeargs) } android { diff --git a/firestore/build.gradle.kts b/firestore/build.gradle.kts index c69b056a6c..8479eb52d7 100644 --- a/firestore/build.gradle.kts +++ b/firestore/build.gradle.kts @@ -1,10 +1,10 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false - id("androidx.navigation.safeargs") version "2.9.6" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false + alias(libs.plugins.navigation.safeargs) apply false } allprojects { diff --git a/functions/build.gradle.kts b/functions/build.gradle.kts index a042a1fbf0..81a82fd0d4 100644 --- a/functions/build.gradle.kts +++ b/functions/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false } allprojects { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b99548b0f2..9b50ae8687 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,9 @@ coreKtx = "1.17.0" espressoCore = "3.7.0" firebaseBom = "34.7.0" googleServices = "4.4.4" +firebaseCrashlytics = "3.0.6" +firebasePerf = "2.0.2" +gradleVersions = "0.53.0" junit = "4.13.2" junitVersion = "1.3.0" kotlin = "2.3.0" @@ -58,7 +61,12 @@ material = { module = "com.google.android.material:material", version.ref = "mat [plugins] android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlytics" } +firebase-perf = { id = "com.google.firebase.firebase-perf", version.ref = "firebasePerf" } +navigation-safeargs = { id = "androidx.navigation.safeargs", version.ref = "composeNavigation" } +gradle-versions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" } diff --git a/inappmessaging/build.gradle.kts b/inappmessaging/build.gradle.kts index a042a1fbf0..81a82fd0d4 100644 --- a/inappmessaging/build.gradle.kts +++ b/inappmessaging/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false } allprojects { diff --git a/messaging/build.gradle.kts b/messaging/build.gradle.kts index 727b81933c..09575b4178 100644 --- a/messaging/build.gradle.kts +++ b/messaging/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false } allprojects { diff --git a/perf/app/build.gradle.kts b/perf/app/build.gradle.kts index 9574ee2788..87d2aa5079 100644 --- a/perf/app/build.gradle.kts +++ b/perf/app/build.gradle.kts @@ -1,14 +1,8 @@ -import com.android.build.gradle.internal.tasks.factory.dependsOn -import com.google.firebase.perf.plugin.FirebasePerfExtension plugins { - id("com.android.application") - id("com.google.gms.google-services") - id("com.google.firebase.firebase-perf") -} - -tasks { - check.dependsOn("assembleDebugAndroidTest") + alias(libs.plugins.android.application) + alias(libs.plugins.google.services) + alias(libs.plugins.firebase.perf) } android { @@ -21,9 +15,9 @@ android { targetSdk = 36 versionCode = 1 versionName = "1.0" - multiDexEnabled = true testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + buildTypes { getByName("release") { isMinifyEnabled = false @@ -42,6 +36,7 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + buildFeatures { viewBinding = true } diff --git a/perf/build.gradle.kts b/perf/build.gradle.kts index f470cdb6d8..e4fcb4d0b1 100644 --- a/perf/build.gradle.kts +++ b/perf/build.gradle.kts @@ -1,10 +1,10 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false - id("com.google.firebase.firebase-perf") version "2.0.2" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false + alias(libs.plugins.firebase.perf) apply false } allprojects { diff --git a/storage/build.gradle.kts b/storage/build.gradle.kts index a042a1fbf0..81a82fd0d4 100644 --- a/storage/build.gradle.kts +++ b/storage/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.13.2" apply false - id("com.android.library") version "8.13.2" apply false - id("com.google.gms.google-services") version "4.4.4" apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.google.services) apply false } allprojects { From 0da91fecd25797cae3823ee564277c84109c898d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Wed, 4 Mar 2026 20:33:04 +0000 Subject: [PATCH 44/52] ci(dataconnect): use dataconnect:compile to generate SDKs in CI (#2766) This should update our CI pipeline to install the Firebase CLI and run `firebase dataconnect:compile` to generate the SDKs for the Data Connect quickstart --- .github/workflows/android.yml | 7 ++++--- build.gradle.kts | 3 ++- dataconnect/app/build.gradle.kts | 3 ++- dataconnect/build.gradle.kts | 17 +++++++++++++++++ .../dataconnect/movie-connector/connector.yaml | 2 +- .../dataconnect/movie-connector/queries.gql | 8 ++++---- gradle/libs.versions.toml | 2 ++ scripts/ci_remove_fdc.py | 8 -------- 8 files changed, 32 insertions(+), 18 deletions(-) delete mode 100644 scripts/ci_remove_fdc.py diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index b653e9a4df..d402382fb5 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -19,13 +19,14 @@ jobs: with: distribution: 'zulu' java-version: 17 + - name: Install Node to use the Firebase CLI + uses: actions/setup-node@v6 + with: + node-version: 24 - name: Setup Gradle uses: gradle/gradle-build-action@v2 - name: Check Snippets run: python scripts/checksnippets.py - # TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed - - name: Remove Firebase Data Connect from CI - run: python scripts/ci_remove_fdc.py - name: Copy mock google_services.json run: ./copy_mock_google_services_json.sh - name: Build with Gradle (Pull Request) diff --git a/build.gradle.kts b/build.gradle.kts index 0b0bb5f37b..e7900ed75b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -48,7 +48,8 @@ tasks.register("ktlintCheck") { "--code-style=android_studio", "--reporter=plain", "--reporter=checkstyle,output=${outputFile}", - "**/*.kt" + "**/*.kt", + "!**/build/**" ) jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts index 28fb4f7c25..6c32450d26 100644 --- a/dataconnect/app/build.gradle.kts +++ b/dataconnect/app/build.gradle.kts @@ -47,7 +47,7 @@ android { } } sourceSets.getByName("main") { - java.srcDirs("build/generated/sources") + kotlin.directories.add("build/generated/sources") } } @@ -62,6 +62,7 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) + implementation(libs.compose.material.icons) implementation(libs.compose.navigation) implementation(libs.androidx.lifecycle.runtime.compose.android) implementation(libs.coil.compose) diff --git a/dataconnect/build.gradle.kts b/dataconnect/build.gradle.kts index 9c6b19866e..11467f73d9 100644 --- a/dataconnect/build.gradle.kts +++ b/dataconnect/build.gradle.kts @@ -4,3 +4,20 @@ plugins { alias(libs.plugins.google.services) apply false alias(libs.plugins.compose.compiler) apply false } + +tasks { + register("dataconnectCompile") { + workingDir = project.file("./dataconnect") + if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) { + commandLine("npx.cmd", "-y", "firebase-tools@latest", "dataconnect:compile") + } else { + commandLine("npx", "-y", "firebase-tools@latest", "dataconnect:compile") + } + isIgnoreExitValue = true + } + + register("clean", Delete::class) { + delete(rootProject.layout.buildDirectory) + finalizedBy("dataconnectCompile") + } +} \ No newline at end of file diff --git a/dataconnect/dataconnect/movie-connector/connector.yaml b/dataconnect/dataconnect/movie-connector/connector.yaml index 2ba51749dc..21099bb852 100644 --- a/dataconnect/dataconnect/movie-connector/connector.yaml +++ b/dataconnect/dataconnect/movie-connector/connector.yaml @@ -10,4 +10,4 @@ generate: package: com.google.firebase.dataconnect.movies # Specify where to store the generated SDK # We're using the build/ directory so that generated code doesn't get checked into git - outputDir: ../../app/build/generated/sources/com/google/firebase/dataconnect/movies + outputDir: ../../app/build/generated/sources diff --git a/dataconnect/dataconnect/movie-connector/queries.gql b/dataconnect/dataconnect/movie-connector/queries.gql index 5e05b17e22..4e0229e98a 100644 --- a/dataconnect/dataconnect/movie-connector/queries.gql +++ b/dataconnect/dataconnect/movie-connector/queries.gql @@ -1,5 +1,5 @@ # List subset of fields for movies -query ListMovies($orderByRating: OrderDirection, $orderByReleaseYear: OrderDirection, $limit: Int) @auth(level: PUBLIC) { +query ListMovies($orderByRating: OrderDirection, $orderByReleaseYear: OrderDirection, $limit: Int) @auth(level: PUBLIC, insecureReason: "Test Mode") { movies( orderBy: [ { rating: $orderByRating }, @@ -19,7 +19,7 @@ query ListMovies($orderByRating: OrderDirection, $orderByReleaseYear: OrderDirec } # Get movie by id -query GetMovieById($id: UUID!) @auth(level: PUBLIC) { +query GetMovieById($id: UUID!) @auth(level: PUBLIC, insecureReason: "Test Mode") { movie(id: $id) { id title @@ -58,7 +58,7 @@ query GetMovieById($id: UUID!) @auth(level: PUBLIC) { } # Get actor by id -query GetActorById($id: UUID!) @auth(level: PUBLIC) { +query GetActorById($id: UUID!) @auth(level: PUBLIC, insecureReason: "Test Mode") { actor(id: $id) { id name @@ -130,7 +130,7 @@ query SearchAll( $minRating: Float! $maxRating: Float! $genre: String! -) @auth(level: PUBLIC) { +) @auth(level: PUBLIC, insecureReason: "Test Mode") { moviesMatchingTitle: movies( where: { _and: [ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9b50ae8687..42b0b1c295 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ kotlin = "2.3.0" kotlinxSerializationCore = "1.9.0" lifecycle = "2.10.0" material = "1.13.0" +materialIcons = "1.7.8" webkit = "1.14.0" [libraries] @@ -58,6 +59,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationCore" } material = { module = "com.google.android.material:material", version.ref = "material" } +compose-material-icons = { group = "androidx.compose.material", name = "material-icons-core", version.ref = "materialIcons"} [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/scripts/ci_remove_fdc.py b/scripts/ci_remove_fdc.py deleted file mode 100644 index c425af615b..0000000000 --- a/scripts/ci_remove_fdc.py +++ /dev/null @@ -1,8 +0,0 @@ -# TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed -with open('settings.gradle.kts', 'r') as file: - filedata = file.read() - -filedata = filedata.replace('":dataconnect:app",', '') - -with open('settings.gradle.kts', 'w') as file: - file.write(filedata) From e7b1b2a0b20218664947afbb9d35dec89fad595c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Wed, 4 Mar 2026 23:09:33 +0000 Subject: [PATCH 45/52] chore(ai, fdc): upgrade to Coil 3 (#2765) This should remove Coil 2 from our codebase and keep Coil 3 only :) --- .../feature/actordetail/ActorDetailScreen.kt | 2 +- .../feature/moviedetail/MovieDetailScreen.kt | 2 +- .../example/dataconnect/ui/components/ActorsList.kt | 2 +- .../example/dataconnect/ui/components/MoviesList.kt | 2 +- firebase-ai/app/build.gradle.kts | 10 +++++----- gradle/libs.versions.toml | 4 +--- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt index e1a9baa90e..8164f69675 100644 --- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt +++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/actordetail/ActorDetailScreen.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import coil.compose.AsyncImage +import coil3.compose.AsyncImage import com.google.firebase.dataconnect.movies.GetActorByIdQuery import com.google.firebase.example.dataconnect.R import com.google.firebase.example.dataconnect.ui.components.ErrorCard diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt index 148293f348..cc56757bfe 100644 --- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt +++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/feature/moviedetail/MovieDetailScreen.kt @@ -38,7 +38,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import coil.compose.AsyncImage +import coil3.compose.AsyncImage import com.google.firebase.dataconnect.movies.GetMovieByIdQuery import com.google.firebase.example.dataconnect.R import com.google.firebase.example.dataconnect.ui.components.Actor diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt index 9e783df8df..b60b271c2c 100644 --- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt +++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/ActorsList.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage +import coil3.compose.AsyncImage val ACTOR_CARD_SIZE = 64.dp diff --git a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt index 0be44d7abd..620910ab47 100644 --- a/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt +++ b/dataconnect/app/src/main/java/com/google/firebase/example/dataconnect/ui/components/MoviesList.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage +import coil3.compose.AsyncImage /** * Used to represent a movie in a list UI diff --git a/firebase-ai/app/build.gradle.kts b/firebase-ai/app/build.gradle.kts index fa28134b21..2a5a4efc4e 100644 --- a/firebase-ai/app/build.gradle.kts +++ b/firebase-ai/app/build.gradle.kts @@ -1,9 +1,9 @@ plugins { - id("com.android.application") - id("org.jetbrains.kotlin.plugin.compose") - id("org.jetbrains.kotlin.plugin.serialization") - id("com.google.gms.google-services") + alias(libs.plugins.android.application) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.google.services) } android { @@ -75,7 +75,7 @@ dependencies { implementation(libs.firebase.ai) // Image loading - implementation(libs.coil3.coil.compose) + implementation(libs.coil.compose) implementation(libs.coil.network.okhttp) implementation(libs.coil.svg) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 42b0b1c295..0afc49131d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,6 @@ activityCompose = "1.12.1" agp = "9.0.0" camerax = "1.5.2" -coilCompose = "2.7.0" coil3Compose = "3.3.0" composeBom = "2025.12.00" composeNavigation = "2.9.6" @@ -48,10 +47,9 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } -coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" } +coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3Compose" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil3Compose" } coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil3Compose" } -coil3-coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3Compose" } compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"} firebase-ai = { module = "com.google.firebase:firebase-ai" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } From 385574bc4ec8793cc6e9f5ed1b3ab4a36bf3371e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:40:25 +0000 Subject: [PATCH 46/52] chore(deps): bump fast-xml-parser in /functions/functions (#2763) Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 5.3.6 to 5.4.2. - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.3.6...v5.4.2) --- updated-dependencies: - dependency-name: fast-xml-parser dependency-version: 5.4.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- functions/functions/package-lock.json | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/functions/functions/package-lock.json b/functions/functions/package-lock.json index 89a6e0d684..6aa29293da 100644 --- a/functions/functions/package-lock.json +++ b/functions/functions/package-lock.json @@ -1036,10 +1036,23 @@ "license": "MIT", "optional": true }, + "node_modules/fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/fast-xml-parser": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", - "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.2.tgz", + "integrity": "sha512-pw/6pIl4k0CSpElPEJhDppLzaixDEuWui2CUQQBH/ECDf7+y6YwA4Gf7Tyb0Rfe4DIMuZipYj4AEL0nACKglvQ==", "funding": [ { "type": "github", @@ -1049,6 +1062,7 @@ "license": "MIT", "optional": true, "dependencies": { + "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { From 70b702e6a6e602d001b0ab5177b69c965f5484a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Sat, 7 Mar 2026 00:02:45 +0000 Subject: [PATCH 47/52] unpin play-services-auth (#2768) --- build.gradle.kts | 3 +-- firestore/app/build.gradle.kts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e7900ed75b..d60dd6bf8e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -71,8 +71,7 @@ fun isBlockListed(candidate: ModuleComponentIdentifier): Boolean { "androidx.webkit:webkit", "com.facebook.android", "com.google.guava", - "com.github.bumptech.glide", - "com.google.android.gms" + "com.github.bumptech.glide" ).any { keyword -> keyword in candidate.toString().lowercase() } diff --git a/firestore/app/build.gradle.kts b/firestore/app/build.gradle.kts index 50c151923a..c38d9062e7 100644 --- a/firestore/app/build.gradle.kts +++ b/firestore/app/build.gradle.kts @@ -60,7 +60,6 @@ dependencies { implementation("com.google.firebase:firebase-auth") // Google Play services - // Pinned to 20.7.0 as a workaround for issue https://github.com/firebase/quickstart-android/issues/1647 implementation("com.google.android.gms:play-services-auth:20.7.0") // FirebaseUI (for authentication) From 69aa3c3271a05175204452fb30e2958e0d995130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Fri, 13 Mar 2026 16:42:21 +0000 Subject: [PATCH 48/52] refactor(ai-logic): split samples into various ViewModels (#2767) The goal is to make these samples more readable by splitting them into various ViewModels - each sample/feature has its own ViewModel. There should be no UI change (other than typo fixes) or functionality changes. --- firebase-ai/README.md | 70 ++- .../quickstart/ai/FirebaseAISamples.kt | 435 ------------------ .../firebase/quickstart/ai/MainActivity.kt | 135 +++--- .../ai/feature/live/BidiViewModel.kt | 62 +-- .../ai/feature/live/StreamAudioViewModel.kt | 82 ++++ .../ai/feature/live/StreamVideoViewModel.kt | 36 ++ .../ai/feature/media/imagen/EditingMode.kt | 10 - .../media/imagen/ImagenGenerationViewModel.kt | 55 +++ .../media/imagen/ImagenInpaintingViewModel.kt | 72 +++ .../imagen/ImagenOutpaintingViewModel.kt | 82 ++++ .../imagen/ImagenStyleTransferViewModel.kt | 68 +++ .../imagen/ImagenSubjectReferenceViewModel.kt | 72 +++ .../media/imagen/ImagenTemplateViewModel.kt | 52 +++ .../feature/media/imagen/ImagenViewModel.kt | 231 ++-------- .../quickstart/ai/feature/svg/SvgViewModel.kt | 72 --- .../text/AudioSummarizationViewModel.kt | 51 ++ .../feature/text/AudioTranslationViewModel.kt | 39 ++ .../ai/feature/text/ChatViewModel.kt | 218 +++------ .../text/CourseRecommendationsViewModel.kt | 42 ++ .../text/DocumentComparisonViewModel.kt | 43 ++ .../text/GoogleSearchGroundingViewModel.kt | 36 ++ .../feature/text/ImageBlogCreatorViewModel.kt | 40 ++ .../feature/text/ImageGenerationViewModel.kt | 43 ++ .../text/ServerPromptTemplateViewModel.kt | 57 +++ .../ai/feature/text/SvgViewModel.kt | 71 +++ .../ai/feature/text/TextGenViewModel.kt | 83 ---- .../ai/feature/text/ThinkingChatViewModel.kt | 42 ++ .../ai/feature/text/TravelTipsViewModel.kt | 70 +++ .../text/VideoHashtagGeneratorViewModel.kt | 41 ++ .../text/VideoSummarizationViewModel.kt | 51 ++ .../ai/feature/text/WeatherChatViewModel.kt | 107 +++++ .../ai/{feature/live => ui}/CameraView.kt | 6 +- .../ai/{feature/text => ui}/ChatScreen.kt | 47 +- .../firebase/quickstart/ai/ui/ChatUiState.kt | 27 ++ .../media/imagen => ui}/ImagenScreen.kt | 27 +- .../quickstart/ai/ui/ImagenUiState.kt | 14 + .../ServerPromptScreen.kt} | 66 ++- .../quickstart/ai/ui/ServerPromptUiState.kt | 8 + .../live => ui}/StreamRealtimeScreen.kt | 8 +- .../live => ui}/StreamRealtimeVideoScreen.kt | 7 +- .../ai/{feature/svg => ui}/SvgScreen.kt | 19 +- .../firebase/quickstart/ai/ui/SvgUiState.kt | 8 + .../ai/ui/navigation/FirebaseAISamples.kt | 233 ++++++++++ .../ai/ui/navigation/MainMenuScreen.kt | 1 - .../quickstart/ai/ui/navigation/Sample.kt | 46 +- 45 files changed, 1850 insertions(+), 1235 deletions(-) delete mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamAudioViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamVideoViewModel.kt delete mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt delete mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioTranslationViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/CourseRecommendationsViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/DocumentComparisonViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/GoogleSearchGroundingViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageBlogCreatorViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageGenerationViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ServerPromptTemplateViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/SvgViewModel.kt delete mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ThinkingChatViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TravelTipsViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoHashtagGeneratorViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoSummarizationViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/WeatherChatViewModel.kt rename firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/{feature/live => ui}/CameraView.kt (93%) rename firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/{feature/text => ui}/ChatScreen.kt (95%) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatUiState.kt rename firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/{feature/media/imagen => ui}/ImagenScreen.kt (91%) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenUiState.kt rename firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/{feature/text/TextGenScreen.kt => ui/ServerPromptScreen.kt} (67%) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptUiState.kt rename firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/{feature/live => ui}/StreamRealtimeScreen.kt (95%) rename firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/{feature/live => ui}/StreamRealtimeVideoScreen.kt (92%) rename firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/{feature/svg => ui}/SvgScreen.kt (89%) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgUiState.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt diff --git a/firebase-ai/README.md b/firebase-ai/README.md index f3b76b6a7f..e35339307c 100644 --- a/firebase-ai/README.md +++ b/firebase-ai/README.md @@ -2,32 +2,68 @@ This Android sample app demonstrates how to use state-of-the-art generative AI models (like Gemini) to build AI-powered features and applications. + For more information about Firebase AI Logic, visit the [documentation](http://firebase.google.com/docs/ai-logic). +## Setup & Configuration + +### Prerequisites +* **Google AI (Gemini) API Key**: Most samples work out of the box with the Google AI SDK. +* **Vertex AI**: Samples marked with *(Vertex AI)* require you to enable the Vertex AI API in your Google Cloud project and have your files in Cloud Storage. +* **Server Prompt Templates**: These samples require you to set up templates in the [Firebase Console](https://console.firebase.google.com/project/_/ai-logic). + ## Getting Started To try out this sample app, you need to use latest stable version of Android Studio. -However, if you want to latest lint checks and AI productivity features in Android -Studio use the latest preview version of [Android Studio](https://developer.android.com/studio/preview). + +* [Set up your Android app for Firebase][setup-android] + * Use the package name `com.google.firebase.quickstart.ai` +* [Set up Firebase AI Logic][setup-ai-logic] +* Run the app on an Android device or emulator. ## Features -There are 2 main files that demonstrate the use of Firebase AI Logic: - -- [ChatViewModel.kt](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt) - which can do things such as: - - [Generate Text](https://firebase.google.com/docs/ai-logic/generate-text) - - [Generate structured output (JSON)](https://firebase.google.com/docs/ai-logic/generate-structured-output) - - [Analyze images](https://firebase.google.com/docs/ai-logic/analyze-images) - - [Analyze video](https://firebase.google.com/docs/ai-logic/analyze-video) - - [Analyze audio](https://firebase.google.com/docs/ai-logic/analyze-audio) - - [Analyze documents (PDFs)](https://firebase.google.com/docs/ai-logic/analyze-documents) - - [Generate images using Gemini 2.0](https://firebase.google.com/docs/ai-logic/generate-images-imagen) - - [Function calling](https://firebase.google.com/docs/ai-logic/function-calling) -- [ImagenViewModel](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt) - which shows how to [Generate images using Imagen models](https://firebase.google.com/docs/ai-logic/generate-images-imagen) +You can find the implementation for each feature by clicking on the links below: + +### Text / Chat +- [Travel tips](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TravelTipsViewModel.kt): The user wants the model to help a new traveler with travel tips +- [Chatbot recommendations for courses](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/CourseRecommendationsViewModel.kt): A chatbot suggests courses for a performing arts program. +- [Weather Chat](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/WeatherChatViewModel.kt): Use function calling to get the weather conditions for a specific US city on a specific date. +- [Grounding with Google Search](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/GoogleSearchGroundingViewModel.kt): Use Grounding with Google Search to get responses based on up-to-date information from the web. +- [Thinking](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ThinkingChatViewModel.kt): Gemini 2.5 Flash with dynamic thinking +- [Server Prompt Templates - Gemini](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ServerPromptTemplateViewModel.kt): Generate an invoice using server prompt templates. + +### Image analysis / generation +- [Imagen 4 - image generation](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt): Generate images using Imagen 4 +- [Imagen 3 - Inpainting (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt): Replace part of an image using Imagen 3 +- [Imagen 3 - Outpainting (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt): Expand an image by drawing in more background +- [Imagen 3 - Subject Reference (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt): Generate an image using a referenced subject (must be an animal) +- [Imagen 3 - Style Transfer (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt): Change the art style of a cat picture using a reference +- [Gemini 2.5 Flash Image (aka nanobanana)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageGenerationViewModel.kt): Generate and/or edit images using Gemini 2.5 Flash Image aka nanobanana +- [Server Prompt Template - Imagen](app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt): Generate an image using a server prompt template. +- [SVG Generator](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/SvgViewModel.kt): Use Gemini 3 Flash preview to create SVG illustrations +- [Blog post creator (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageBlogCreatorViewModel.kt): Create a blog post from an image file stored in Cloud Storage. + +### Audio analysis +- [Audio Summarization](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt): Summarize an audio file +- [Translation from audio (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioTranslationViewModel.kt): Translate an audio file stored in Cloud Storage + +### Video analysis +- [Hashtags for a video (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoHashtagGeneratorViewModel.kt): Generate hashtags for a video ad stored in Cloud Storage +- [Summarize video](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoSummarizationViewModel.kt): Summarize a video and extract important dialogue. + +### Live API (Real-time bidrectional streaming) +- [ForecastTalk](app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamAudioViewModel.kt): Use bidirectional streaming to get information about weather conditions +- [Gemini Live (Video input)](app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamVideoViewModel.kt): Use bidirectional streaming to chat with Gemini using your phone's camera + +### Document (PDFs) analysis +- [Document comparison (Vertex AI)](app/src/main/java/com/google/firebase/quickstart/ai/feature/text/DocumentComparisonViewModel.kt): Compare the contents of 2 documents in Cloud Storage. + ## All samples The full list of available samples can be found in the -[FirebaseAISamples.kt file](app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt). +[FirebaseAISamples.kt file](app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt). + +[setup-android]: https://firebase.google.com/docs/android/setup +[setup-ai-logic]: https://firebase.google.com/docs/ai-logic/get-started?api=dev#set-up-firebase \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt deleted file mode 100644 index c58ae508f6..0000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt +++ /dev/null @@ -1,435 +0,0 @@ -package com.google.firebase.quickstart.ai - -import com.google.firebase.ai.type.FunctionDeclaration -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.ai.type.ResponseModality -import com.google.firebase.ai.type.Schema -import com.google.firebase.ai.type.Tool -import com.google.firebase.ai.type.content -import com.google.firebase.ai.type.generationConfig -import com.google.firebase.ai.type.thinkingConfig -import com.google.firebase.quickstart.ai.feature.media.imagen.EditingMode -import com.google.firebase.quickstart.ai.ui.navigation.Category -import com.google.firebase.quickstart.ai.ui.navigation.Sample - -@OptIn(PublicPreviewAPI::class) -val FIREBASE_AI_SAMPLES = listOf( - Sample( - title = "Travel tips", - description = "The user wants the model to help a new traveler" + - " with travel tips", - navRoute = "chat", - categories = listOf(Category.TEXT), - systemInstructions = content { - text( - "You are a Travel assistant. You will answer" + - " questions the user asks based on the information listed" + - " in Relevant Information. Do not hallucinate. Do not use" + - " the internet." - ) - }, - chatHistory = listOf( - content { - role = "user" - text("I have never traveled before. When should I book a flight?") - }, - content { - role = "model" - text( - "You should book flights a couple of months ahead of time." + - " It will be cheaper and more flexible for you." - ) - }, - content { - role = "user" - text("Do I need a passport?") - }, - content { - role = "model" - text( - "If you are traveling outside your own country, make sure" + - " your passport is up-to-date and valid for more" + - " than 6 months during your travel." - ) - } - ), - initialPrompt = content { text("What else is important when traveling?") } - ), - Sample( - title = "Chatbot recommendations for courses", - description = "A chatbot suggests courses for a performing arts program.", - navRoute = "chat", - categories = listOf(Category.TEXT), - systemInstructions = content { - text( - "You are a chatbot for the county's performing and fine arts" + - " program. You help students decide what course they will" + - " take during the summer." - ) - }, - initialPrompt = content { - text("I am interested in Performing Arts. I have taken Theater 1A.") - } - ), - Sample( - title = "Audio Summarization", - description = "Summarize an audio file", - navRoute = "chat", - categories = listOf(Category.AUDIO), - chatHistory = listOf( - content { text("Can you help me summarize an audio file?") }, - content("model") { - text( - "Of course! Click on the attach button" + - " below and choose an audio file for me to summarize." - ) - } - ), - initialPrompt = content { - text( - "I have attached the audio file. Please analyze it and summarize the contents" + - " of the audio as bullet points." - ) - } - ), - Sample( - title = "Translation from audio (Vertex AI)", - description = "Translate an audio file stored in Cloud Storage", - navRoute = "chat", - categories = listOf(Category.AUDIO), - backend = GenerativeBackend.vertexAI(), - initialPrompt = content { - fileData( - "https://storage.googleapis.com/cloud-samples-data/generative-ai/audio/" + - "How_to_create_a_My_Map_in_Google_Maps.mp3", - "audio/mpeg" - ) - text("Please translate the audio to Mandarin.") - } - ), - Sample( - title = "Blog post creator (Vertex AI)", - description = "Create a blog post from an image file stored in Cloud Storage.", - navRoute = "chat", - categories = listOf(Category.IMAGE), - backend = GenerativeBackend.vertexAI(), - initialPrompt = content { - fileData( - "https://storage.googleapis.com/cloud-samples-data/generative-ai/image/meal-prep.jpeg", - "image/jpeg" - ) - text( - "Write a short, engaging blog post based on this picture." + - " It should include a description of the meal in the" + - " photo and talk about my journey meal prepping." - ) - } - ), - Sample( - title = "Imagen 4 - image generation", - description = "Generate images using Imagen 4", - navRoute = "imagen", - categories = listOf(Category.IMAGE), - initialPrompt = content { - text( - "A photo of a modern building with water in the background" - ) - }, - allowEmptyPrompt = false, - editingMode = EditingMode.GENERATE, - ), - Sample( - title = "Imagen 3 - Inpainting (Vertex AI)", - description = "Replace part of an image using Imagen 3", - modelName = "imagen-3.0-capability-001", - backend = GenerativeBackend.vertexAI(), - navRoute = "imagen", - categories = listOf(Category.IMAGE), - initialPrompt = content { text("A sunny beach") }, - includeAttach = true, - allowEmptyPrompt = true, - selectionOptions = listOf("Mask", "Background", "Foreground"), - editingMode = EditingMode.INPAINTING, - ), - Sample( - title = "Imagen 3 - Outpainting (Vertex AI)", - description = "Expand an image by drawing in more background", - modelName = "imagen-3.0-capability-001", - backend = GenerativeBackend.vertexAI(), - navRoute = "imagen", - categories = listOf(Category.IMAGE), - initialPrompt = content { text("") }, - includeAttach = true, - allowEmptyPrompt = true, - selectionOptions = listOf("Image Alignment", "Center", "Top", "Bottom", "Left", "Right"), - editingMode = EditingMode.OUTPAINTING, - ), - Sample( - title = "Imagen 3 - Subject Reference (Vertex AI)", - description = "Generate an image using a referenced subject (must be an animal)", - modelName = "imagen-3.0-capability-001", - backend = GenerativeBackend.vertexAI(), - navRoute = "imagen", - categories = listOf(Category.IMAGE), - initialPrompt = content { text(" flying through space") }, - includeAttach = true, - allowEmptyPrompt = false, - editingMode = EditingMode.SUBJECT_REFERENCE, - ), - Sample( - title = "Imagen 3 - Style Transfer (Vertex AI)", - description = "Change the art style of a cat picture using a reference", - modelName = "imagen-3.0-capability-001", - backend = GenerativeBackend.vertexAI(), - navRoute = "imagen", - categories = listOf(Category.IMAGE), - initialPrompt = content { text("A picture of a cat") }, - includeAttach = true, - allowEmptyPrompt = true, - additionalImage = MainActivity.catImage, - imageLabels = listOf("Style Target", "Style Source"), - editingMode = EditingMode.STYLE_TRANSFER - ), - Sample( - title = "Gemini 2.5 Flash Image (aka nanobanana)", - description = "Generate and/or edit images using Gemini 2.5 Flash Image aka nanobanana", - navRoute = "chat", - categories = listOf(Category.IMAGE), - modelName = "gemini-2.5-flash-image", - initialPrompt = content { - text( - "Hi, can you create a 3d rendered image of a pig " + - "with wings and a top hat flying over a happy " + - "futuristic scifi city with lots of greenery?" - ) - }, - generationConfig = generationConfig { - responseModalities = listOf(ResponseModality.TEXT, ResponseModality.IMAGE) - } - ), - Sample( - title = "Document comparison (Vertex AI)", - description = "Compare the contents of 2 documents." + - " Only supported by the Vertex AI Gemini API because the documents are stored in Cloud Storage", - navRoute = "chat", - categories = listOf(Category.DOCUMENT), - backend = GenerativeBackend.vertexAI(), - initialPrompt = content { - fileData( - "https://storage.googleapis.com/cloud-samples-data/generative-ai/pdf/form_1040_2013.pdf", - "application/pdf" - ) - fileData( - "https://storage.googleapis.com/cloud-samples-data/generative-ai/pdf/form_1040_2023.pdf", - "application/pdf" - ) - text( - "The first document is from 2013, and the second document is" + - " from 2023. How did the standard deduction evolve?" - ) - } - ), - Sample( - title = "Hashtags for a video (Vertex AI)", - description = "Generate hashtags for a video ad stored in Cloud Storage", - navRoute = "chat", - categories = listOf(Category.VIDEO), - backend = GenerativeBackend.vertexAI(), - initialPrompt = content { - fileData( - "https://storage.googleapis.com/cloud-samples-data/generative-ai/video/google_home_celebrity_ad.mp4", - "video/mpeg" - ) - text( - "Generate 5-10 hashtags that relate to the video content." + - " Try to use more popular and engaging terms," + - " e.g. #Viral. Do not add content not related to" + - " the video.\n Start the output with 'Tags:'" - ) - } - ), - Sample( - title = "Summarize video", - description = "Summarize a video and extract important dialogue.", - navRoute = "chat", - categories = listOf(Category.VIDEO), - chatHistory = listOf( - content { text("Can you help me with the description of a video file?") }, - content("model") { - text( - "Sure! Click on the attach button below and choose a" + - " video file for me to describe." - ) - } - ), - initialPrompt = content { - text( - "I have attached the video file. Provide a description of" + - " the video. The description should also contain" + - " anything important which people say in the video." - ) - } - ), - Sample( - title = "ForecastTalk", - description = "Use bidirectional streaming to get information about" + - " weather conditions for a specific US city on a specific date", - navRoute = "stream", - categories = listOf(Category.LIVE_API, Category.AUDIO, Category.FUNCTION_CALLING), - tools = listOf( - Tool.functionDeclarations( - listOf( - FunctionDeclaration( - "fetchWeather", - "Get the weather conditions for a specific US city on a specific date.", - mapOf( - "city" to Schema.string("The US city of the location."), - "state" to Schema.string("The US state of the location."), - "date" to Schema.string( - "The date for which to get the weather." + - " Date must be in the format: YYYY-MM-DD." - ), - ), - ) - ) - ) - ), - initialPrompt = content { - text("What was the weather in Boston, MA on October 17, 2024?") - } - ), - Sample( - title = "Gemini Live (Video input)", - description = "Use bidirectional streaming to chat with Gemini using your" + - " phone's camera", - navRoute = "streamVideo", - categories = listOf(Category.LIVE_API, Category.VIDEO, Category.FUNCTION_CALLING), - tools = listOf( - Tool.functionDeclarations( - listOf( - FunctionDeclaration( - "fetchWeather", - "Get the weather conditions for a specific US city on a specific date.", - mapOf( - "city" to Schema.string("The US city of the location."), - "state" to Schema.string("The US state of the location."), - "date" to Schema.string( - "The date for which to get the weather." + - " Date must be in the format: YYYY-MM-DD." - ), - ), - ) - ) - ) - ), - initialPrompt = content { - text("What was the weather in Boston, MA on October 17, 2024?") - } - ), - Sample( - title = "Weather Chat", - description = "Use function calling to get the weather conditions" + - " for a specific US city on a specific date.", - navRoute = "chat", - categories = listOf(Category.TEXT, Category.FUNCTION_CALLING), - tools = listOf( - Tool.functionDeclarations( - listOf( - FunctionDeclaration( - "fetchWeather", - "Get the weather conditions for a specific US city on a specific date.", - mapOf( - "city" to Schema.string("The US city of the location."), - "state" to Schema.string("The US state of the location."), - "date" to Schema.string( - "The date for which to get the weather." + - " Date must be in the format: YYYY-MM-DD." - ), - ), - ) - ) - ) - ), - initialPrompt = content { - text("What was the weather in Boston, MA on October 17, 2024?") - } - ), - Sample( - title = "Grounding with Google Search", - description = "Use Grounding with Google Search to get responses based on up-to-date information from the" + - " web.", - navRoute = "chat", - categories = listOf(Category.TEXT, Category.DOCUMENT), - modelName = "gemini-2.5-flash", - tools = listOf(Tool.googleSearch()), - initialPrompt = content { - text( - "What's the weather in Chicago this weekend?" - ) - }, - ), - Sample( - title = "Server Prompt Template - Imagen", - description = "Generate an image using a server prompt template. Note that you need to setup the template in" + - "the Firebase console before running this demo.", - navRoute = "imagen", - categories = listOf(Category.IMAGE), - initialPrompt = content { text("List of things that should be in the image") }, - allowEmptyPrompt = false, - editingMode = EditingMode.TEMPLATE, - // To make this work, create an "Imagen (Basic)" server prompt template in your Firebase project with this name - templateId = "imagen-basic", - templateKey = "prompt" - ), - Sample( - title = "Server Prompt Templates - Gemini", - description = "Generate an invoice using server prompt templates. Note that you need to setup the template" + - " in the Firebase console before running this demo.", - navRoute = "text", - categories = listOf(Category.TEXT), - initialPrompt = content { text("Jane Doe") }, - allowEmptyPrompt = false, - // To make this work, create an `Input + System Instructions` template in your Firebase project with this name - templateId = "input-system-instructions", - templateKey = "customerName" - ), - Sample( - title = "Thinking", - description = "Gemini 2.5 Flash with dynamic thinking", - navRoute = "chat", - categories = listOf(Category.TEXT), - initialPrompt = content { - text("Analogize photosynthesis and growing up.") - }, - generationConfig = generationConfig { - thinkingConfig = thinkingConfig { - includeThoughts = true - thinkingBudget = -1 // Dynamic Thinking - } - } - ), - Sample( - title = "SVG Generator", - description = "Use Gemini 3 Flash preview to create SVG illustrations", - navRoute = "svg", - categories = listOf(Category.IMAGE, Category.TEXT), - initialPrompt = content { - text( - "a kitten" - ) - }, - generationConfig = generationConfig { - thinkingConfig { - thinkingBudget = -1 - } - }, - systemInstructions = content { text(""" - You are an expert at turning image prompts into SVG code. When given a prompt, - use your creativity to code a 800x600 SVG rendering of it. - Always add viewBox="0 0 800 600" to the root svg tag. Do - not import external assets, they won't work. Return ONLY the SVG code, nothing else, - no commentary. - """.trimIndent()) } - ), -) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt index e3e37064bc..06ee42f8d0 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt @@ -1,8 +1,6 @@ package com.google.firebase.quickstart.ai -import android.Manifest import android.annotation.SuppressLint -import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Bundle @@ -22,27 +20,26 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.NavDestination import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.google.firebase.ai.type.toImagenInlineImage -import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeRoute -import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeScreen -import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoRoute -import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoScreen -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenRoute -import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenScreen -import com.google.firebase.quickstart.ai.feature.svg.SvgRoute -import com.google.firebase.quickstart.ai.feature.svg.SvgScreen -import com.google.firebase.quickstart.ai.feature.text.ChatRoute -import com.google.firebase.quickstart.ai.feature.text.ChatScreen -import com.google.firebase.quickstart.ai.feature.text.TextGenRoute -import com.google.firebase.quickstart.ai.feature.text.TextGenScreen +import com.google.firebase.quickstart.ai.feature.live.BidiViewModel +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenViewModel +import com.google.firebase.quickstart.ai.feature.text.ChatViewModel +import com.google.firebase.quickstart.ai.feature.text.ServerPromptTemplateViewModel +import com.google.firebase.quickstart.ai.feature.text.SvgViewModel +import com.google.firebase.quickstart.ai.ui.ChatScreen +import com.google.firebase.quickstart.ai.ui.ImagenScreen +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.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() { @@ -61,7 +58,7 @@ class MainActivity : ComponentActivity() { TopAppBar( colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primaryContainer, - titleContentColor = MaterialTheme.colorScheme.primary + titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer ), title = { Text(topBarTitle) @@ -81,74 +78,66 @@ class MainActivity : ComponentActivity() { MainMenuScreen( onSampleClicked = { topBarTitle = it.title - when (it.navRoute) { - "chat" -> { - navController.navigate(ChatRoute(it.id)) - } + navController.navigate(it.route) + } + ) + } - "imagen" -> { - navController.navigate(ImagenRoute(it.id)) - } + // 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) - "stream" -> { - navController.navigate(StreamRealtimeRoute(it.id)) - } - "streamVideo" -> { - navController.navigate(StreamRealtimeVideoRoute(it.id)) - } - "text" -> { - navController.navigate(TextGenRoute(it.id)) + when (sample.screenType) { + ScreenType.CHAT -> { + (vm as? ChatViewModel)?.let { ChatScreen(it) } + } + + ScreenType.IMAGEN -> { + (vm as? ImagenViewModel)?.let { ImagenScreen(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) } - "svg" -> { - navController.navigate(SvgRoute(it.id)) + } + + ScreenType.BIDI_VIDEO -> { + (vm as? BidiViewModel)?.let { + @SuppressLint("MissingPermission") + StreamRealtimeVideoScreen(it) } } } - ) - } - // Text Samples - composable { - ChatScreen() - } - // Imagen Samples - composable { - ImagenScreen() - } - // The permission is checked by the @RequiresPermission annotation on the - // StreamRealtimeScreen composable. - @SuppressLint("MissingPermission") - composable { - StreamRealtimeScreen() - } - // The permission is checked by the @RequiresPermission annotation on the - // StreamRealtimeVideoScreen composable. - @SuppressLint("MissingPermission") - composable { - StreamRealtimeVideoScreen() - } - composable { - TextGenScreen() - } - composable { - SvgScreen() + } } } } } - navController.addOnDestinationChangedListener(object : NavController.OnDestinationChangedListener { - override fun onDestinationChanged( - controller: NavController, - destination: NavDestination, - arguments: Bundle? - ) { - if (destination.route == "mainMenu") { - topBarTitle = getString(R.string.app_name) - } + navController.addOnDestinationChangedListener { _, destination, _ -> + if (destination.route == "mainMenu") { + topBarTitle = getString(R.string.app_name) } - }) + } } } - companion object{ + + companion object { lateinit var catImage: Bitmap } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt index 80589b18f0..547931580c 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt @@ -2,74 +2,24 @@ package com.google.firebase.quickstart.ai.feature.live import android.annotation.SuppressLint import android.graphics.Bitmap -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.navigation.toRoute -import com.google.firebase.Firebase -import com.google.firebase.ai.FirebaseAI import com.google.firebase.ai.type.FunctionCallPart import com.google.firebase.ai.type.FunctionResponsePart import com.google.firebase.ai.type.InlineData import com.google.firebase.ai.type.LiveSession import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.ai.type.ResponseModality -import com.google.firebase.ai.type.SpeechConfig -import com.google.firebase.ai.type.Voice -import com.google.firebase.ai.type.liveGenerationConfig -import com.google.firebase.app -import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES -import com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository.Companion.fetchWeather -import java.io.ByteArrayOutputStream import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonPrimitive - -@OptIn(PublicPreviewAPI::class) -class BidiViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { - private val sampleId = savedStateHandle.toRoute().sampleId - private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } +import java.io.ByteArrayOutputStream - // Firebase AI Logic - private var liveSession: LiveSession - init { - val liveGenerationConfig = liveGenerationConfig { - speechConfig = SpeechConfig(voice = Voice("CHARON")) - // Change this to ContentModality.TEXT if you want text output. - responseModality = ResponseModality.AUDIO - } - - @OptIn(PublicPreviewAPI::class) - val liveModel = - FirebaseAI.getInstance(Firebase.app, sample.backend) - .liveModel( - // If you are using Vertex AI, change the model name to - // "gemini-live-2.5-flash-preview-native-audio-09-2025" - modelName = sample.modelName ?: "gemini-2.5-flash-native-audio-preview-09-2025", - generationConfig = liveGenerationConfig, - tools = sample.tools, - ) - runBlocking { liveSession = liveModel.connect() } - } +@OptIn(PublicPreviewAPI::class) +abstract class BidiViewModel : ViewModel() { + protected lateinit var liveSession: LiveSession - fun handler(fetchWeatherCall: FunctionCallPart): FunctionResponsePart { - val response: JsonObject - fetchWeatherCall.let { - val city = it.args["city"]?.jsonPrimitive?.content - val state = it.args["state"]?.jsonPrimitive?.content - val date = it.args["date"]?.jsonPrimitive?.content - runBlocking { - response = - if (!city.isNullOrEmpty() and !state.isNullOrEmpty() and !date.isNullOrEmpty()) { - fetchWeather(city!!, state!!, date!!) - } else { - JsonObject(emptyMap()) - } - } - } - return FunctionResponsePart("fetchWeather", response, fetchWeatherCall.id) + open fun handler(functionCall: FunctionCallPart): FunctionResponsePart { + return FunctionResponsePart(functionCall.name, JsonObject(emptyMap()), functionCall.id) } // The permission check is handled by the view that calls this function. diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamAudioViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamAudioViewModel.kt new file mode 100644 index 0000000000..edea6ddf16 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamAudioViewModel.kt @@ -0,0 +1,82 @@ +package com.google.firebase.quickstart.ai.feature.live + +import com.google.firebase.Firebase +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.FunctionCallPart +import com.google.firebase.ai.type.FunctionDeclaration +import com.google.firebase.ai.type.FunctionResponsePart +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.ai.type.ResponseModality +import com.google.firebase.ai.type.Schema +import com.google.firebase.ai.type.SpeechConfig +import com.google.firebase.ai.type.Tool +import com.google.firebase.ai.type.Voice +import com.google.firebase.ai.type.liveGenerationConfig +import com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository.Companion.fetchWeather +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonPrimitive + +@Serializable +object StreamRealtimeAudioRoute + +@OptIn(PublicPreviewAPI::class) +class StreamAudioViewModel : BidiViewModel() { + init { + val liveGenerationConfig = liveGenerationConfig { + speechConfig = SpeechConfig(voice = Voice("CHARON")) + responseModality = ResponseModality.AUDIO + } + + val liveModel = + Firebase.ai(backend = GenerativeBackend.googleAI()) + .liveModel( + // Note that each backend supports a different set of models. + // See our documentation for a breakdown of models by backend: + // https://firebase.google.com/docs/ai-logic/live-api#supported-models + modelName = "gemini-2.5-flash-native-audio-preview-09-2025", + generationConfig = liveGenerationConfig, + tools = listOf( + Tool.functionDeclarations( + listOf( + FunctionDeclaration( + "fetchWeather", + "Get the weather conditions for a specific US city on a specific date.", + mapOf( + "city" to Schema.string("The US city of the location."), + "state" to Schema.string("The US state of the location."), + "date" to Schema.string( + "The date for which to get the weather." + + " Date must be in the format: YYYY-MM-DD." + ), + ), + ) + ) + ) + ), + ) + runBlocking { liveSession = liveModel.connect() } + } + + override fun handler(functionCall: FunctionCallPart): FunctionResponsePart { + val response: JsonObject + if (functionCall.name == "fetchWeather") { + val city = functionCall.args["city"]?.jsonPrimitive?.content + val state = functionCall.args["state"]?.jsonPrimitive?.content + val date = functionCall.args["date"]?.jsonPrimitive?.content + runBlocking { + response = + if (!city.isNullOrEmpty() and !state.isNullOrEmpty() and !date.isNullOrEmpty()) { + fetchWeather(city!!, state!!, date!!) + } else { + JsonObject(emptyMap()) + } + } + } else { + response = JsonObject(emptyMap()) + } + return FunctionResponsePart(functionCall.name, response, functionCall.id) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamVideoViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamVideoViewModel.kt new file mode 100644 index 0000000000..8e715785cb --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamVideoViewModel.kt @@ -0,0 +1,36 @@ +package com.google.firebase.quickstart.ai.feature.live + +import com.google.firebase.Firebase +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.ai.type.ResponseModality +import com.google.firebase.ai.type.SpeechConfig +import com.google.firebase.ai.type.Voice +import com.google.firebase.ai.type.liveGenerationConfig +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.Serializable + +@Serializable +object StreamRealtimeVideoRoute + +@OptIn(PublicPreviewAPI::class) +class StreamVideoViewModel : BidiViewModel() { + init { + val liveGenerationConfig = liveGenerationConfig { + speechConfig = SpeechConfig(voice = Voice("CHARON")) + responseModality = ResponseModality.AUDIO + } + + // Note that each backend supports a different set of models. + // See our documentation for a breakdown of models by backend: + // https://firebase.google.com/docs/ai-logic/live-api#supported-models + val liveModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).liveModel( + modelName = "gemini-2.5-flash-native-audio-preview-09-2025", + generationConfig = liveGenerationConfig, + ) + runBlocking { liveSession = liveModel.connect() } + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt deleted file mode 100644 index 10093e7350..0000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/EditingMode.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.google.firebase.quickstart.ai.feature.media.imagen - -enum class EditingMode { - GENERATE, - INPAINTING, - OUTPAINTING, - SUBJECT_REFERENCE, - STYLE_TRANSFER, - TEMPLATE, -} \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt new file mode 100644 index 0000000000..1bcd6fb867 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenGenerationViewModel.kt @@ -0,0 +1,55 @@ +package com.google.firebase.quickstart.ai.feature.media.imagen + +import android.graphics.Bitmap +import com.google.firebase.Firebase +import com.google.firebase.ai.ImagenModel +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.ImagenGenerationResponse +import com.google.firebase.ai.type.ImagenImageFormat +import com.google.firebase.ai.type.ImagenInlineImage +import com.google.firebase.ai.type.ImagenPersonFilterLevel +import com.google.firebase.ai.type.ImagenSafetyFilterLevel +import com.google.firebase.ai.type.ImagenSafetySettings +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.ai.type.imagenGenerationConfig +import com.google.firebase.quickstart.ai.ui.ImagenUiState +import kotlinx.serialization.Serializable + +@Serializable +object ImagenGenerationRoute + +@OptIn(PublicPreviewAPI::class) +class ImagenGenerationViewModel : ImagenViewModel() { + override val initialPrompt: String = "" + override val includeAttach: Boolean = false + override val selectionOptions: List = emptyList() + override val allowEmptyPrompt: Boolean = false + override val additionalImage: Bitmap? = null + override val imageLabels: List = emptyList() + + private val imagenModel: ImagenModel + + init { + imagenModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).imagenModel( + modelName = "imagen-4.0-generate-001", + generationConfig = imagenGenerationConfig { + numberOfImages = 4 + imageFormat = ImagenImageFormat.png() + }, + safetySettings = ImagenSafetySettings( + safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, + personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL + ) + ) + } + + override suspend fun performGeneration( + inputText: String, + currentState: ImagenUiState.Success + ): ImagenGenerationResponse { + return imagenModel.generateImages(inputText) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt new file mode 100644 index 0000000000..40e249e315 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenInpaintingViewModel.kt @@ -0,0 +1,72 @@ +package com.google.firebase.quickstart.ai.feature.media.imagen + +import android.graphics.Bitmap +import com.google.firebase.Firebase +import com.google.firebase.ai.ImagenModel +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.ImagenBackgroundMask +import com.google.firebase.ai.type.ImagenEditMode +import com.google.firebase.ai.type.ImagenEditingConfig +import com.google.firebase.ai.type.ImagenForegroundMask +import com.google.firebase.ai.type.ImagenGenerationResponse +import com.google.firebase.ai.type.ImagenImageFormat +import com.google.firebase.ai.type.ImagenInlineImage +import com.google.firebase.ai.type.ImagenPersonFilterLevel +import com.google.firebase.ai.type.ImagenRawImage +import com.google.firebase.ai.type.ImagenSafetyFilterLevel +import com.google.firebase.ai.type.ImagenSafetySettings +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.ai.type.imagenGenerationConfig +import com.google.firebase.ai.type.toImagenInlineImage +import com.google.firebase.quickstart.ai.ui.ImagenUiState +import kotlinx.serialization.Serializable + +@Serializable +object ImagenInpaintingRoute + +@OptIn(PublicPreviewAPI::class) +class ImagenInpaintingViewModel : ImagenViewModel() { + override val initialPrompt: String = "A sunny beach" + override val includeAttach: Boolean = true + override val selectionOptions: List = listOf("Mask", "Background", "Foreground") + override val allowEmptyPrompt: Boolean = true + override val additionalImage: Bitmap? = null + override val imageLabels: List = emptyList() + + private val imagenModel: ImagenModel + + init { + val config = imagenGenerationConfig { + numberOfImages = 4 + imageFormat = ImagenImageFormat.png() + } + val settings = ImagenSafetySettings( + safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, + personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL + ) + imagenModel = Firebase.ai( + backend = GenerativeBackend.vertexAI() + ).imagenModel( + modelName = "imagen-3.0-capability-001", + generationConfig = config, + safetySettings = settings + ) + } + + override suspend fun performGeneration( + inputText: String, + currentState: ImagenUiState.Success + ): ImagenGenerationResponse { + val bitmap = currentState.attachedImage!! + val mask = when (currentState.selectedOption) { + "Foreground" -> ImagenForegroundMask() + else -> ImagenBackgroundMask() + } + return imagenModel.editImage( + listOfNotNull(ImagenRawImage(bitmap.toImagenInlineImage()), mask), + inputText, + ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION) + ) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt new file mode 100644 index 0000000000..1f85333639 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenOutpaintingViewModel.kt @@ -0,0 +1,82 @@ +package com.google.firebase.quickstart.ai.feature.media.imagen + +import android.graphics.Bitmap +import com.google.firebase.Firebase +import com.google.firebase.ai.ImagenModel +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Dimensions +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.ImagenEditMode +import com.google.firebase.ai.type.ImagenEditingConfig +import com.google.firebase.ai.type.ImagenGenerationResponse +import com.google.firebase.ai.type.ImagenImageFormat +import com.google.firebase.ai.type.ImagenImagePlacement +import com.google.firebase.ai.type.ImagenInlineImage +import com.google.firebase.ai.type.ImagenMaskReference +import com.google.firebase.ai.type.ImagenPersonFilterLevel +import com.google.firebase.ai.type.ImagenRawMask +import com.google.firebase.ai.type.ImagenSafetyFilterLevel +import com.google.firebase.ai.type.ImagenSafetySettings +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.ai.type.imagenGenerationConfig +import com.google.firebase.ai.type.toImagenInlineImage +import com.google.firebase.quickstart.ai.ui.ImagenUiState +import kotlinx.serialization.Serializable + +@Serializable +object ImagenOutpaintingRoute + +@OptIn(PublicPreviewAPI::class) +class ImagenOutpaintingViewModel : ImagenViewModel() { + override val initialPrompt: String = "" + override val includeAttach: Boolean = true + override val selectionOptions: List = listOf("Image Alignment", "Center", "Top", "Bottom", "Left", "Right") + override val allowEmptyPrompt: Boolean = true + override val additionalImage: Bitmap? = null + override val imageLabels: List = emptyList() + + private val imagenModel: ImagenModel + + init { + val config = imagenGenerationConfig { + numberOfImages = 4 + imageFormat = ImagenImageFormat.png() + } + val settings = ImagenSafetySettings( + safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, + personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL + ) + imagenModel = Firebase.ai( + backend = GenerativeBackend.vertexAI() + ).imagenModel( + modelName = "imagen-3.0-capability-001", + generationConfig = config, + safetySettings = settings + ) + } + + override suspend fun performGeneration( + inputText: String, + currentState: ImagenUiState.Success + ): ImagenGenerationResponse { + val bitmap = currentState.attachedImage!! + val position = when (currentState.selectedOption) { + "Top" -> ImagenImagePlacement.TOP_CENTER + "Bottom" -> ImagenImagePlacement.BOTTOM_CENTER + "Left" -> ImagenImagePlacement.LEFT_CENTER + "Right" -> ImagenImagePlacement.RIGHT_CENTER + else -> ImagenImagePlacement.CENTER + } + val dimensions = Dimensions(bitmap.width * 2, bitmap.height * 2) + val (sourceImage, mask) = ImagenMaskReference.generateMaskAndPadForOutpainting( + bitmap.toImagenInlineImage(), + dimensions, + position + ) + return imagenModel.editImage( + listOf(sourceImage, ImagenRawMask(mask.image!!, 0.05)), + inputText, + ImagenEditingConfig(ImagenEditMode.OUTPAINT) + ) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt new file mode 100644 index 0000000000..8186811ebe --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenStyleTransferViewModel.kt @@ -0,0 +1,68 @@ +package com.google.firebase.quickstart.ai.feature.media.imagen + +import android.graphics.Bitmap +import com.google.firebase.Firebase +import com.google.firebase.ai.ImagenModel +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.ImagenGenerationResponse +import com.google.firebase.ai.type.ImagenImageFormat +import com.google.firebase.ai.type.ImagenInlineImage +import com.google.firebase.ai.type.ImagenPersonFilterLevel +import com.google.firebase.ai.type.ImagenRawImage +import com.google.firebase.ai.type.ImagenSafetyFilterLevel +import com.google.firebase.ai.type.ImagenSafetySettings +import com.google.firebase.ai.type.ImagenStyleReference +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.ai.type.imagenGenerationConfig +import com.google.firebase.ai.type.toImagenInlineImage +import com.google.firebase.quickstart.ai.MainActivity +import com.google.firebase.quickstart.ai.ui.ImagenUiState +import kotlinx.serialization.Serializable + +@Serializable +object ImagenStyleTransferRoute + +@OptIn(PublicPreviewAPI::class) +class ImagenStyleTransferViewModel : ImagenViewModel() { + override val initialPrompt: String = "A picture of a cat" + override val includeAttach: Boolean = true + override val selectionOptions: List = emptyList() + override val allowEmptyPrompt: Boolean = true + override val additionalImage: Bitmap = MainActivity.catImage + override val imageLabels: List = listOf("Style Target", "Style Source") + + private val imagenModel: ImagenModel + + init { + val config = imagenGenerationConfig { + numberOfImages = 4 + imageFormat = ImagenImageFormat.png() + } + val settings = ImagenSafetySettings( + safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, + personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL + ) + imagenModel = Firebase.ai( + backend = GenerativeBackend.vertexAI() + ).imagenModel( + modelName = "imagen-3.0-capability-001", + generationConfig = config, + safetySettings = settings + ) + } + + override suspend fun performGeneration( + inputText: String, + currentState: ImagenUiState.Success + ): ImagenGenerationResponse { + val attachedImage = currentState.attachedImage!! + return imagenModel.editImage( + listOf( + ImagenRawImage(MainActivity.catImage.toImagenInlineImage()), + ImagenStyleReference(attachedImage.toImagenInlineImage(), 1, "an art style") + ), + "Generate an image in an art style [1] based on the following caption: $inputText", + ) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt new file mode 100644 index 0000000000..a0a6ef8f05 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenSubjectReferenceViewModel.kt @@ -0,0 +1,72 @@ +package com.google.firebase.quickstart.ai.feature.media.imagen + +import android.graphics.Bitmap +import com.google.firebase.Firebase +import com.google.firebase.ai.ImagenModel +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.ImagenGenerationResponse +import com.google.firebase.ai.type.ImagenImageFormat +import com.google.firebase.ai.type.ImagenInlineImage +import com.google.firebase.ai.type.ImagenPersonFilterLevel +import com.google.firebase.ai.type.ImagenSafetyFilterLevel +import com.google.firebase.ai.type.ImagenSafetySettings +import com.google.firebase.ai.type.ImagenSubjectReference +import com.google.firebase.ai.type.ImagenSubjectReferenceType +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.ai.type.imagenGenerationConfig +import com.google.firebase.ai.type.toImagenInlineImage +import com.google.firebase.quickstart.ai.ui.ImagenUiState +import kotlinx.serialization.Serializable + +@Serializable +object ImagenSubjectReferenceRoute + +@OptIn(PublicPreviewAPI::class) +class ImagenSubjectReferenceViewModel : ImagenViewModel() { + override val initialPrompt: String = " flying through space" + override val includeAttach: Boolean = true + override val selectionOptions: List = emptyList() + override val allowEmptyPrompt: Boolean = false + override val additionalImage: Bitmap? = null + override val imageLabels: List = emptyList() + + private val imagenModel: ImagenModel + + init { + val config = imagenGenerationConfig { + numberOfImages = 4 + imageFormat = ImagenImageFormat.png() + } + val settings = ImagenSafetySettings( + safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, + personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL + ) + imagenModel = Firebase.ai( + backend = GenerativeBackend.vertexAI() + ).imagenModel( + modelName = "imagen-3.0-capability-001", + generationConfig = config, + safetySettings = settings + ) + } + + override suspend fun performGeneration( + inputText: String, + currentState: ImagenUiState.Success + ): ImagenGenerationResponse { + val attachedImage = currentState.attachedImage!! + return imagenModel.editImage( + listOf( + ImagenSubjectReference( + referenceId = 1, + image = attachedImage.toImagenInlineImage(), + subjectType = ImagenSubjectReferenceType.ANIMAL, + description = "An animal" + ) + ), + "Create an image about An animal [1] to match the description: " + + inputText.replace("", "An animal [1]"), + ) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt new file mode 100644 index 0000000000..2e882faa1f --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenTemplateViewModel.kt @@ -0,0 +1,52 @@ +package com.google.firebase.quickstart.ai.feature.media.imagen + +import android.graphics.Bitmap +import com.google.firebase.Firebase +import com.google.firebase.ai.TemplateImagenModel +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.ImagenGenerationResponse +import com.google.firebase.ai.type.ImagenInlineImage +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.quickstart.ai.ui.ImagenUiState +import kotlinx.serialization.Serializable + +@Serializable +object ImagenTemplateRoute + +@OptIn(PublicPreviewAPI::class) +class ImagenTemplateViewModel : ImagenViewModel() { + override val initialPrompt: String = "List of things that should be in the image" + override val includeAttach: Boolean = false + override val selectionOptions: List = emptyList() + override val allowEmptyPrompt: Boolean = false + override val additionalImage: Bitmap? = null + override val imageLabels: List = emptyList() + + private var templateImagenModel: TemplateImagenModel + + init { + templateImagenModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).templateImagenModel() + } + + override suspend fun performGeneration( + inputText: String, + currentState: ImagenUiState.Success + ): ImagenGenerationResponse { + return try { + templateImagenModel.generateImages("imagen-basic", mapOf("prompt" to inputText)) + } catch (e: Exception) { + if (e.localizedMessage?.contains("not found") == true) { + throw Exception( + """ + Template was not found, please verify that your project contains a template named "imagen-basic". + """.trimIndent() + ) + } else { + throw e + } + } + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt index cf794a0368..a10d91e337 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt @@ -2,132 +2,46 @@ package com.google.firebase.quickstart.ai.feature.media.imagen import android.graphics.Bitmap import android.graphics.BitmapFactory -import androidx.lifecycle.SavedStateHandle +import androidx.core.graphics.scale import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.navigation.toRoute -import com.google.firebase.Firebase -import com.google.firebase.ai.ImagenModel -import com.google.firebase.ai.ai -import com.google.firebase.ai.type.ImagenImageFormat -import com.google.firebase.ai.type.ImagenPersonFilterLevel -import com.google.firebase.ai.type.ImagenSafetyFilterLevel -import com.google.firebase.ai.type.ImagenSafetySettings +import com.google.firebase.ai.type.ImagenGenerationResponse +import com.google.firebase.ai.type.ImagenInlineImage import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.ai.type.asTextOrNull -import com.google.firebase.ai.type.imagenGenerationConfig -import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES +import com.google.firebase.quickstart.ai.ui.ImagenUiState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import androidx.core.graphics.scale -import com.google.firebase.ai.TemplateImagenModel -import com.google.firebase.ai.type.Dimensions -import com.google.firebase.ai.type.ImagenBackgroundMask -import com.google.firebase.ai.type.ImagenEditMode -import com.google.firebase.ai.type.ImagenEditingConfig -import com.google.firebase.ai.type.ImagenForegroundMask -import com.google.firebase.ai.type.ImagenGenerationResponse -import com.google.firebase.ai.type.ImagenImagePlacement -import com.google.firebase.ai.type.ImagenInlineImage -import com.google.firebase.ai.type.ImagenMaskReference -import com.google.firebase.ai.type.ImagenRawImage -import com.google.firebase.ai.type.ImagenRawMask -import com.google.firebase.ai.type.ImagenStyleReference -import com.google.firebase.ai.type.ImagenSubjectReference -import com.google.firebase.ai.type.ImagenSubjectReferenceType -import com.google.firebase.ai.type.toImagenInlineImage -import com.google.firebase.quickstart.ai.MainActivity -import kotlin.collections.component1 -import kotlin.collections.component2 @OptIn(PublicPreviewAPI::class) -class ImagenViewModel( - savedStateHandle: SavedStateHandle -) : ViewModel() { - private val sampleId = savedStateHandle.toRoute().sampleId - private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } - val initialPrompt = sample.initialPrompt?.parts?.first()?.asTextOrNull().orEmpty() - val imageLabels = sample.imageLabels - - private val _errorMessage: MutableStateFlow = MutableStateFlow(null) - val errorMessage: StateFlow = _errorMessage - - private val _isLoading = MutableStateFlow(false) - val isLoading: StateFlow = _isLoading - - val includeAttach = sample.includeAttach - - val selectionOptions = sample.selectionOptions - - private val _selectedOption = MutableStateFlow(null) - val selectedOption: StateFlow = _selectedOption - - val allowEmptyPrompt = sample.allowEmptyPrompt - - val additionalImage = sample.additionalImage +abstract class ImagenViewModel : ViewModel() { - val templateId = sample.templateId + abstract val initialPrompt: String + abstract val includeAttach: Boolean + abstract val selectionOptions: List + abstract val allowEmptyPrompt: Boolean + abstract val additionalImage: Bitmap? + abstract val imageLabels: List - val templateKey = sample.templateKey + private val _uiState = MutableStateFlow(ImagenUiState.Success()) + val uiState: StateFlow = _uiState.asStateFlow() - private val _attachedImage = MutableStateFlow(null) - val attachedImage: StateFlow = _attachedImage - - private val _generatedBitmaps = MutableStateFlow(listOf()) - val generatedBitmaps: StateFlow> = _generatedBitmaps - - // Firebase AI Logic - private val imagenModel: ImagenModel - private val templateImagenModel: TemplateImagenModel - - init { - val config = imagenGenerationConfig { - numberOfImages = 4 - imageFormat = ImagenImageFormat.png() - } - val settings = ImagenSafetySettings( - safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, - personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL - ) - imagenModel = Firebase.ai( - backend = sample.backend - ).imagenModel( - modelName = sample.modelName ?: "imagen-4.0-generate-001", - generationConfig = config, - safetySettings = settings - ) - templateImagenModel = Firebase.ai.templateImagenModel() - } + protected abstract suspend fun performGeneration( + inputText: String, + currentState: ImagenUiState.Success + ): ImagenGenerationResponse fun generateImages(inputText: String) { + val currentState = (_uiState.value as? ImagenUiState.Success) ?: ImagenUiState.Success() + viewModelScope.launch { - _isLoading.value = true - _errorMessage.value = null // clear error message + _uiState.value = ImagenUiState.Loading try { - val imageResponse = when(sample.editingMode) { - EditingMode.INPAINTING -> inpaint(imagenModel, inputText) - EditingMode.OUTPAINTING -> outpaint(imagenModel, inputText) - EditingMode.SUBJECT_REFERENCE -> drawReferenceSubject(imagenModel, inputText) - EditingMode.STYLE_TRANSFER -> transferStyle(imagenModel, inputText) - EditingMode.TEMPLATE -> - generateWithTemplate(templateImagenModel, templateId!!, mapOf(templateKey!! to inputText)) - else -> generate(imagenModel, inputText) - } - _generatedBitmaps.value = imageResponse.images.map { it.asBitmap() } + val imageResponse = performGeneration(inputText, currentState) + _uiState.value = currentState.copy(images = imageResponse.images.map { it.asBitmap() }) } catch (e: Exception) { - val errorMessage = - if ((e.localizedMessage?.contains("not found") == true) && - sample.editingMode == EditingMode.TEMPLATE) { - "Template was not found, please verify that your project contains a" + - " template named \"$templateId\"." - } else { - e.localizedMessage - } - _errorMessage.value = errorMessage - } finally { - _isLoading.value = false + _uiState.value = ImagenUiState.Error(e.localizedMessage ?: "Unknown error") } } } @@ -140,101 +54,12 @@ class ImagenViewModel( 512, (originalBitmap.height * (512.0 / originalBitmap.width)).toInt() ) - _attachedImage.emit(resizedBitmap) + val currentState = (_uiState.value as? ImagenUiState.Success) ?: ImagenUiState.Success() + _uiState.value = currentState.copy(attachedImage = resizedBitmap) } fun selectOption(selection: String) { - viewModelScope.launch { - _selectedOption.emit(selection) - } - } - - suspend fun transferStyle( - model: ImagenModel, - inputText: String, - ): ImagenGenerationResponse { - return model.editImage( - listOf( - ImagenRawImage(MainActivity.catImage.toImagenInlineImage()), - ImagenStyleReference(attachedImage.first()!!.toImagenInlineImage(), 1, "an art style") - ), - "Generate an image in an art style [1] based on the following caption: $inputText", - ) - } - - suspend fun drawReferenceSubject( - model: ImagenModel, - inputText: String, - ): ImagenGenerationResponse { - return model.editImage( - listOf( - ImagenSubjectReference( - referenceId = 1, - image = attachedImage.first()!!.toImagenInlineImage(), - subjectType = ImagenSubjectReferenceType.ANIMAL, - description = "An animal" - ) - ), - "Create an image about An animal [1] to match the description: " + - inputText.replace("", "An animal [1]"), - ) - } - - suspend fun outpaint( - model: ImagenModel, - inputText: String, - ): ImagenGenerationResponse { - val bitmap = attachedImage.first()!! - val position = when (selectedOption.first()) { - "Top" -> ImagenImagePlacement.TOP_CENTER - "Bottom" -> ImagenImagePlacement.BOTTOM_CENTER - "Left" -> ImagenImagePlacement.LEFT_CENTER - "Right" -> ImagenImagePlacement.RIGHT_CENTER - else -> ImagenImagePlacement.CENTER - } - val dimensions = Dimensions(bitmap.width * 2, bitmap.height * 2) - val (sourceImage, mask) = ImagenMaskReference.generateMaskAndPadForOutpainting( - bitmap.toImagenInlineImage(), - dimensions, - position - ) - return model.editImage( - listOf(sourceImage, ImagenRawMask(mask.image!!, 0.05)), - inputText, - ImagenEditingConfig(ImagenEditMode.OUTPAINT) - ) - } - - suspend fun inpaint( - model: ImagenModel, - inputText: String, - ): ImagenGenerationResponse { - val bitmap = attachedImage.first()!! - val mask = when (selectedOption.first()) { - "Foreground" -> ImagenForegroundMask() - else -> ImagenBackgroundMask() - } - return model.editImage( - listOfNotNull(ImagenRawImage(bitmap.toImagenInlineImage()), mask), - inputText, - ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION) - ) - } - - suspend fun generate( - model: ImagenModel, - inputText: String, - ): ImagenGenerationResponse { - return model.generateImages( - inputText - ) - } - - suspend fun generateWithTemplate( - model: TemplateImagenModel, - templateId: String, - inputMap: Map - ): ImagenGenerationResponse { - return model.generateImages(templateId, inputMap) + val currentState = (_uiState.value as? ImagenUiState.Success) ?: ImagenUiState.Success() + _uiState.value = currentState.copy(selectedOption = selection) } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgViewModel.kt deleted file mode 100644 index 4ad04b3bdf..0000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgViewModel.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.google.firebase.quickstart.ai.feature.svg - -import androidx.compose.runtime.mutableStateListOf -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.navigation.toRoute -import com.google.firebase.Firebase -import com.google.firebase.ai.GenerativeModel -import com.google.firebase.ai.ai -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.TextPart -import com.google.firebase.ai.type.asTextOrNull -import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES -import com.google.firebase.quickstart.ai.feature.text.ChatRoute -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch - -class SvgViewModel( - savedStateHandle: SavedStateHandle -) : ViewModel() { - private val sampleId = savedStateHandle.toRoute().sampleId - private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } - val initialPrompt: String = - sample.initialPrompt?.parts - ?.filterIsInstance() - ?.first() - ?.asTextOrNull().orEmpty() - - private val _isLoading = MutableStateFlow(false) - val isLoading: StateFlow = _isLoading - - private val _errorMessage = MutableStateFlow(null) - val errorMessage: StateFlow = _errorMessage - - private val _generatedSvgList = mutableStateListOf() - val generatedSvgs: StateFlow> = - MutableStateFlow>(_generatedSvgList) - - private val generativeModel: GenerativeModel - - init { - generativeModel = Firebase.ai( - backend = sample.backend - ).generativeModel( - modelName = sample.modelName ?: "gemini-3-flash-preview", - systemInstruction = sample.systemInstructions, - generationConfig = sample.generationConfig, - tools = sample.tools - ) - } - - fun generateSVG(prompt: String) { - _isLoading.value = true - viewModelScope.launch(Dispatchers.IO) { - try { - val response = generativeModel.generateContent(prompt) - response.text?.let { - _generatedSvgList.add(0, it) - } - _errorMessage.value = null - } catch (e: Exception) { - _errorMessage.value = e.localizedMessage - } finally { - _isLoading.value = false - } - } - - } -} \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt new file mode 100644 index 0000000000..8ddb16c92a --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt @@ -0,0 +1,51 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.content +import com.google.firebase.quickstart.ai.ui.ChatUiState +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object AudioSummarizationRoute + +class AudioSummarizationViewModel : ChatViewModel() { + + override val initialPrompt: String = + """ + I have attached the audio file. Please analyze it and summarize + the contents of the audio as bullet points. + """.trimIndent() + + private val chat: Chat + + init { + val generativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).generativeModel( + modelName = "gemini-2.5-flash" + ) + chat = generativeModel.startChat( + listOf( + content { text("Can you help me summarize an audio file?") }, + content("model") { + text( + "Of course! Click on the attach button" + + " below and choose an audio file for me to summarize." + ) + } + )) + _messages.value = chat.history.map { UiChatMessage(it) } + _uiState.value = ChatUiState.Success + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioTranslationViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioTranslationViewModel.kt new file mode 100644 index 0000000000..9b83c8b085 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioTranslationViewModel.kt @@ -0,0 +1,39 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object AudioTranslationRoute + +class AudioTranslationViewModel : ChatViewModel() { + + override val initialPrompt: String = "Please translate the audio to Mandarin." + + private val chat: Chat + + init { + val generativeModel = Firebase.ai(backend = GenerativeBackend.vertexAI()).generativeModel( + modelName = "gemini-2.5-flash" + ) + chat = generativeModel.startChat() + + // Handling the initial fileData in the prompt builder for the first message + contentBuilder.fileData( + "https://storage.googleapis.com/cloud-samples-data/generative-ai/audio/" + + "How_to_create_a_My_Map_in_Google_Maps.mp3", + "audio/mpeg" + ) + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt index 33afaad2ee..d33e948d68 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatViewModel.kt @@ -1,139 +1,91 @@ package com.google.firebase.quickstart.ai.feature.text import android.graphics.BitmapFactory -import android.util.Log -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.toMutableStateList -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.navigation.toRoute -import com.google.firebase.Firebase -import com.google.firebase.ai.Chat -import com.google.firebase.ai.ai import com.google.firebase.ai.type.Content -import com.google.firebase.ai.type.FileDataPart -import com.google.firebase.ai.type.FunctionResponsePart import com.google.firebase.ai.type.GenerateContentResponse -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.GroundingMetadata -import com.google.firebase.ai.type.TextPart -import com.google.firebase.ai.type.asTextOrNull -import com.google.firebase.ai.type.content -import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES -import com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.quickstart.ai.ui.Attachment +import com.google.firebase.quickstart.ai.ui.ChatUiState +import com.google.firebase.quickstart.ai.ui.UiChatMessage import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import kotlinx.serialization.json.jsonPrimitive - -/** - * A wrapper for a model [Content] object that includes additional UI-specific metadata. - */ -data class UiChatMessage( - val content: Content, - val groundingMetadata: GroundingMetadata? = null, -) - -class ChatViewModel( - savedStateHandle: SavedStateHandle -) : ViewModel() { - private val sampleId = savedStateHandle.toRoute().sampleId - private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } - val initialPrompt: String = - sample.initialPrompt?.parts - ?.filterIsInstance() - ?.first() - ?.asTextOrNull().orEmpty() - - private val _isLoading = MutableStateFlow(false) - val isLoading: StateFlow = _isLoading - - private val _errorMessage = MutableStateFlow(null) - val errorMessage: StateFlow = _errorMessage - - private val _messageList: MutableList = - sample.chatHistory.map { UiChatMessage(it) }.toMutableStateList() - private val _messages = MutableStateFlow>(_messageList) - val messages: StateFlow> = - _messages - - private val _attachmentsList: MutableList = - sample.initialPrompt?.parts?.filterIsInstance()?.map { - Attachment(it.uri) - }?.toMutableStateList() ?: mutableStateListOf() - private val _attachments = MutableStateFlow>(_attachmentsList) - val attachments: StateFlow> - get() = _attachments - - // Firebase AI Logic - private var contentBuilder = Content.Builder() - private val chat: Chat - - init { - val generativeModel = Firebase.ai( - backend = sample.backend // GenerativeBackend.googleAI() by default - ).generativeModel( - modelName = sample.modelName ?: "gemini-2.5-flash", - systemInstruction = sample.systemInstructions, - generationConfig = sample.generationConfig, - tools = sample.tools - ) - chat = generativeModel.startChat(sample.chatHistory) - - // add attachments from initial prompt - sample.initialPrompt?.parts?.forEach { part -> - if (part is TextPart) { - /* Ignore text parts, as the text will come from the textInputField */ - } else { - contentBuilder.part(part) - } - } - } +@OptIn(PublicPreviewAPI::class) +abstract class ChatViewModel : ViewModel() { + + protected val _uiState = MutableStateFlow(ChatUiState.Success) + val uiState: StateFlow = _uiState.asStateFlow() + + protected val _messages = MutableStateFlow>(emptyList()) + val messages: StateFlow> = _messages.asStateFlow() + + protected val _attachments = MutableStateFlow>(emptyList()) + val attachments: StateFlow> = _attachments.asStateFlow() + + abstract val initialPrompt: String + + // Builder for the next message + protected var contentBuilder = Content.Builder() + + /** + * Entry point for sending a message. + * Handles adding the message to the UI and setting the loading state. + */ fun sendMessage(userMessage: String) { val prompt = contentBuilder .text(userMessage) .build() - _messageList.add(UiChatMessage(prompt)) + _messages.value = _messages.value + UiChatMessage(prompt) viewModelScope.launch { - _isLoading.value = true + _uiState.value = ChatUiState.Loading try { - val response = chat.sendMessage(prompt) - if (response.functionCalls.isEmpty()) { - // Samples without function calling can display the response in the UI - val candidate = response.candidates.first() - - // Compliance check for grounding - if (candidate.groundingMetadata != null - && candidate.groundingMetadata?.groundingChunks?.isNotEmpty() == true - && candidate.groundingMetadata?.searchEntryPoint == null) { - _errorMessage.value = - "Could not display the response because it was missing required attribution components." - } else { - _messageList.add( - UiChatMessage(candidate.content, candidate.groundingMetadata) - ) - _errorMessage.value = null // clear errors - } - } else { - // Samples WITH function calling need to perform - // additional handling - handleFunctionCalls(response) - } - _errorMessage.value = null // clear errors + performSendMessage(prompt, _messages.value) } catch (e: Exception) { - _errorMessage.value = e.localizedMessage + _uiState.value = ChatUiState.Error(e.localizedMessage ?: "Unknown error") } finally { - _isLoading.value = false contentBuilder = Content.Builder() // reset the builder - _attachmentsList.clear() } } } + /** + * Subclasses implement this to handle the actual AI logic. + */ + protected abstract suspend fun performSendMessage( + prompt: Content, + currentMessages: List + ) + + /** + * Centralized method to validate the AI response (grounding check) and update the UI state. + */ + protected fun validateAndDisplayResponse( + response: GenerateContentResponse, + currentMessages: List + ) { + val candidate = response.candidates.firstOrNull() ?: return + + // Compliance check for grounding + if (candidate.groundingMetadata != null + && candidate.groundingMetadata?.groundingChunks?.isNotEmpty() == true + && candidate.groundingMetadata?.searchEntryPoint == null + ) { + _uiState.value = ChatUiState.Error( + "Could not display the response because it was missing required attribution components." + ) + } else { + _messages.value = currentMessages + UiChatMessage(candidate.content, candidate.groundingMetadata) + _attachments.value = emptyList() + _uiState.value = ChatUiState.Success + } + } + fun attachFile( fileInBytes: ByteArray, mimeType: String?, @@ -145,52 +97,10 @@ class ChatViewModel( } else { contentBuilder.inlineData(fileInBytes, mimeType ?: "text/plain") } - _attachmentsList.add(Attachment(fileName ?: "Unnamed file")) - } - /** - * Only used by samples with function calling - */ - private suspend fun handleFunctionCalls( - response: GenerateContentResponse - ) { - response.functionCalls.forEach { functionCall -> - Log.d( - "ChatViewModel", "Model responded with function call:" + - functionCall.name - ) - when (functionCall.name) { - "fetchWeather" -> { - // Handle the call to fetchWeather() - val city = functionCall.args["city"]!!.jsonPrimitive.content - val state = functionCall.args["city"]!!.jsonPrimitive.content - val date = functionCall.args["date"]!!.jsonPrimitive.content - - val functionResponse = WeatherRepository - .fetchWeather(city, state, date) - - // Send the response(s) from the function back to the model - // so that the model can use it to generate its final response. - val finalResponse = chat.sendMessage(content("function") { - part(FunctionResponsePart("fetchWeather", functionResponse)) - }) - - Log.d("ChatViewModel", "Model responded with: ${finalResponse.text}") - val candidate = finalResponse.candidates.first() - _messageList.add(UiChatMessage(candidate.content, - candidate.groundingMetadata)) - } - - else -> { - Log.d( - "ChatViewModel", "Model responded with unknown" + - " function call: ${functionCall.name}" - ) - } - } - } + _attachments.value = _attachments.value + Attachment(fileName ?: "Unnamed file") } - private fun decodeBitmapFromImage(input: ByteArray) = + protected fun decodeBitmapFromImage(input: ByteArray) = BitmapFactory.decodeByteArray(input, 0, input.size) } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/CourseRecommendationsViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/CourseRecommendationsViewModel.kt new file mode 100644 index 0000000000..4c96674516 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/CourseRecommendationsViewModel.kt @@ -0,0 +1,42 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.content +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object CourseRecommendationsRoute + +class CourseRecommendationsViewModel : ChatViewModel() { + + override val initialPrompt: String = "I am interested in Performing Arts. I have taken Theater 1A." + + private val chat: Chat + + init { + val generativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).generativeModel( + modelName = "gemini-2.5-flash", + systemInstruction = content { + text( + "You are a chatbot for the county's performing and fine arts" + + " program. You help students decide what course they will" + + " take during the summer." + ) + } + ) + chat = generativeModel.startChat() + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/DocumentComparisonViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/DocumentComparisonViewModel.kt new file mode 100644 index 0000000000..e91fb2fa0c --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/DocumentComparisonViewModel.kt @@ -0,0 +1,43 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object DocumentComparisonRoute + +class DocumentComparisonViewModel : ChatViewModel() { + + override val initialPrompt: String = "The first document is from 2013, and the second document is" + + " from 2023. How did the standard deduction evolve?" + + private val chat: Chat + + init { + val generativeModel = Firebase.ai(backend = GenerativeBackend.vertexAI()).generativeModel( + modelName = "gemini-2.5-flash" + ) + chat = generativeModel.startChat() + + // Pre-attach the documents + contentBuilder.fileData( + "https://storage.googleapis.com/cloud-samples-data/generative-ai/pdf/form_1040_2013.pdf", + "application/pdf" + ) + contentBuilder.fileData( + "https://storage.googleapis.com/cloud-samples-data/generative-ai/pdf/form_1040_2023.pdf", + "application/pdf" + ) + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/GoogleSearchGroundingViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/GoogleSearchGroundingViewModel.kt new file mode 100644 index 0000000000..f2520878fa --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/GoogleSearchGroundingViewModel.kt @@ -0,0 +1,36 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.Tool +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object GoogleSearchGroundingRoute + +class GoogleSearchGroundingViewModel : ChatViewModel() { + + override val initialPrompt: String = "What's the weather in Chicago this weekend?" + + private val chat: Chat + + init { + val generativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).generativeModel( + modelName = "gemini-2.5-flash", + tools = listOf(Tool.googleSearch()) + ) + chat = generativeModel.startChat() + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageBlogCreatorViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageBlogCreatorViewModel.kt new file mode 100644 index 0000000000..8efc9bc42a --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageBlogCreatorViewModel.kt @@ -0,0 +1,40 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object ImageBlogCreatorRoute + +class ImageBlogCreatorViewModel : ChatViewModel() { + + override val initialPrompt: String = "Write a short, engaging blog post based on this picture." + + " It should include a description of the meal in the" + + " photo and talk about my journey meal prepping." + + private val chat: Chat + + init { + val generativeModel = Firebase.ai(backend = GenerativeBackend.vertexAI()).generativeModel( + modelName = "gemini-2.5-flash" + ) + chat = generativeModel.startChat() + + // Pre-attach the image from cloud storage + contentBuilder.fileData( + "https://storage.googleapis.com/cloud-samples-data/generative-ai/image/meal-prep.jpeg", + "image/jpeg" + ) + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageGenerationViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageGenerationViewModel.kt new file mode 100644 index 0000000000..09c0802152 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ImageGenerationViewModel.kt @@ -0,0 +1,43 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.ResponseModality +import com.google.firebase.ai.type.generationConfig +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object ImageGenerationRoute + +class ImageGenerationViewModel : ChatViewModel() { + + override val initialPrompt: String = """ + Hi, can you create a 3d rendered image of a pig + with wings and a top hat flying over a happy + futuristic scifi city with lots of greenery? + """.trimIndent() + + private val chat: Chat + + init { + val generativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).generativeModel( + modelName = "gemini-2.5-flash-image", + generationConfig = generationConfig { + responseModalities = listOf(ResponseModality.TEXT, ResponseModality.IMAGE) + } + ) + chat = generativeModel.startChat() + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ServerPromptTemplateViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ServerPromptTemplateViewModel.kt new file mode 100644 index 0000000000..2fd85d6225 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ServerPromptTemplateViewModel.kt @@ -0,0 +1,57 @@ +package com.google.firebase.quickstart.ai.feature.text + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.Firebase +import com.google.firebase.ai.TemplateGenerativeModel +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.quickstart.ai.ui.ServerPromptUiState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable + +@Serializable +object ServerPromptTemplateRoute + +@OptIn(PublicPreviewAPI::class) +class ServerPromptTemplateViewModel : ViewModel() { + val initialPrompt = "Jane Doe" + val allowEmptyPrompt = false + + private val _uiState = MutableStateFlow(ServerPromptUiState.Success()) + val uiState: StateFlow = _uiState.asStateFlow() + + private var templateGenerativeModel: TemplateGenerativeModel + + init { + templateGenerativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).templateGenerativeModel() + } + + fun generate(inputText: String) { + viewModelScope.launch { + _uiState.value = ServerPromptUiState.Loading + try { + val response = templateGenerativeModel + .generateContent("input-system-instructions", mapOf("customerName" to inputText)) + _uiState.value = ServerPromptUiState.Success(response.text) + } catch (e: Exception) { + _uiState.value = ServerPromptUiState.Error( + if (e.localizedMessage?.contains("not found") == true) { + """ + Template was not found, please verify that your project contains a template + named "input-system-instructions". + """.trimIndent() + } else { + e.localizedMessage ?: "Unknown error" + } + ) + } + } + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/SvgViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/SvgViewModel.kt new file mode 100644 index 0000000000..bc6b4d7e22 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/SvgViewModel.kt @@ -0,0 +1,71 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.Firebase +import com.google.firebase.ai.GenerativeModel +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.content +import com.google.firebase.ai.type.generationConfig +import com.google.firebase.ai.type.thinkingConfig +import kotlinx.coroutines.Dispatchers +import com.google.firebase.quickstart.ai.ui.SvgUiState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +@Serializable +object SvgRoute + +class SvgViewModel : ViewModel() { + private val _uiState = MutableStateFlow(SvgUiState.Success()) + val uiState: StateFlow = _uiState.asStateFlow() + + private val generativeModel: GenerativeModel + + init { + generativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).generativeModel( + modelName = "gemini-3-flash-preview", + systemInstruction = content { + text( + """ + You are an expert at turning image prompts into SVG code. When given a prompt, + use your creativity to code a 800x600 SVG rendering of it. + Always add viewBox="0 0 800 600" to the root svg tag. Do + not import external assets, they won't work. Return ONLY the SVG code, nothing else, + no commentary. + """.trimIndent() + ) + }, + generationConfig = generationConfig { + thinkingConfig { + thinkingBudget = -1 + } + } + ) + } + + fun generateSVG(prompt: String) { + val currentSvgs = (_uiState.value as? SvgUiState.Success)?.svgs ?: emptyList() + _uiState.value = SvgUiState.Loading + viewModelScope.launch(Dispatchers.IO) { + try { + val response = generativeModel.generateContent(prompt) + val newSvg = response.text + if (newSvg != null) { + _uiState.value = SvgUiState.Success(listOf(newSvg) + currentSvgs) + } else { + _uiState.value = SvgUiState.Success(currentSvgs) + } + } catch (e: Exception) { + _uiState.value = SvgUiState.Error(e.localizedMessage ?: "Unknown error") + } + } + } +} \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt deleted file mode 100644 index 52532daef2..0000000000 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenViewModel.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.google.firebase.quickstart.ai.feature.text - -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.navigation.toRoute -import com.google.firebase.Firebase -import com.google.firebase.ai.ai -import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.ai.type.asTextOrNull -import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import com.google.firebase.ai.GenerativeModel -import com.google.firebase.ai.TemplateGenerativeModel - -@OptIn(PublicPreviewAPI::class) -class TextGenViewModel( - savedStateHandle: SavedStateHandle -) : ViewModel() { - private val sampleId = savedStateHandle.toRoute().sampleId - private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId } - val initialPrompt = sample.initialPrompt?.parts?.first()?.asTextOrNull().orEmpty() - - private val _errorMessage: MutableStateFlow = MutableStateFlow(null) - val errorMessage: StateFlow = _errorMessage - - private val _isLoading = MutableStateFlow(false) - val isLoading: StateFlow = _isLoading - - val allowEmptyPrompt = sample.allowEmptyPrompt - - val templateId = sample.templateId - - val templateKey = sample.templateKey - - private val _generatedText = MutableStateFlow(null) - val generatedText: StateFlow = _generatedText - - // Firebase AI Logic - private val generativeModel: GenerativeModel - private val templateGenerativeModel: TemplateGenerativeModel - - init { - generativeModel = Firebase.ai( - backend = sample.backend // GenerativeBackend.googleAI() by default - ).generativeModel( - modelName = sample.modelName ?: "gemini-2.5-flash", - systemInstruction = sample.systemInstructions, - generationConfig = sample.generationConfig, - tools = sample.tools - ) - templateGenerativeModel = Firebase.ai.templateGenerativeModel() - } - - fun generate(inputText: String) { - viewModelScope.launch { - _isLoading.value = true - _errorMessage.value = null // clear error message - try { - val generativeResponse = if (templateId != null) { - templateGenerativeModel - .generateContent(templateId, mapOf(templateKey!! to inputText)) - } else { - generativeModel.generateContent(inputText) - } - _generatedText.value = generativeResponse.text - } catch (e: Exception) { - val errorMessage = - if ((e.localizedMessage?.contains("not found") == true) && (templateId != null)) { - "Template was not found, please verify that your project contains a" + - " template named \"$templateId\"." - } else { - e.localizedMessage - } - _errorMessage.value = errorMessage - } finally { - _isLoading.value = false - } - } - } -} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ThinkingChatViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ThinkingChatViewModel.kt new file mode 100644 index 0000000000..8b59f9e2a6 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ThinkingChatViewModel.kt @@ -0,0 +1,42 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.generationConfig +import com.google.firebase.ai.type.thinkingConfig +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object ThinkingChatRoute + +class ThinkingChatViewModel : ChatViewModel() { + + override val initialPrompt: String = "Analogize photosynthesis and growing up." + + private val chat: Chat + + init { + val generativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).generativeModel( + modelName = "gemini-2.5-flash", + generationConfig = generationConfig { + thinkingConfig = thinkingConfig { + includeThoughts = true + thinkingBudget = -1 // Dynamic Thinking + } + } + ) + chat = generativeModel.startChat() + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TravelTipsViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TravelTipsViewModel.kt new file mode 100644 index 0000000000..dd3662f811 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TravelTipsViewModel.kt @@ -0,0 +1,70 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.content +import com.google.firebase.quickstart.ai.ui.ChatUiState +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object TravelTipsRoute + +class TravelTipsViewModel : ChatViewModel() { + + override val initialPrompt: String = "What else is important when traveling?" + + private val chat: Chat + + init { + val generativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).generativeModel( + modelName = "gemini-2.5-flash", + systemInstruction = content { + text( + "You are a Travel assistant. You will answer" + + " questions the user asks based on the information listed" + + " in Relevant Information. Do not hallucinate. Do not use" + + " the internet." + ) + } + ) + + chat = generativeModel.startChat( + history = listOf( + content("role") { + text("I have never traveled before. When should I book a flight?") + }, + content("model") { + text( + "You should book flights a couple of months ahead of time." + + " It will be cheaper and more flexible for you." + ) + }, + content("user") { + text("Do I need a passport?") + }, + content("model") { + text( + "If you are traveling outside your own country, make sure" + + " your passport is up-to-date and valid for more" + + " than 6 months during your travel." + ) + } + ) + ) + + _messages.value = chat.history.map { UiChatMessage(it) } + _uiState.value = ChatUiState.Success + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoHashtagGeneratorViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoHashtagGeneratorViewModel.kt new file mode 100644 index 0000000000..ff1b1192b3 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoHashtagGeneratorViewModel.kt @@ -0,0 +1,41 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object VideoHashtagGeneratorRoute + +class VideoHashtagGeneratorViewModel : ChatViewModel() { + + override val initialPrompt: String = "Generate 5-10 hashtags that relate to the video content." + + " Try to use more popular and engaging terms," + + " e.g. #Viral. Do not add content not related to" + + " the video.\n Start the output with 'Tags:'" + + private val chat: Chat + + init { + val generativeModel = Firebase.ai(backend = GenerativeBackend.vertexAI()).generativeModel( + modelName = "gemini-2.5-flash" + ) + chat = generativeModel.startChat() + + // Pre-attach the video + contentBuilder.fileData( + "https://storage.googleapis.com/cloud-samples-data/generative-ai/video/google_home_celebrity_ad.mp4", + "video/mpeg" + ) + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoSummarizationViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoSummarizationViewModel.kt new file mode 100644 index 0000000000..7c5bc64211 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/VideoSummarizationViewModel.kt @@ -0,0 +1,51 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.content +import com.google.firebase.quickstart.ai.ui.ChatUiState +import com.google.firebase.quickstart.ai.ui.UiChatMessage + +@Serializable +object VideoSummarizationRoute + +class VideoSummarizationViewModel : ChatViewModel() { + + override val initialPrompt: String = "I have attached the video file. Provide a description of" + + " the video. The description should also contain" + + " anything important which people say in the video." + + private val chat: Chat + + init { + val chatHistory = listOf( + content { text("Can you help me with the description of a video file?") }, + content("model") { + text( + "Sure! Click on the attach button below and choose a" + + " video file for me to describe." + ) + } + ) + + _messages.value = chatHistory.map { UiChatMessage(it) } + _uiState.value = ChatUiState.Success + + val generativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).generativeModel( + modelName = "gemini-2.5-flash" + ) + chat = generativeModel.startChat(chatHistory) + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/WeatherChatViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/WeatherChatViewModel.kt new file mode 100644 index 0000000000..f91998cf90 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/WeatherChatViewModel.kt @@ -0,0 +1,107 @@ +package com.google.firebase.quickstart.ai.feature.text + +import kotlinx.serialization.Serializable + +import android.util.Log +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.FunctionDeclaration +import com.google.firebase.ai.type.FunctionResponsePart +import com.google.firebase.ai.type.GenerateContentResponse +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.Schema +import com.google.firebase.ai.type.Tool +import com.google.firebase.ai.type.content +import com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository +import com.google.firebase.quickstart.ai.ui.UiChatMessage +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.jsonPrimitive + +@Serializable +object WeatherChatRoute + +class WeatherChatViewModel : ChatViewModel() { + + override val initialPrompt: String = "What was the weather in Boston, MA on October 17, 2024?" + + private val chat: Chat + + init { + val generativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).generativeModel( + modelName = "gemini-2.5-flash", + tools = listOf( + Tool.functionDeclarations( + listOf( + FunctionDeclaration( + "fetchWeather", + "Get the weather conditions for a specific US city on a specific date.", + mapOf( + "city" to Schema.string("The US city of the location."), + "state" to Schema.string("The US state of the location."), + "date" to Schema.string( + "The date for which to get the weather." + + " Date must be in the format: YYYY-MM-DD." + ), + ), + ) + ) + ) + ) + ) + chat = generativeModel.startChat() + } + + override suspend fun performSendMessage(prompt: Content, currentMessages: List) { + val response = chat.sendMessage(prompt) + if (response.functionCalls.isEmpty()) { + validateAndDisplayResponse(response, currentMessages) + } else { + handleFunctionCalls(response, currentMessages) + } + } + + private suspend fun handleFunctionCalls( + response: GenerateContentResponse, + currentMessages: List + ) { + response.functionCalls.forEach { functionCall -> + Log.d( + "WeatherChatViewModel", "Model responded with function call:" + + functionCall.name + ) + when (functionCall.name) { + "fetchWeather" -> { + val city = functionCall.args["city"]?.jsonPrimitive?.content + val state = functionCall.args["state"]?.jsonPrimitive?.content // Fixed state retrieval + val date = functionCall.args["date"]?.jsonPrimitive?.content + + val finalResponse = if (city == null || state == null || date == null) { + chat.sendMessage(content("function") { + part(FunctionResponsePart("fetchWeather", + JsonObject( + mapOf( + "error" to JsonPrimitive("Unable to fetch weather - one of the parameters was null"), + ) + ))) + }) + } else { + val functionResponse = WeatherRepository + .fetchWeather(city, state, date) + + chat.sendMessage(content("function") { + part(FunctionResponsePart("fetchWeather", functionResponse)) + }) + } + + validateAndDisplayResponse(finalResponse, currentMessages) + } + } + } + } +} + diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/CameraView.kt similarity index 93% rename from firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt rename to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/CameraView.kt index 8fcf0258ba..d68c25cb3f 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/CameraView.kt @@ -1,4 +1,4 @@ -package com.google.firebase.quickstart.ai.feature.live +package com.google.firebase.quickstart.ai.ui import android.annotation.SuppressLint import android.graphics.Bitmap @@ -12,10 +12,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner import kotlin.time.Duration.Companion.seconds @Composable @@ -59,7 +59,7 @@ private fun bindPreview( onFrameCaptured: (Bitmap) -> Unit, ) { val preview = - Preview.Builder().build().also { it.setSurfaceProvider(previewView.surfaceProvider) } + Preview.Builder().build().also { it.surfaceProvider = previewView.surfaceProvider } val imageAnalysis = ImageAnalysis.Builder() diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatScreen.kt similarity index 95% rename from firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt rename to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatScreen.kt index ee169e1766..683cfe29dc 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatScreen.kt @@ -1,10 +1,9 @@ -package com.google.firebase.quickstart.ai.feature.text +package com.google.firebase.quickstart.ai.ui import android.annotation.SuppressLint import android.content.Intent -import android.graphics.Bitmap +import android.graphics.Color import android.net.Uri -import androidx.core.net.toUri import android.provider.OpenableColumns import android.text.format.Formatter import android.webkit.WebResourceRequest @@ -12,8 +11,10 @@ import android.webkit.WebView import android.webkit.WebViewClient import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints @@ -38,8 +39,6 @@ import androidx.compose.material.icons.filled.AttachFile import androidx.compose.material.icons.filled.Attachment import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.ExpandMore -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.clickable import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.DropdownMenu @@ -66,34 +65,30 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.webkit.WebSettingsCompat -import androidx.webkit.WebViewFeature import com.google.firebase.ai.type.FileDataPart import com.google.firebase.ai.type.ImagePart import com.google.firebase.ai.type.InlineDataPart import com.google.firebase.ai.type.TextPart import com.google.firebase.ai.type.WebGroundingChunk +import com.google.firebase.quickstart.ai.feature.text.ChatViewModel import kotlinx.coroutines.launch -import kotlinx.serialization.Serializable -@Serializable -class ChatRoute(val sampleId: String) @Composable fun ChatScreen( - chatViewModel: ChatViewModel = viewModel() + chatViewModel: ChatViewModel ) { - val messages: List by chatViewModel.messages.collectAsStateWithLifecycle() - val isLoading: Boolean by chatViewModel.isLoading.collectAsStateWithLifecycle() - val errorMessage: String? by chatViewModel.errorMessage.collectAsStateWithLifecycle() - val attachments: List by chatViewModel.attachments.collectAsStateWithLifecycle() + val uiState by chatViewModel.uiState.collectAsStateWithLifecycle() + val messages by chatViewModel.messages.collectAsStateWithLifecycle() + val attachments by chatViewModel.attachments.collectAsStateWithLifecycle() val initialPrompt: String = chatViewModel.initialPrompt @@ -111,6 +106,7 @@ fun ChatScreen( .fillMaxSize() .weight(0.5f) ) + Box( contentAlignment = Alignment.BottomCenter ) { @@ -119,14 +115,14 @@ fun ChatScreen( .fillMaxWidth() .background(color = MaterialTheme.colorScheme.surfaceContainer) ) { - if (isLoading) { + if (uiState is ChatUiState.Loading) { LinearProgressIndicator( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 8.dp) ) } - errorMessage?.let { + (uiState as? ChatUiState.Error)?.let { Card( colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.errorContainer @@ -134,7 +130,7 @@ fun ChatScreen( modifier = Modifier.fillMaxWidth() ) { Text( - text = it, + text = it.message, modifier = Modifier.padding(16.dp), color = MaterialTheme.colorScheme.onErrorContainer ) @@ -173,7 +169,7 @@ fun ChatScreen( chatViewModel.attachFile(bytes, mimeType, fileName) } }, - isLoading = isLoading + isLoading = uiState is ChatUiState.Loading ) } } @@ -322,7 +318,7 @@ fun ChatBubbleItem( } } - setBackgroundColor(android.graphics.Color.TRANSPARENT) + setBackgroundColor(Color.TRANSPARENT) loadDataWithBaseURL( null, searchEntryPoint.renderedContent, @@ -534,13 +530,6 @@ fun AttachmentsMenu( } } -/** - * Meant to present attachments in the UI - */ -data class Attachment( - val fileName: String, - val image: Bitmap? = null // only for image attachments -) @Composable fun AttachmentsList( @@ -623,7 +612,7 @@ fun ThoughtBubble( Text( text = text.trimIndent(), style = MaterialTheme.typography.bodySmall.copy( - fontStyle = androidx.compose.ui.text.font.FontStyle.Italic + fontStyle = FontStyle.Italic ), modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.onTertiaryContainer diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatUiState.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatUiState.kt new file mode 100644 index 0000000000..4176ca7f25 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ChatUiState.kt @@ -0,0 +1,27 @@ +package com.google.firebase.quickstart.ai.ui + +import android.graphics.Bitmap +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GroundingMetadata + +/** + * Meant to present attachments in the UI + */ +data class Attachment( + val fileName: String, + val image: Bitmap? = null // only for image attachments +) + +/** + * A wrapper for a model [Content] object that includes additional UI-specific metadata. + */ +data class UiChatMessage( + val content: Content, + val groundingMetadata: GroundingMetadata? = null, +) + +sealed interface ChatUiState { + data object Loading : ChatUiState + data object Success : ChatUiState + data class Error(val message: String) : ChatUiState +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenScreen.kt similarity index 91% rename from firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenScreen.kt rename to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenScreen.kt index 451ebe77e4..418d2a5ce3 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenScreen.kt @@ -1,4 +1,4 @@ -package com.google.firebase.quickstart.ai.feature.media.imagen +package com.google.firebase.quickstart.ai.ui import android.net.Uri import android.provider.OpenableColumns @@ -44,25 +44,22 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.google.firebase.quickstart.ai.R -import com.google.firebase.quickstart.ai.feature.text.Attachment -import com.google.firebase.quickstart.ai.feature.text.AttachmentsList +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenViewModel import kotlinx.coroutines.launch -import kotlinx.serialization.Serializable -@Serializable -class ImagenRoute(val sampleId: String) @Composable fun ImagenScreen( - imagenViewModel: ImagenViewModel = viewModel() + imagenViewModel: ImagenViewModel ) { + val uiState by imagenViewModel.uiState.collectAsStateWithLifecycle() + val successState = uiState as? ImagenUiState.Success + val attachedImage = successState?.attachedImage + val generatedImages = successState?.images ?: emptyList() + var imagenPrompt by rememberSaveable { mutableStateOf(imagenViewModel.initialPrompt) } - val errorMessage by imagenViewModel.errorMessage.collectAsStateWithLifecycle() - val isLoading by imagenViewModel.isLoading.collectAsStateWithLifecycle() - val generatedImages by imagenViewModel.generatedBitmaps.collectAsStateWithLifecycle() - val attachedImage by imagenViewModel.attachedImage.collectAsStateWithLifecycle() + val context = LocalContext.current val contentResolver = context.contentResolver val scope = rememberCoroutineScope() @@ -152,7 +149,7 @@ fun ImagenScreen( } - if (isLoading) { + if (uiState is ImagenUiState.Loading) { Box( contentAlignment = Alignment.Center, modifier = Modifier @@ -162,7 +159,7 @@ fun ImagenScreen( CircularProgressIndicator() } } - errorMessage?.let { + (uiState as? ImagenUiState.Error)?.let { Card( modifier = Modifier .padding(horizontal = 16.dp) @@ -173,7 +170,7 @@ fun ImagenScreen( ) ) { Text( - text = it, + text = it.message, color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(all = 16.dp) ) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenUiState.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenUiState.kt new file mode 100644 index 0000000000..907cabd134 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ImagenUiState.kt @@ -0,0 +1,14 @@ +package com.google.firebase.quickstart.ai.ui + +import android.graphics.Bitmap + +sealed interface ImagenUiState { + data object Idle : ImagenUiState + data object Loading : ImagenUiState + data class Success( + val images: List = emptyList(), + val attachedImage: Bitmap? = null, + val selectedOption: String? = null + ) : ImagenUiState + data class Error(val message: String) : ImagenUiState +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptScreen.kt similarity index 67% rename from firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt rename to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptScreen.kt index 26331333bf..a06cb91162 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TextGenScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptScreen.kt @@ -1,29 +1,15 @@ -package com.google.firebase.quickstart.ai.feature.text +package com.google.firebase.quickstart.ai.ui -import android.net.Uri -import android.provider.OpenableColumns -import android.text.format.Formatter -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid -import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ElevatedCard import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField @@ -31,35 +17,47 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel -import com.google.firebase.quickstart.ai.R -import kotlinx.coroutines.launch -import kotlinx.serialization.Serializable +import com.google.firebase.quickstart.ai.feature.text.ServerPromptTemplateViewModel -@Serializable -class TextGenRoute(val sampleId: String) @Composable -fun TextGenScreen( - textGenViewModel: TextGenViewModel = viewModel() +fun ServerPromptScreen( + viewModel: ServerPromptTemplateViewModel ) { - var textPrompt by rememberSaveable { mutableStateOf(textGenViewModel.initialPrompt) } - val errorMessage by textGenViewModel.errorMessage.collectAsStateWithLifecycle() - val isLoading by textGenViewModel.isLoading.collectAsStateWithLifecycle() - val generatedText by textGenViewModel.generatedText.collectAsStateWithLifecycle() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + val isLoading = uiState is ServerPromptUiState.Loading + val errorMessage = (uiState as? ServerPromptUiState.Error)?.message + val generatedText = (uiState as? ServerPromptUiState.Success)?.generatedText + + ServerPromptContent( + initialPrompt = viewModel.initialPrompt, + isLoading = isLoading, + errorMessage = errorMessage, + generatedText = generatedText, + allowEmptyPrompt = viewModel.allowEmptyPrompt, + onGenerate = { viewModel.generate(it) } + ) +} + +@Composable +private fun ServerPromptContent( + initialPrompt: String, + isLoading: Boolean, + errorMessage: String?, + generatedText: String?, + allowEmptyPrompt: Boolean, + onGenerate: (String) -> Unit +) { + var textPrompt by rememberSaveable { mutableStateOf(initialPrompt) } Column( modifier = Modifier.verticalScroll(rememberScrollState()) @@ -82,8 +80,8 @@ fun TextGenScreen( Row() { TextButton( onClick = { - if (textGenViewModel.allowEmptyPrompt || textPrompt.isNotBlank()) { - textGenViewModel.generate(textPrompt) + if (allowEmptyPrompt || textPrompt.isNotBlank()) { + onGenerate(textPrompt) } }, modifier = Modifier.padding(end = 16.dp, bottom = 16.dp) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptUiState.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptUiState.kt new file mode 100644 index 0000000000..d42d318b5a --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/ServerPromptUiState.kt @@ -0,0 +1,8 @@ +package com.google.firebase.quickstart.ai.ui + +sealed interface ServerPromptUiState { + data object Idle : ServerPromptUiState + data object Loading : ServerPromptUiState + data class Success(val generatedText: String? = null) : ServerPromptUiState + data class Error(val message: String) : ServerPromptUiState +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeScreen.kt similarity index 95% rename from firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt rename to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeScreen.kt index 194b04023f..01df3111d9 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeScreen.kt @@ -1,4 +1,4 @@ -package com.google.firebase.quickstart.ai.feature.live +package com.google.firebase.quickstart.ai.ui import android.Manifest import androidx.annotation.RequiresPermission @@ -32,17 +32,15 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.firebase.quickstart.ai.feature.live.BidiViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.serialization.Serializable -@Serializable -class StreamRealtimeRoute(val sampleId: String) @RequiresPermission(Manifest.permission.RECORD_AUDIO) @Composable -fun StreamRealtimeScreen(bidiView: BidiViewModel = viewModel()) { +fun StreamRealtimeScreen(bidiView: BidiViewModel) { val isConversationActive = remember { mutableStateOf(false) } val backgroundColor = MaterialTheme.colorScheme.background diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeVideoScreen.kt similarity index 92% rename from firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt rename to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeVideoScreen.kt index a30c93980c..6b42a8754b 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/StreamRealtimeVideoScreen.kt @@ -1,4 +1,4 @@ -package com.google.firebase.quickstart.ai.feature.live +package com.google.firebase.quickstart.ai.ui import android.Manifest import android.content.pm.PackageManager @@ -24,14 +24,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.core.content.ContextCompat import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.firebase.quickstart.ai.feature.live.BidiViewModel import kotlinx.coroutines.launch -import kotlinx.serialization.Serializable -@Serializable class StreamRealtimeVideoRoute(val sampleId: String) @RequiresPermission(allOf = [Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA]) @Composable -fun StreamRealtimeVideoScreen(bidiView: BidiViewModel = viewModel()) { +fun StreamRealtimeVideoScreen(bidiView: BidiViewModel) { val backgroundColor = MaterialTheme.colorScheme.background val scope = rememberCoroutineScope() diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgScreen.kt similarity index 89% rename from firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgScreen.kt rename to firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgScreen.kt index be745faec2..6f32f98248 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/svg/SvgScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgScreen.kt @@ -1,4 +1,4 @@ -package com.google.firebase.quickstart.ai.feature.svg +package com.google.firebase.quickstart.ai.ui import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -30,21 +30,20 @@ import coil3.compose.SubcomposeAsyncImage import coil3.request.ImageRequest import coil3.request.crossfade import coil3.svg.SvgDecoder +import com.google.firebase.quickstart.ai.feature.text.SvgViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.serialization.Serializable import java.nio.ByteBuffer -@Serializable -class SvgRoute(val sampleId: String) - @Composable fun SvgScreen( - svgViewModel: SvgViewModel = viewModel() + svgViewModel: SvgViewModel ) { - var prompt by rememberSaveable { mutableStateOf(svgViewModel.initialPrompt) } - val errorMessage by svgViewModel.errorMessage.collectAsStateWithLifecycle() - val isLoading by svgViewModel.isLoading.collectAsStateWithLifecycle() - val generatedSvgs by svgViewModel.generatedSvgs.collectAsStateWithLifecycle() + var prompt by rememberSaveable { mutableStateOf("A kitten") } + val uiState by svgViewModel.uiState.collectAsStateWithLifecycle() + + val isLoading = uiState is SvgUiState.Loading + val errorMessage = (uiState as? SvgUiState.Error)?.message + val generatedSvgs = (uiState as? SvgUiState.Success)?.svgs ?: emptyList() Column { ElevatedCard( diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgUiState.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgUiState.kt new file mode 100644 index 0000000000..a3126cf690 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/SvgUiState.kt @@ -0,0 +1,8 @@ +package com.google.firebase.quickstart.ai.ui + +sealed interface SvgUiState { + data object Idle : SvgUiState + data object Loading : SvgUiState + data class Success(val svgs: List = emptyList()) : SvgUiState + data class Error(val message: String) : SvgUiState +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt new file mode 100644 index 0000000000..be38f7b762 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt @@ -0,0 +1,233 @@ +package com.google.firebase.quickstart.ai.ui.navigation + +import com.google.firebase.quickstart.ai.feature.live.StreamAudioViewModel +import com.google.firebase.quickstart.ai.feature.live.StreamVideoViewModel +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.media.imagen.ImagenGenerationRoute +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenGenerationViewModel +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenInpaintingRoute +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenInpaintingViewModel +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenOutpaintingRoute +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenOutpaintingViewModel +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenStyleTransferRoute +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenStyleTransferViewModel +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenSubjectReferenceRoute +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenSubjectReferenceViewModel +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenTemplateRoute +import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenTemplateViewModel +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.CourseRecommendationsRoute +import com.google.firebase.quickstart.ai.feature.text.CourseRecommendationsViewModel +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.ImageGenerationRoute +import com.google.firebase.quickstart.ai.feature.text.ImageGenerationViewModel +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.ThinkingChatRoute +import com.google.firebase.quickstart.ai.feature.text.ThinkingChatViewModel +import com.google.firebase.quickstart.ai.feature.text.TravelTipsRoute +import com.google.firebase.quickstart.ai.feature.text.TravelTipsViewModel +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 + +val FIREBASE_AI_SAMPLES = listOf( + Sample( + title = "Travel tips", + description = "The user wants the model to help a new traveler" + + " with travel tips", + route = TravelTipsRoute, + screenType = ScreenType.CHAT, + viewModelClass = TravelTipsViewModel::class, + categories = listOf(Category.TEXT), + ), + Sample( + title = "Chatbot recommendations for courses", + description = "A chatbot suggests courses for a performing arts program.", + route = CourseRecommendationsRoute, + screenType = ScreenType.CHAT, + viewModelClass = CourseRecommendationsViewModel::class, + categories = listOf(Category.TEXT), + ), + Sample( + title = "Audio Summarization", + description = "Summarize an audio file", + route = AudioSummarizationRoute, + screenType = ScreenType.CHAT, + viewModelClass = AudioSummarizationViewModel::class, + categories = listOf(Category.AUDIO), + ), + Sample( + title = "Translation from audio (Vertex AI)", + description = "Translate an audio file stored in Cloud Storage", + route = AudioTranslationRoute, + screenType = ScreenType.CHAT, + viewModelClass = AudioTranslationViewModel::class, + categories = listOf(Category.AUDIO) + ), + Sample( + title = "Blog post creator (Vertex AI)", + description = "Create a blog post from an image file stored in Cloud Storage.", + route = ImageBlogCreatorRoute, + screenType = ScreenType.CHAT, + viewModelClass = ImageBlogCreatorViewModel::class, + categories = listOf(Category.IMAGE) + ), + Sample( + title = "Imagen 4 - image generation", + description = "Generate images using Imagen 4", + route = ImagenGenerationRoute, + screenType = ScreenType.IMAGEN, + viewModelClass = ImagenGenerationViewModel::class, + categories = listOf(Category.IMAGE) + ), + Sample( + title = "Imagen 3 - Inpainting (Vertex AI)", + description = "Replace part of an image using Imagen 3", + route = ImagenInpaintingRoute, + screenType = ScreenType.IMAGEN, + viewModelClass = ImagenInpaintingViewModel::class, + categories = listOf(Category.IMAGE) + ), + Sample( + title = "Imagen 3 - Outpainting (Vertex AI)", + description = "Expand an image by drawing in more background", + route = ImagenOutpaintingRoute, + screenType = ScreenType.IMAGEN, + viewModelClass = ImagenOutpaintingViewModel::class, + categories = listOf(Category.IMAGE) + ), + Sample( + title = "Imagen 3 - Subject Reference (Vertex AI)", + description = "Generate an image using a referenced subject (must be an animal)", + route = ImagenSubjectReferenceRoute, + screenType = ScreenType.IMAGEN, + viewModelClass = ImagenSubjectReferenceViewModel::class, + categories = listOf(Category.IMAGE) + ), + Sample( + title = "Imagen 3 - Style Transfer (Vertex AI)", + description = "Change the art style of a cat picture using a reference", + route = ImagenStyleTransferRoute, + screenType = ScreenType.IMAGEN, + viewModelClass = ImagenStyleTransferViewModel::class, + categories = listOf(Category.IMAGE) + ), + Sample( + title = "Gemini 2.5 Flash Image (aka nanobanana)", + description = "Generate and/or edit images using Gemini 2.5 Flash Image aka nanobanana", + route = ImageGenerationRoute, + screenType = ScreenType.CHAT, + viewModelClass = ImageGenerationViewModel::class, + categories = listOf(Category.IMAGE) + ), + Sample( + title = "Document comparison (Vertex AI)", + description = "Compare the contents of 2 documents." + + " Only supported by the Vertex AI Gemini API because the documents are stored in Cloud Storage", + route = DocumentComparisonRoute, + screenType = ScreenType.CHAT, + viewModelClass = DocumentComparisonViewModel::class, + categories = listOf(Category.DOCUMENT) + ), + Sample( + title = "Hashtags for a video (Vertex AI)", + description = "Generate hashtags for a video ad stored in Cloud Storage", + route = VideoHashtagGeneratorRoute, + screenType = ScreenType.CHAT, + viewModelClass = VideoHashtagGeneratorViewModel::class, + categories = listOf(Category.VIDEO) + ), + Sample( + title = "Summarize video", + description = "Summarize a video and extract important dialogue.", + route = VideoSummarizationRoute, + screenType = ScreenType.CHAT, + viewModelClass = VideoSummarizationViewModel::class, + categories = listOf(Category.VIDEO) + ), + Sample( + title = "ForecastTalk", + description = "Use bidirectional streaming to get information about" + + " weather conditions for a specific US city on a specific date", + route = StreamRealtimeAudioRoute, + screenType = ScreenType.BIDI, + viewModelClass = StreamAudioViewModel::class, + categories = listOf(Category.LIVE_API, Category.AUDIO, Category.FUNCTION_CALLING) + ), + Sample( + title = "Gemini Live (Video input)", + description = "Use bidirectional streaming to chat with Gemini using your" + + " phone's camera", + route = StreamRealtimeVideoRoute, + screenType = ScreenType.BIDI_VIDEO, + viewModelClass = StreamVideoViewModel::class, + categories = listOf(Category.LIVE_API, Category.VIDEO, Category.FUNCTION_CALLING) + ), + Sample( + title = "Weather Chat", + description = "Use function calling to get the weather conditions" + + " for a specific US city on a specific date.", + route = WeatherChatRoute, + screenType = ScreenType.CHAT, + viewModelClass = WeatherChatViewModel::class, + categories = listOf(Category.TEXT, Category.FUNCTION_CALLING) + ), + Sample( + title = "Grounding with Google Search", + description = "Use Grounding with Google Search to get responses based on up-to-date information from the" + + " web.", + route = GoogleSearchGroundingRoute, + screenType = ScreenType.CHAT, + viewModelClass = GoogleSearchGroundingViewModel::class, + categories = listOf(Category.TEXT, Category.DOCUMENT) + ), + Sample( + title = "Server Prompt Template - Imagen", + description = "Generate an image using a server prompt template. Note that you need to setup the template in " + + "the Firebase console before running this demo.", + route = ImagenTemplateRoute, + screenType = ScreenType.IMAGEN, + viewModelClass = ImagenTemplateViewModel::class, + categories = listOf(Category.IMAGE) + ), + Sample( + title = "Server Prompt Templates - Gemini", + description = "Generate an invoice using server prompt templates. Note that you need to setup the template" + + " in the Firebase console before running this demo.", + route = ServerPromptTemplateRoute, + screenType = ScreenType.SERVER_PROMPT, + viewModelClass = ServerPromptTemplateViewModel::class, + categories = listOf(Category.TEXT), + ), + Sample( + title = "Thinking", + description = "Gemini 2.5 Flash with dynamic thinking", + route = ThinkingChatRoute, + screenType = ScreenType.CHAT, + viewModelClass = ThinkingChatViewModel::class, + categories = listOf(Category.TEXT) + ), + Sample( + title = "SVG Generator", + description = "Use Gemini 3 Flash preview to create SVG illustrations", + route = SvgRoute, + screenType = ScreenType.SVG, + viewModelClass = SvgViewModel::class, + categories = listOf(Category.IMAGE, Category.TEXT) + ) +) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/MainMenuScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/MainMenuScreen.kt index aa14ed497c..74a9f9689a 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/MainMenuScreen.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/MainMenuScreen.kt @@ -27,7 +27,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES val MIN_CARD_SIZE = 180.dp diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt index 3704b2b449..76bb0c934a 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt @@ -1,16 +1,7 @@ package com.google.firebase.quickstart.ai.ui.navigation -import android.graphics.Bitmap -import com.google.firebase.ai.ImagenModel -import com.google.firebase.ai.type.Content -import com.google.firebase.ai.type.GenerationConfig -import com.google.firebase.ai.type.GenerativeBackend -import com.google.firebase.ai.type.ImagenGenerationResponse -import com.google.firebase.ai.type.ImagenInlineImage -import com.google.firebase.ai.type.PublicPreviewAPI -import com.google.firebase.ai.type.Tool -import com.google.firebase.quickstart.ai.feature.media.imagen.EditingMode -import java.util.UUID +import androidx.lifecycle.ViewModel +import kotlin.reflect.KClass enum class Category( val label: String @@ -21,30 +12,23 @@ enum class Category( AUDIO("Audio"), DOCUMENT("Document"), FUNCTION_CALLING("Function calling"), - LIVE_API("LiveAPI Streaming") + LIVE_API("Live API Streaming") +} + +enum class ScreenType { + CHAT, + IMAGEN, + SVG, + SERVER_PROMPT, + BIDI, + BIDI_VIDEO } -@OptIn(PublicPreviewAPI::class) data class Sample( - val id: String = UUID.randomUUID().toString(), // used for navigation val title: String, val description: String, - val navRoute: String, + val route: Any, + val screenType: ScreenType, + val viewModelClass: KClass? = null, val categories: List, - // Optional parameters - val modelName: String? = null, - val backend: GenerativeBackend = GenerativeBackend.googleAI(), - val initialPrompt: Content? = null, - val systemInstructions: Content? = null, - val generationConfig: GenerationConfig? = null, - val chatHistory: List = emptyList(), - val tools: List? = null, - val includeAttach: Boolean = false, - val allowEmptyPrompt: Boolean = false, - val additionalImage: Bitmap? = null, - val imageLabels: List = emptyList(), - val selectionOptions: List = emptyList(), - val editingMode: EditingMode? = null, - val templateId: String? = null, - val templateKey: String? = null, ) From 2691dc7bf2e32b2ae735dcf6642a693a8f24bcb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Mon, 16 Mar 2026 16:19:24 +0000 Subject: [PATCH 49/52] feat(ai-logic): add samples demonstrating Gemini 3.1 Flash-Lite preview (#2769) This should add a new translation sample that uses Gemini 3.1 Flash-Lite preview Also updates the audio summarization sample to use Gemini 3.1 Flash-Lite instead of Gemini 2.5 Flash. --- .../text/AudioSummarizationViewModel.kt | 2 +- .../ai/feature/text/TranslationViewModel.kt | 44 +++++++++++++++++++ .../ai/ui/navigation/FirebaseAISamples.kt | 14 +++++- 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TranslationViewModel.kt diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt index 8ddb16c92a..1c225656db 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/AudioSummarizationViewModel.kt @@ -28,7 +28,7 @@ class AudioSummarizationViewModel : ChatViewModel() { val generativeModel = Firebase.ai( backend = GenerativeBackend.googleAI() ).generativeModel( - modelName = "gemini-2.5-flash" + modelName = "gemini-3.1-flash-lite-preview" ) chat = generativeModel.startChat( listOf( diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TranslationViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TranslationViewModel.kt new file mode 100644 index 0000000000..bc10192e9e --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/TranslationViewModel.kt @@ -0,0 +1,44 @@ +package com.google.firebase.quickstart.ai.feature.text + +import com.google.firebase.Firebase +import com.google.firebase.ai.Chat +import com.google.firebase.ai.ai +import com.google.firebase.ai.type.Content +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.content +import com.google.firebase.quickstart.ai.ui.UiChatMessage +import kotlinx.serialization.Serializable + +@Serializable +object TranslationRoute + +class TranslationViewModel : ChatViewModel() { + override val initialPrompt: String + get() = """ + Translate the following text to Spanish: + Hey, are you down to grab some pizza later? I'm starving! + """.trimIndent() + + private val chat: Chat + + init { + val generativeModel = Firebase.ai( + backend = GenerativeBackend.googleAI() + ).generativeModel( + modelName = "gemini-3.1-flash-lite-preview", + systemInstruction = content { + text("Only output the translated text") + } + ) + + chat = generativeModel.startChat() + } + + override suspend fun performSendMessage( + prompt: Content, + currentMessages: List + ) { + val response = chat.sendMessage(prompt) + validateAndDisplayResponse(response, currentMessages) + } +} \ No newline at end of file diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt index be38f7b762..8bb2cb1539 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt @@ -36,6 +36,8 @@ 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.ThinkingChatRoute import com.google.firebase.quickstart.ai.feature.text.ThinkingChatViewModel +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.TravelTipsRoute import com.google.firebase.quickstart.ai.feature.text.TravelTipsViewModel import com.google.firebase.quickstart.ai.feature.text.VideoHashtagGeneratorRoute @@ -46,6 +48,14 @@ import com.google.firebase.quickstart.ai.feature.text.WeatherChatRoute import com.google.firebase.quickstart.ai.feature.text.WeatherChatViewModel val FIREBASE_AI_SAMPLES = listOf( + Sample( + title = "Translate text", + description = "Use Gemini 3.1 Flash-Lite to translate text", + route = TranslationRoute, + screenType = ScreenType.CHAT, + viewModelClass = TranslationViewModel::class, + categories = listOf(Category.TEXT) + ), Sample( title = "Travel tips", description = "The user wants the model to help a new traveler" + @@ -65,7 +75,7 @@ val FIREBASE_AI_SAMPLES = listOf( ), Sample( title = "Audio Summarization", - description = "Summarize an audio file", + description = "Use Gemini 3.1 Flash Lite to summarize an audio file", route = AudioSummarizationRoute, screenType = ScreenType.CHAT, viewModelClass = AudioSummarizationViewModel::class, @@ -194,7 +204,7 @@ val FIREBASE_AI_SAMPLES = listOf( route = GoogleSearchGroundingRoute, screenType = ScreenType.CHAT, viewModelClass = GoogleSearchGroundingViewModel::class, - categories = listOf(Category.TEXT, Category.DOCUMENT) + categories = listOf(Category.TEXT) ), Sample( title = "Server Prompt Template - Imagen", From 8255a4cf113e64d1d03e3f67500c80d2ea68ae55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:55:45 +0000 Subject: [PATCH 50/52] chore(deps): bump fast-xml-parser in /functions/functions (#2772) Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 5.4.2 to 5.5.7. - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.4.2...v5.5.7) --- updated-dependencies: - dependency-name: fast-xml-parser dependency-version: 5.5.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- functions/functions/package-lock.json | 44 +++++++++++++++++++-------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/functions/functions/package-lock.json b/functions/functions/package-lock.json index 6aa29293da..0396e89827 100644 --- a/functions/functions/package-lock.json +++ b/functions/functions/package-lock.json @@ -1037,9 +1037,9 @@ "optional": true }, "node_modules/fast-xml-builder": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", - "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", "funding": [ { "type": "github", @@ -1047,12 +1047,15 @@ } ], "license": "MIT", - "optional": true + "optional": true, + "dependencies": { + "path-expression-matcher": "^1.1.3" + } }, "node_modules/fast-xml-parser": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.2.tgz", - "integrity": "sha512-pw/6pIl4k0CSpElPEJhDppLzaixDEuWui2CUQQBH/ECDf7+y6YwA4Gf7Tyb0Rfe4DIMuZipYj4AEL0nACKglvQ==", + "version": "5.5.7", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.7.tgz", + "integrity": "sha512-LteOsISQ2GEiDHZch6L9hB0+MLoYVLToR7xotrzU0opCICBkxOPgHAy1HxAvtxfJNXDJpgAsQN30mkrfpO2Prg==", "funding": [ { "type": "github", @@ -1062,8 +1065,9 @@ "license": "MIT", "optional": true, "dependencies": { - "fast-xml-builder": "^1.0.0", - "strnum": "^2.1.2" + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.1.3", + "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" @@ -1916,6 +1920,22 @@ "node": ">= 0.8" } }, + "node_modules/path-expression-matcher": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", + "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", @@ -2279,9 +2299,9 @@ } }, "node_modules/strnum": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", - "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.1.tgz", + "integrity": "sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==", "funding": [ { "type": "github", From 7da89ea2e750c6393fb15e83d2de601862e2abf0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:56:05 +0000 Subject: [PATCH 51/52] Bump form-data from 2.5.2 to 2.5.5 in /functions/functions (#2707) Bumps [form-data](https://github.com/form-data/form-data) from 2.5.2 to 2.5.5. - [Release notes](https://github.com/form-data/form-data/releases) - [Changelog](https://github.com/form-data/form-data/blob/v2.5.5/CHANGELOG.md) - [Commits](https://github.com/form-data/form-data/compare/v2.5.2...v2.5.5) --- updated-dependencies: - dependency-name: form-data dependency-version: 2.5.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- functions/functions/package-lock.json | 145 ++++++++++++++++++++------ 1 file changed, 115 insertions(+), 30 deletions(-) diff --git a/functions/functions/package-lock.json b/functions/functions/package-lock.json index 0396e89827..be0b9779c3 100644 --- a/functions/functions/package-lock.json +++ b/functions/functions/package-lock.json @@ -685,6 +685,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/capitalize-sentence": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/capitalize-sentence/-/capitalize-sentence-0.1.5.tgz", @@ -848,6 +861,20 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexify": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", @@ -903,13 +930,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -923,6 +947,34 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1165,15 +1217,17 @@ } }, "node_modules/form-data": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", - "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "license": "MIT", "optional": true, "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" }, "engines": { @@ -1270,16 +1324,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -1288,6 +1347,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/google-auth-library": { "version": "9.14.2", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.2.tgz", @@ -1345,12 +1417,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1382,10 +1454,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1394,11 +1466,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", + "optional": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -1738,6 +1814,15 @@ "lru-cache": "6.0.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", From ee3c9337887bac373594c8b335442fd8f04c64e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ros=C3=A1rio=20P=2E=20Fernandes?= Date: Mon, 30 Mar 2026 18:02:35 +0100 Subject: [PATCH 52/52] feat(ai-logic): add hybrid on-device inference sample (#2773) --- firebase-ai/app/build.gradle.kts | 3 +- .../firebase/quickstart/ai/MainActivity.kt | 6 + .../quickstart/ai/feature/hybrid/Expense.kt | 10 + .../hybrid/HybridInferenceViewModel.kt | 153 +++++++++++++ .../quickstart/ai/ui/HybridInferenceScreen.kt | 206 ++++++++++++++++++ .../ai/ui/HybridInferenceUiState.kt | 10 + .../ai/ui/navigation/FirebaseAISamples.kt | 10 + .../quickstart/ai/ui/navigation/Sample.kt | 6 +- gradle/libs.versions.toml | 3 +- 9 files changed, 403 insertions(+), 4 deletions(-) create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/hybrid/Expense.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/hybrid/HybridInferenceViewModel.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/HybridInferenceScreen.kt create mode 100644 firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/HybridInferenceUiState.kt diff --git a/firebase-ai/app/build.gradle.kts b/firebase-ai/app/build.gradle.kts index 2a5a4efc4e..6b47e50869 100644 --- a/firebase-ai/app/build.gradle.kts +++ b/firebase-ai/app/build.gradle.kts @@ -12,7 +12,7 @@ android { defaultConfig { applicationId = "com.google.firebase.quickstart.ai" - minSdk = 23 + minSdk = 26 targetSdk = 36 versionCode = 1 versionName = "1.0" @@ -73,6 +73,7 @@ dependencies { // Firebase implementation(platform(libs.firebase.bom)) implementation(libs.firebase.ai) + implementation(libs.firebase.ai.ondevice) // Image loading implementation(libs.coil.compose) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt index 06ee42f8d0..51ed6ce398 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt @@ -27,6 +27,7 @@ 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.HybridInferenceViewModel import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenViewModel import com.google.firebase.quickstart.ai.feature.text.ChatViewModel import com.google.firebase.quickstart.ai.feature.text.ServerPromptTemplateViewModel @@ -36,6 +37,7 @@ import com.google.firebase.quickstart.ai.ui.ImagenScreen 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.SvgScreen import com.google.firebase.quickstart.ai.ui.navigation.FIREBASE_AI_SAMPLES import com.google.firebase.quickstart.ai.ui.navigation.MainMenuScreen @@ -123,6 +125,10 @@ class MainActivity : ComponentActivity() { StreamRealtimeVideoScreen(it) } } + + ScreenType.HYBRID -> { + (vm as? HybridInferenceViewModel)?.let { HybridInferenceScreen(it) } + } } } } diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/hybrid/Expense.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/hybrid/Expense.kt new file mode 100644 index 0000000000..60bf5cf323 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/hybrid/Expense.kt @@ -0,0 +1,10 @@ +package com.google.firebase.quickstart.ai.feature.hybrid + +import kotlinx.serialization.Serializable + +@Serializable +data class Expense( + val name: String, + val price: Double, + val inferenceMode: String = "" +) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/hybrid/HybridInferenceViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/hybrid/HybridInferenceViewModel.kt new file mode 100644 index 0000000000..d0644026cf --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/hybrid/HybridInferenceViewModel.kt @@ -0,0 +1,153 @@ +package com.google.firebase.quickstart.ai.feature.hybrid + +import android.graphics.Bitmap +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.Firebase +import com.google.firebase.ai.InferenceMode +import com.google.firebase.ai.InferenceSource +import com.google.firebase.ai.OnDeviceConfig +import com.google.firebase.ai.ai +import com.google.firebase.ai.ondevice.DownloadStatus +import com.google.firebase.ai.ondevice.FirebaseAIOnDevice +import com.google.firebase.ai.ondevice.OnDeviceModelStatus +import com.google.firebase.ai.type.GenerativeBackend +import com.google.firebase.ai.type.PublicPreviewAPI +import com.google.firebase.ai.type.content +import com.google.firebase.quickstart.ai.ui.HybridInferenceUiState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import java.util.UUID + +@Serializable +object HybridInferenceRoute + +@OptIn(PublicPreviewAPI::class) +class HybridInferenceViewModel : ViewModel() { + private val _uiState = MutableStateFlow( + HybridInferenceUiState( + expenses = listOf( + Expense("Lunch", 15.50, "Example data"), + Expense("Coffee", 4.75, "Example data") + ) + ) + ) + val uiState: StateFlow = _uiState.asStateFlow() + + private val model = Firebase.ai(backend = GenerativeBackend.googleAI()).generativeModel( + modelName = "gemini-3.1-flash-lite-preview", + onDeviceConfig = OnDeviceConfig(mode = InferenceMode.PREFER_ON_DEVICE) + ) + + init { + checkAndDownloadModel() + } + + private fun checkAndDownloadModel() { + viewModelScope.launch { + try { + val status = FirebaseAIOnDevice.checkStatus() + updateStatus(status) + + if (status == OnDeviceModelStatus.DOWNLOADABLE) { + FirebaseAIOnDevice.download().collect { downloadStatus -> + when (downloadStatus) { + is DownloadStatus.DownloadStarted -> { + _uiState.update { it.copy(modelStatus = "Downloading model...") } + } + + is DownloadStatus.DownloadInProgress -> { + val progress = downloadStatus.totalBytesDownloaded + _uiState.update { it.copy(modelStatus = "Downloading: $progress bytes downloaded") } + } + + is DownloadStatus.DownloadCompleted -> { + _uiState.update { it.copy(modelStatus = "Model ready") } + } + + is DownloadStatus.DownloadFailed -> { + _uiState.update { + it.copy( + modelStatus = "Download failed", errorMessage = "Model download failed" + ) + } + } + } + } + } + } catch (e: Exception) { + _uiState.update { it.copy(modelStatus = "Error checking status", errorMessage = e.message) } + } + } + } + + private fun updateStatus(status: OnDeviceModelStatus) { + val statusText = when (status) { + OnDeviceModelStatus.AVAILABLE -> "Model available" + OnDeviceModelStatus.DOWNLOADABLE -> "Model downloadable" + OnDeviceModelStatus.DOWNLOADING -> "Model downloading..." + OnDeviceModelStatus.UNAVAILABLE -> "On-device model unavailable" + else -> "Unknown" + } + _uiState.update { it.copy(modelStatus = statusText) } + } + + fun scanReceipt(bitmap: Bitmap) { + viewModelScope.launch { + _uiState.update { it.copy(isScanning = true, errorMessage = null) } + try { + val prompt = content { + image(bitmap) + text( + """ + Extract the store name and the total price from this receipt. + Output only in JSON format containg 2 fields '{name,price}'. + Do not include any currency signs or backticks or any text around it. + Use dots for decimals. + Examples: + - {"name": "FakeStore", "price": "2.0"} + - {"name": "SomeMarket", "price": "3.5"} + """.trimIndent() + ) + } + + val response = model.generateContent(prompt) + val text = response.text + val inferenceMode = if (response.inferenceSource == InferenceSource.ON_DEVICE) { + "On-device" + } else { + "Cloud" + } + Log.d("HybridVM", "$inferenceMode response: $text") + if (text != null) { + parseAndAddExpense(text, inferenceMode) + } else { + _uiState.update { it.copy(errorMessage = "Could not extract data") } + } + } catch (e: Exception) { + _uiState.update { it.copy(errorMessage = "Error: ${e.message}") } + } finally { + _uiState.update { it.copy(isScanning = false) } + } + } + } + + private fun parseAndAddExpense(text: String, inferenceMode: String) { + val json = text + // The on-device model sometimes outputs backticks, so we remove those + .replace("```json", "") + .replace("```", "") + try { + val newExpense = Json.decodeFromString(json).copy(inferenceMode = inferenceMode) + _uiState.update { it.copy(expenses = it.expenses + newExpense) } + } catch (e: Exception) { + _uiState.update { it.copy(errorMessage = e.localizedMessage) } + } + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/HybridInferenceScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/HybridInferenceScreen.kt new file mode 100644 index 0000000000..dd04de6435 --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/HybridInferenceScreen.kt @@ -0,0 +1,206 @@ +package com.google.firebase.quickstart.ai.ui + +import android.Manifest +import android.content.pm.PackageManager +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.CameraAlt +import androidx.compose.material.icons.filled.ReceiptLong +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.firebase.quickstart.ai.feature.hybrid.HybridInferenceViewModel + +@Composable +fun HybridInferenceScreen( + viewModel: HybridInferenceViewModel = viewModel() +) { + val uiState by viewModel.uiState.collectAsState() + val context = LocalContext.current + + val cameraLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.TakePicturePreview(), + onResult = { bitmap -> + bitmap?.let { viewModel.scanReceipt(it) } + } + ) + + val permissionLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted -> + if (isGranted) { + cameraLauncher.launch(null) + } + } + + Scaffold( + floatingActionButton = { + FloatingActionButton( + onClick = { + val permissionCheckResult = + ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) + if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) { + cameraLauncher.launch(null) + } else { + permissionLauncher.launch(Manifest.permission.CAMERA) + } + }, + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) { + if (uiState.isScanning) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = MaterialTheme.colorScheme.onPrimary, + strokeWidth = 2.dp + ) + } else { + Icon(Icons.Default.CameraAlt, contentDescription = "Scan Receipt") + } + } + } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(16.dp) + ) { + // Model Status Card + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) + ) { + Row( + modifier = Modifier.padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + Icons.Default.ReceiptLong, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSecondaryContainer + ) + Spacer(modifier = Modifier.size(12.dp)) + Column { + Text( + "Hybrid AI Status", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + Text( + uiState.modelStatus, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + "Expenses", + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold + ) + + Spacer(modifier = Modifier.height(8.dp)) + + if (uiState.expenses.isEmpty()) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("No expenses yet. Scan a receipt to add one.", color = Color.Gray) + } + } else { + LazyColumn( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(uiState.expenses) { expense -> + ExpenseItem(expense.name, expense.price, expense.inferenceMode) + } + } + } + + if (uiState.errorMessage != null) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = uiState.errorMessage!!, + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall + ) + } + } + } +} + +@Composable +fun ExpenseItem(name: String, price: Double, inferenceMode: String) { + Card( + modifier = Modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Row( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text( + name, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Medium + ) + if (inferenceMode.isNotEmpty()) { + Text( + inferenceMode, + style = MaterialTheme.typography.labelSmall, + color = Color.Gray + ) + } + } + Text( + "$${String.format("%.2f", price)}", + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) + } + } +} diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/HybridInferenceUiState.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/HybridInferenceUiState.kt new file mode 100644 index 0000000000..eb61e6e2ab --- /dev/null +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/HybridInferenceUiState.kt @@ -0,0 +1,10 @@ +package com.google.firebase.quickstart.ai.ui + +import com.google.firebase.quickstart.ai.feature.hybrid.Expense + +data class HybridInferenceUiState( + val expenses: List = emptyList(), + val isScanning: Boolean = false, + val modelStatus: String = "Checking model status...", + val errorMessage: String? = null +) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt index 8bb2cb1539..a72196be2f 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/FirebaseAISamples.kt @@ -2,6 +2,8 @@ package com.google.firebase.quickstart.ai.ui.navigation import com.google.firebase.quickstart.ai.feature.live.StreamAudioViewModel import com.google.firebase.quickstart.ai.feature.live.StreamVideoViewModel +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.live.StreamRealtimeAudioRoute import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoRoute import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenGenerationRoute @@ -239,5 +241,13 @@ val FIREBASE_AI_SAMPLES = listOf( screenType = ScreenType.SVG, viewModelClass = SvgViewModel::class, categories = listOf(Category.IMAGE, Category.TEXT) + ), + Sample( + title = "Hybrid Receipt Scanner", + description = "Use hybrid inference to scan receipts and extract expense data on-device whenever possible.", + route = HybridInferenceRoute, + screenType = ScreenType.HYBRID, + viewModelClass = HybridInferenceViewModel::class, + categories = listOf(Category.TEXT, Category.IMAGE, Category.HYBRID) ) ) diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt index 76bb0c934a..a51b56315b 100644 --- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt +++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt @@ -12,7 +12,8 @@ enum class Category( AUDIO("Audio"), DOCUMENT("Document"), FUNCTION_CALLING("Function calling"), - LIVE_API("Live API Streaming") + LIVE_API("Live API Streaming"), + HYBRID("Hybrid inference") } enum class ScreenType { @@ -21,7 +22,8 @@ enum class ScreenType { SVG, SERVER_PROMPT, BIDI, - BIDI_VIDEO + BIDI_VIDEO, + HYBRID } data class Sample( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0afc49131d..5fc9de341f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ composeBom = "2025.12.00" composeNavigation = "2.9.6" coreKtx = "1.17.0" espressoCore = "3.7.0" -firebaseBom = "34.7.0" +firebaseBom = "34.11.0" googleServices = "4.4.4" firebaseCrashlytics = "3.0.6" firebasePerf = "2.0.2" @@ -52,6 +52,7 @@ coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil3Compose" } compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"} firebase-ai = { module = "com.google.firebase:firebase-ai" } +firebase-ai-ondevice = { module = "com.google.firebase:firebase-ai-ondevice", version = "16.0.0-beta01" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } junit = { group = "junit", name = "junit", version.ref = "junit" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" }