RRTutors
Android & AI Module 1: Core Architecture

Introduction to Google ADK: Building Code-First AI Agents in Kotlin

By RR Tutors Editorial Updated July 2026

As artificial intelligence shifts from passive chatbots to autonomous systems, developers need tools that treat AI agents as deterministic software components rather than unpredictable black boxes. While early-stage prompt engineering relied heavily on drag-and-drop graphical user interfaces (GUIs), enterprise scaling demands a code-first approach.

Google's Agent Development Kit (ADK) for Kotlin and Android provides an open-source, type-safe framework that lets you weave deterministic business logic with adaptive AI reasoning seamlessly within your native architectures.

Why Code-First Beats GUI Agent Builders for Enterprise Scale

Visual drag-and-drop builders are excellent for rapid prototyping, but they break down quickly under enterprise production demands:

  • No Version Control: GUI workflows live inside proprietary databases. They cannot be easily diffed, code-reviewed, or rolled back using standard Git Git-flow patterns.
  • Brittle Prompt Structures: Visual interfaces mask underlying prompt chains. Minor prompt tweaks can trigger cascading failures across complex nodes with no automated way to run regression tests.
  • Isolation from the App Architecture: A production-grade app requires its AI components to live safely behind a data layer. GUIs force tight coupling between the agent logic and vendor ecosystems, making it highly complex to inject custom local tools, handle dependencies (like Hilt), or implement clean architecture patterns (like MVI/MVVM).

Google ADK treats an AI agent as a standard repository or service object. It separates the agent orchestration loop from your UI, allowing it to adapt effortlessly to model updates—whether running on a cloud cluster with Gemini 2.5 Flash or on-device via Gemini Nano.

Step-by-Step Implementation: Configuring ADK in Android

Let's configure a native Android application to run Google ADK utilizing Kotlin Symbol Processing (KSP) to auto-generate agent tool schemas at compile time.

1. Add Dependencies & KSP Plugin

Open your app-level build.gradle.kts file and configure the KSP plugin and ADK dependencies.

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    // Apply Kotlin Symbol Processing (KSP)
    id("com.google.devtools.ksp") version "2.0.21-1.0.26" 
}

dependencies {
    // Core Google ADK dependencies
    implementation("com.google.adk:google-adk-kotlin-core-android:0.1.0")
    ksp("com.google.adk:google-adk-kotlin-processor:0.1.0")
    
    // Core Android X dependencies
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
}

2. Secure Your Gemini API Key

Never hardcode your Gemini API credentials in plain text. Secure them within your project's local local.properties file and load them via BuildConfig.

Add this to your local.properties (and verify this file is safely included in your .gitignore):

GEMINI_API_KEY=AIzaSyYourActualSecureKeyHere...

Then, expose it through your app's build.gradle.kts:

android {
    namespace = "com.example.adkdemoapp"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.adkdemoapp"
        minSdk = 26
        targetSdk = 34
        
        // Load API key safely from local.properties
        val localProperties = java.util.Properties()
        val localPropertiesFile = project.rootProject.file("local.properties")
        if (localPropertiesFile.exists()) {
            localPropertiesFile.inputStream().use { localProperties.load(it) }
        }
        val apiKey = localProperties.getProperty("GEMINI_API_KEY") ?: ""
        buildConfigField("String", "GEMINI_API_KEY", "\"$apiKey\"")
    }

    buildFeatures {
        buildConfig = true
    }
}

Initializing Your First Root Agent with LlmAgent

The mental model of a Google ADK agent revolves around three core pillars:

  1. The Model: The language model engine handling execution logic.
  2. The Instructions: The immutable behavioral guidelines assigned to the agent.
  3. The Tools: Explicit capabilities or native functions the model can execute.

Let’s implement a single, production-ready Root Agent. We enclose this agent definition inside a standard Kotlin object using @JvmField. This allows ADK's processor to seamlessly recognize it during the build lifecycle.

package com.example.adkdemoapp.agents

import com.example.adkdemoapp.BuildConfig
import com.google.adk.kt.agents.Instruction
import com.google.adk.kt.agents.LlmAgent
import com.google.adk.kt.models.Gemini
import com.google.adk.kt.tools.GoogleSearchTool

object PrimaryAssistantAgent {

    @JvmField
    val rootAgent = LlmAgent(
        name = "primary_support_agent",
        description = "An enterprise agent configured to handle structural app navigation and data lookup tasks.",
        model = Gemini(
            name = "gemini-2.5-flash",
            apiKey = BuildConfig.GEMINI_API_KEY
        ),
        instruction = Instruction(
            """
            You are the primary enterprise assistant for this mobile application. 
            Your behavior must align strictly with the following parameters:
            1. Maintain a professional, clear, and highly concise tone.
            2. When answering user inquiries, prioritize facts returned by your explicitly attached tools.
            3. If a request falls outside of app operations or structural data constraints, gracefully state your boundary limitations.
            """.trimIndent()
        ),
        // Providing the agent with native tool execution power
        tools = listOf(GoogleSearchTool())
    )
}

Key Architectural Takeaways

  • Deterministic Boundaries: By explicitly passing Instruction(...), you set strict behavioral guardrails directly inside the code layer, making it easy to track, version control, and unit-test prompt iterations.
  • Separation of Concerns: The LlmAgent initialization stays isolated inside your data layer. Your UI surfaces and ViewModels do not need to know which model flavor or api keys are processing under the hood—they simply talk to an abstraction layer handling your user session.