diff --git a/build.gradle.kts b/build.gradle.kts index 5d187bfa..a1157c7f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,6 +25,7 @@ plugins { id("com.google.cloud.artifactregistry.gradle-plugin") version "2.2.4" apply false kotlin("plugin.serialization") version "2.3.21" apply false alias(libs.plugins.gradle.test.retry) apply false + alias(libs.plugins.google.services) apply false } val jdkVersion = providers.gradleProperty("jdkVersion").getOrElse("17").toInt() diff --git a/examples/firebase/.gitignore b/examples/firebase/.gitignore new file mode 100644 index 00000000..0bf03144 --- /dev/null +++ b/examples/firebase/.gitignore @@ -0,0 +1,5 @@ +# Ignored in THIS sample so forks point at their own Firebase project instead of ours -- the file +# just identifies a specific project. Its contents are not secret (the Firebase config/API key is +# public by design and ships inside the APK), so your own app is free to commit it if you prefer. +# Drop your own file here (see README.md); the google-services Gradle plugin picks it up. +google-services.json diff --git a/examples/firebase/README.md b/examples/firebase/README.md new file mode 100644 index 00000000..92cc4946 --- /dev/null +++ b/examples/firebase/README.md @@ -0,0 +1,82 @@ +# ADK Kotlin — Firebase AI example + +A minimal Android app showing an ADK [`LlmAgent`] backed by the Firebase AI +(Gemini) model from the [`:google-adk-kotlin-firebase`](../../firebase) module. +The chat screen demonstrates the two things the module's +`FirebaseIntegrationTest` proves work: + +- **plain chat** — e.g. *"Tell me about the planet Earth"*. +- **tool calling** — e.g. *"What is the current temperature in Mountain + View?"*, which makes the model call the `get_current_temperature` tool (see + [`WeatherTools.kt`](src/main/kotlin/com/google/adk/kt/examples/firebase/WeatherTools.kt)). + +Because Firebase AI needs an Android `Context`, this is a runnable Android app +rather than a JVM `main()` sample. + +## Configure Firebase + +The app needs to know which Firebase project to talk to. Two ways, in order of +preference: + +### 1. `google-services.json` (standard Firebase setup — recommended) + +This mirrors what a normal Firebase Android developer does. + +1. In the [Firebase console](https://console.firebase.google.com/), open your + project (or create one) and enable the **Firebase AI Logic** / Gemini API. +2. Register an **Android app** with the package name + **`com.google.adk.kt.examples.firebase`** (this is the `applicationId` in + [`build.gradle.kts`](build.gradle.kts); change both if you prefer your own). +3. Download the generated `google-services.json` and place it in **this + directory** (`examples/firebase/google-services.json`). + +This file just points the app at a specific Firebase project; its contents are +**not secret** — the Firebase config/API key is +[public by design](https://firebase.google.com/docs/projects/api-keys) and ships +inside the APK anyway, so a normal app can commit it (and often does, especially +in a private repo). It's **git-ignored here only** so that forks of this sample +use their own Firebase project instead of ours. When present, the +`com.google.gms.google-services` Gradle plugin is applied automatically and +initializes the default `FirebaseApp` for you. + +### 2. Build-time Firebase config (fallback) + +If you don't have a `google-services.json`, supply the three values directly and +they are baked into the APK's manifest at build time. Pass them as Gradle +properties: + +```shell +./gradlew :google-adk-kotlin-examples-firebase:installDebug \ + -PFIREBASE_API_KEY=your_api_key \ + -PFIREBASE_APP_ID=your_app_id \ + -PFIREBASE_PROJECT_ID=your_project_id +``` + +or export the matching `FIREBASE_API_KEY` / `FIREBASE_APP_ID` / +`FIREBASE_PROJECT_ID` environment variables before building. (These are read at +build time, not from the device's environment.) + +If neither method provides a configuration, the Gradle build prints a warning, +and the installed app starts but shows a message explaining what to add instead +of calling Firebase with a blank config. + +## Build & run + +With a device or emulator connected: + +```shell +./gradlew :google-adk-kotlin-examples-firebase:installDebug +``` + +Then launch **"ADK Firebase Example"** from the launcher. To change the model, +edit `MODEL_NAME` in +[`FirebaseChatAgent.kt`](src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatAgent.kt). + +> Note: the `FIREBASE_*` values (like those in `google-services.json`) are +> project *identifiers*, not secrets — they're public by design and always end +> up inside the APK. What you must keep out of the app and the repo is a +> genuinely secret key, such as a Gemini Developer API key or an Admin SDK +> service-account key; this sample uses neither (it talks to the model through +> Firebase AI Logic). + +[`LlmAgent`]: ../../core/src/main/kotlin/com/google/adk/kt/agents/LlmAgent.kt diff --git a/examples/firebase/build.gradle.kts b/examples/firebase/build.gradle.kts new file mode 100644 index 00000000..e9498ee4 --- /dev/null +++ b/examples/firebase/build.gradle.kts @@ -0,0 +1,114 @@ +/* + * Copyright 2026 Google LLC + * + * 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 + * + * http://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. + */ + +plugins { + id("com.android.application") + alias(libs.plugins.ksp) +} + +// Standard Firebase developer setup: the `com.google.gms.google-services` plugin reads a +// `google-services.json` from this module's root and generates the default-FirebaseApp +// configuration so `FirebaseApp.getInstance()` works with zero code. The plugin is applied only +// when a developer has dropped their own file in. When it is absent the module still builds and the +// sample falls back to the FIREBASE_* environment variables (see FirebaseChatActivity). Read +// README.md for setup. +if (file("google-services.json").exists()) { + apply(plugin = "com.google.gms.google-services") +} + +// The build-time fallback config keys, read as Gradle properties or environment variables. Shared +// by the manifest-placeholder loop and the "no configuration" diagnostic below. +val firebaseConfigKeys = listOf("FIREBASE_API_KEY", "FIREBASE_APP_ID", "FIREBASE_PROJECT_ID") + +android { + namespace = "com.google.adk.kt.examples.firebase" + + compileSdk { version = release(36) { minorApiLevel = 1 } } + + defaultConfig { + applicationId = "com.google.adk.kt.examples.firebase" + minSdk = rootProject.extra["androidMinSdk"] as Int + targetSdk = 36 + versionCode = 1 + versionName = "0.1.0" + + // Fallback Firebase-config path (see FirebaseChatActivity): when no google-services.json is + // present, the Firebase config can instead be baked into the APK at build time as manifest + // metadata, sourced from Gradle properties (-PFIREBASE_API_KEY=...) or the matching + // environment variables. + for (name in firebaseConfigKeys) { + manifestPlaceholders[name] = + providers + .gradleProperty(name) + .orElse(providers.environmentVariable(name)) + .getOrElse("\${$name}") + } + } + + packaging { + resources { + merges += "**/META-INF/INDEX.LIST" + merges += "**/META-INF/DEPENDENCIES" + } + } +} + +// Build-time diagnostic: if neither a google-services.json nor a complete set of FIREBASE_* values +// is present, the produced APK has no usable Firebase configuration and the app just shows a "no +// configuration" message at runtime. +run { + val hasGoogleServicesJson = file("google-services.json").exists() + val missingKeys = firebaseConfigKeys.filter { name -> + providers + .gradleProperty(name) + .orElse(providers.environmentVariable(name)) + .orNull + .isNullOrBlank() + } + if (!hasGoogleServicesJson && missingKeys.isNotEmpty()) { + val projectPath = project.path + val log = logger + val message = + "examples/firebase: building without a usable Firebase configuration. The app will " + + "build and launch but show a \"No Firebase configuration found\" message instead of " + + "calling Firebase. Add a google-services.json to examples/firebase/, or pass " + + "-PFIREBASE_API_KEY=... -PFIREBASE_APP_ID=... -PFIREBASE_PROJECT_ID=... " + + "(missing: ${missingKeys.joinToString()}). See examples/firebase/README.md." + // Fire only when this app's build/install tasks are actually scheduled (not on every Gradle + // configuration). + gradle.taskGraph.addTaskExecutionGraphListener { graph -> + val buildingThisApp = + graph.allTasks.any { task -> + task.path.startsWith("$projectPath:") && + listOf("assemble", "install", "bundle", "package").any { task.name.startsWith(it) } + } + if (buildingThisApp) log.warn("WARNING: $message") + } + } +} + +dependencies { + implementation(project(":google-adk-kotlin-core")) + implementation(project(":google-adk-kotlin-firebase")) + implementation(platform(libs.google.firebase.platform)) + implementation(libs.google.firebase.ai) + implementation(libs.kotlinx.coroutines.core) + // ViewCompat / WindowInsetsCompat for edge-to-edge inset handling across all API levels. + implementation(libs.androidx.core) + + // Generates the `@Tool` FunctionTools used by the weather agent (WeatherTools.generatedTools()). + ksp(project(":google-adk-kotlin-processor")) +} diff --git a/examples/firebase/src/main/AndroidManifest.xml b/examples/firebase/src/main/AndroidManifest.xml new file mode 100644 index 00000000..5e618c34 --- /dev/null +++ b/examples/firebase/src/main/AndroidManifest.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseAppResolver.kt b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseAppResolver.kt new file mode 100644 index 00000000..132a3a43 --- /dev/null +++ b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseAppResolver.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2026 Google LLC + * + * 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 + * + * http://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. + */ + +package com.google.adk.kt.examples.firebase + +import android.content.Context +import android.content.pm.PackageManager +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions + +/** + * Resolves the [FirebaseApp] this sample talks to. This is Firebase setup plumbing, kept out of + * [FirebaseChatActivity] so the activity shows only how ADK is used (build an agent, run it). + * + * Resolution order: + * 1. Standard setup: a `google-services.json` in this module's root, processed by the + * `com.google.gms.google-services` Gradle plugin, auto-initializes the default [FirebaseApp] at + * process start. In a normal Firebase app this is all you need — [resolve] just returns it and + * the rest of this class is irrelevant. + * 2. Fallback: the `FIREBASE_API_KEY` / `FIREBASE_APP_ID` / `FIREBASE_PROJECT_ID` values baked into + * the APK manifest at build time (see `build.gradle.kts` / `AndroidManifest.xml`), used to build + * a named [FirebaseApp] on demand. + * + * Returns null when neither is available; see README.md for setup. + */ +internal object FirebaseAppResolver { + + /** Prefix of the manifest metadata keys holding the build-time fallback config. */ + private const val META_DATA_PREFIX = "com.google.adk." + + /** Non-default FirebaseApp name used for the build-time-config fallback path. */ + private const val FALLBACK_APP_NAME = "adk-firebase-example" + + /** + * Resolves a usable [FirebaseApp], or null if neither the standard nor fallback config exists. + */ + fun resolve(context: Context): FirebaseApp? { + // 1) Standard path: google-services.json + the google-services plugin auto-initialize the + // default FirebaseApp at process start (via Firebase's init ContentProvider). + runCatching { FirebaseApp.getInstance() } + .getOrNull() + ?.let { + return it + } + + // 2) Fallback path: reuse the named app if an earlier call already built it, otherwise assemble + // one from the FIREBASE_* manifest meta-data baked in at build time. + runCatching { FirebaseApp.getInstance(FALLBACK_APP_NAME) } + .getOrNull() + ?.let { + return it + } + val apiKey = bakedMetaData(context, "FIREBASE_API_KEY") ?: return null + val appId = bakedMetaData(context, "FIREBASE_APP_ID") ?: return null + val projectId = bakedMetaData(context, "FIREBASE_PROJECT_ID") ?: return null + + return FirebaseApp.initializeApp( + context.applicationContext, + FirebaseOptions.Builder() + .setApiKey(apiKey) + .setApplicationId(appId) + .setProjectId(projectId) + .build(), + FALLBACK_APP_NAME, + ) + } + + /** + * Reads the `${[META_DATA_PREFIX]}${[token]}` application manifest metadata entry. Returns null + * if it is missing, blank, or still holds its unresolved `${[token]}` build placeholder (i.e. no + * value was supplied at build time). + */ + private fun bakedMetaData(context: Context, token: String): String? { + val raw = + try { + val appInfo = + context.packageManager.getApplicationInfo( + context.packageName, + PackageManager.GET_META_DATA, + ) + appInfo.metaData?.getString(META_DATA_PREFIX + token) + } catch (_: PackageManager.NameNotFoundException) { + null + } + return raw?.takeIf { it.isNotBlank() && !it.contains(token) } + } +} diff --git a/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatActivity.kt b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatActivity.kt new file mode 100644 index 00000000..e9ec0e03 --- /dev/null +++ b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatActivity.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2026 Google LLC + * + * 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 + * + * http://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. + */ + +package com.google.adk.kt.examples.firebase + +import android.app.Activity +import android.os.Bundle +import android.view.Gravity +import android.widget.Button +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.ScrollView +import android.widget.TextView +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.google.adk.kt.runners.InMemoryRunner +import com.google.adk.kt.sessions.InMemorySessionService +import com.google.adk.kt.types.Content +import com.google.adk.kt.types.Part +import com.google.adk.kt.types.Role +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + +/** + * Minimal Android example: an [com.google.adk.kt.agents.LlmAgent] backed by the Firebase AI + * (Gemini) model from the `:google-adk-kotlin-firebase` module. The chat UI exercises both plain + * conversation and tool calling (via [WeatherTools]). + * + * The Firebase-setup plumbing lives in [FirebaseAppResolver]; the agent wiring lives in + * [FirebaseChatAgent]. What remains here is the typical ADK usage: build an [InMemoryRunner] around + * an agent and drive it with [InMemoryRunner.runAsync]. + */ +// Hardcoded UI strings are intentional in this minimal example; a real app would use resources. +@Suppress("SetTextI18n") +class FirebaseChatActivity : Activity() { + + // Coroutines launch on the default dispatcher; UI updates are marshaled via runOnUiThread. + private val scope = CoroutineScope(SupervisorJob()) + + private val sessionService = InMemorySessionService() + + /** + * Built in onCreate once a FirebaseApp is resolved; null means the agent could not be created. + */ + private var runner: InMemoryRunner? = null + + private lateinit var transcript: TextView + private lateinit var input: EditText + private lateinit var sendButton: Button + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(buildUi()) + + val firebaseApp = FirebaseAppResolver.resolve(applicationContext) + if (firebaseApp == null) { + appendToTranscript( + "No Firebase configuration found. Add a google-services.json to the " + + "examples/firebase/ module (standard setup), or rebuild supplying " + + "-PFIREBASE_API_KEY=... -PFIREBASE_APP_ID=... -PFIREBASE_PROJECT_ID=... " + + "(or the matching environment variables). See the module README.md." + ) + sendButton.isEnabled = false + return + } + + runner = + try { + InMemoryRunner( + agent = FirebaseChatAgent.create(firebaseApp), + appName = APP_NAME, + sessionService = sessionService, + ) + } catch (e: Throwable) { + appendToTranscript("Failed to build agent: ${e.message ?: e::class.simpleName}") + sendButton.isEnabled = false + return + } + + appendToTranscript( + "Ready. Try: \"Tell me about the planet Earth\" or " + + "\"What is the current temperature in Mountain View?\"" + ) + } + + private fun sendToAgent(text: String) { + val activeRunner = runner ?: return + appendToTranscript("you: $text") + scope.launch { + try { + activeRunner + .runAsync( + userId = USER_ID, + sessionId = SESSION_ID, + newMessage = Content(role = Role.USER, parts = listOf(Part(text = text))), + ) + .collect { event -> + val reply = event.content?.parts?.mapNotNull { it.text }?.joinToString(" ").orEmpty() + if (event.author == FirebaseChatAgent.NAME && reply.isNotBlank()) { + appendToTranscript("${FirebaseChatAgent.NAME}: $reply") + } + } + } catch (e: Exception) { + appendToTranscript("Error: ${e.message ?: e::class.simpleName}") + } + } + } + + private fun appendToTranscript(line: String) { + runOnUiThread { transcript.append("$line\n\n") } + } + + private fun buildUi(): LinearLayout { + transcript = TextView(this).apply { setPadding(24, 24, 24, 24) } + val scroll = + ScrollView(this).apply { + layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f) + addView(transcript) + } + input = + EditText(this).apply { + hint = "Type a message…" + layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) + } + sendButton = + Button(this).apply { + text = "Send" + setOnClickListener { + val text = input.text.toString() + if (text.isNotBlank()) { + input.text.clear() + sendToAgent(text) + } + } + } + val inputRow = + LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + addView(input) + addView(sendButton) + } + val root = + LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + addView(scroll) + addView(inputRow) + } + ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets -> + val bars = + insets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()) + view.setPadding(bars.left, bars.top, bars.right, bars.bottom) + insets + } + return root + } + + private companion object { + const val APP_NAME = "FirebaseChatExample" + const val USER_ID = "local-user" + const val SESSION_ID = "local-session" + } +} diff --git a/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatAgent.kt b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatAgent.kt new file mode 100644 index 00000000..9a52ef7f --- /dev/null +++ b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/FirebaseChatAgent.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2026 Google LLC + * + * 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 + * + * http://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. + */ + +package com.google.adk.kt.examples.firebase + +import com.google.adk.firebase.models.Firebase +import com.google.adk.kt.agents.Instruction +import com.google.adk.kt.agents.LlmAgent +import com.google.firebase.FirebaseApp +import com.google.firebase.ai.FirebaseAI + +/** + * Builds the [LlmAgent] used by [FirebaseChatActivity], backed by the Firebase AI (Gemini) model + * from the `:google-adk-kotlin-firebase` module. + * + * The agent both answers general questions (plain chat) and can call [WeatherTools] to look up a + * temperature, mirroring the two scenarios proven by the module's `FirebaseIntegrationTest`. + */ +internal object FirebaseChatAgent { + const val NAME: String = "firebase_agent" + + /** + * The Firebase AI model to use. Any model available to your Firebase project works; override it + * if this one is not enabled for you. + */ + private const val MODEL_NAME: String = "gemini-3.5-flash" + + /** Builds the agent against the given (already initialized) [firebaseApp]. */ + fun create(firebaseApp: FirebaseApp): LlmAgent = + LlmAgent( + name = NAME, + model = Firebase.create(MODEL_NAME, FirebaseAI.getInstance(firebaseApp)), + instruction = + Instruction( + """ + You are a helpful assistant. Answer general questions in one or two sentences. When the + user asks about the current temperature somewhere, call the get_current_temperature tool + and state the exact value it returns. + """ + .trimIndent() + ), + tools = WeatherTools().generatedTools(), + ) +} diff --git a/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/WeatherTools.kt b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/WeatherTools.kt new file mode 100644 index 00000000..0cb71401 --- /dev/null +++ b/examples/firebase/src/main/kotlin/com/google/adk/kt/examples/firebase/WeatherTools.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2026 Google LLC + * + * 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 + * + * http://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. + */ + +package com.google.adk.kt.examples.firebase + +import com.google.adk.kt.annotations.Param +import com.google.adk.kt.annotations.Tool + +/** + * A trivial tool exposed to the Firebase-backed agent through the KSP `@Tool` processor. Calling + * [com.google.adk.kt.examples.firebase.WeatherTools.generatedTools] (generated by the processor) + * turns this into a `FunctionTool` the model can invoke. + * + * It always returns the same made-up temperature: a value the model cannot know on its own, which + * makes it obvious in the transcript that the tool was actually called rather than the answer being + * hallucinated. + */ +class WeatherTools { + @Tool( + name = "get_current_temperature", + description = "Returns the current temperature in Celsius for a given location.", + ) + fun getCurrentTemperature( + @Param("The city to look up, e.g. 'Mountain View'.") location: String + ): Map = mapOf("temperature_celsius" to 42) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3d596f53..39b2202a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ a2a = "0.3.2.Final" a2a-legacy = "0.3.3.Final" androidx-compose-ui = "1.11.2" +androidx-core = "1.16.0" androidx-room = "2.8.4" androidx-test-core = "1.5.0" androidx-test-espresso = "3.7.0" @@ -19,6 +20,7 @@ google-genai-kotlin = "0.2.0" google-gson = "2.10.1" google-mlkit-genai = "1.0.0-beta2" google-protobuf-javalite = "3.25.5" +google-services = "4.4.4" google-truth = "1.4.5" gradle-test-retry = "1.6.5" graphviz = "0.18.1" @@ -56,6 +58,7 @@ a2a-sdk-transport-jsonrpc = { module = "io.github.a2asdk:a2a-java-sdk-transport- a2a-sdk-transport-rest = { module = "io.github.a2asdk:a2a-java-sdk-transport-rest", version.ref = "a2a" } androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-compose-ui" } androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-compose-ui" } +androidx-core = { module = "androidx.core:core", version.ref = "androidx-core" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" } @@ -113,6 +116,7 @@ snakeyaml = { module = "org.yaml:snakeyaml", version.ref = "snakeyaml" } [plugins] # go/keep-sorted start dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } +google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } gradle-test-retry = { id = "org.gradle.test-retry", version.ref = "gradle-test-retry"} ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } # go/keep-sorted end diff --git a/settings.gradle.kts b/settings.gradle.kts index dda5f8e1..5898c2a4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -61,3 +61,7 @@ project(":google-adk-kotlin-litertlm").projectDir = file("litertlm") include(":google-adk-kotlin-examples-android") project(":google-adk-kotlin-examples-android").projectDir = file("examples/android") + +include(":google-adk-kotlin-examples-firebase") + +project(":google-adk-kotlin-examples-firebase").projectDir = file("examples/firebase")