GEMINI LABJP
TTS — gemini-3.1-flash-tts-preview now streams speech generation via streamGenerateContent for lower latencyTRANSLATE — Gemini 3.5 Live Translate arrives, auto-detecting 70+ languages for speech-to-speech while preserving intonationIMAGE — Nano Banana 2 Lite launches as the fastest and most cost-efficient Gemini image modelOMNI — Gemini Omni Flash enters public preview as a natively multimodal model for custom video workflowsMODEL — Gemini 3.5 Flash reaches GA and now powers gemini-flash-latestAGENT — Managed Agents enter public preview in the Gemini API, running in isolated Google-hosted Linux sandboxesTTS — gemini-3.1-flash-tts-preview now streams speech generation via streamGenerateContent for lower latencyTRANSLATE — Gemini 3.5 Live Translate arrives, auto-detecting 70+ languages for speech-to-speech while preserving intonationIMAGE — Nano Banana 2 Lite launches as the fastest and most cost-efficient Gemini image modelOMNI — Gemini Omni Flash enters public preview as a natively multimodal model for custom video workflowsMODEL — Gemini 3.5 Flash reaches GA and now powers gemini-flash-latestAGENT — Managed Agents enter public preview in the Gemini API, running in isolated Google-hosted Linux sandboxes
Articles/API / SDK
API / SDK/2026-06-23Advanced

Integrating Gemini 3.2 Pro Function Calling into iOS/Android Apps: Production Design Patterns

A practical guide to integrating Gemini 3.2 Pro Function Calling into iOS and Android apps. Includes working SwiftUI, Kotlin, and Python code, plus production patterns proven in a real indie wallpaper app — cost, latency, staged rollout, and regression testing.

gemini-api264function-calling19ios12android7swiftui4kotlin4indie-dev40mobile4

Premium Article

User reviews started changing around late 2025. The wallpaper apps I built were no longer getting feedback like "love the images" — instead, people were writing things like "wish it remembered my preferences" or "can it understand what mood I'm in today?"

Having run wallpaper apps as an indie developer for a long time, I've learned to notice when user expectations shift at a structural level. This felt like one of those shifts. The ask was no longer "show me nice images." It was "know me well enough to surprise me."

My first instinct was a recommendation engine. I'd built them before — collaborative filtering, content-based matching, hybrid approaches. They work, but they have a ceiling: they're great at finding things that resemble what you've already liked, and weak at navigating the fuzzy space of "like this, but different." More importantly, they require me to anticipate every variation of user intent at development time, encoding it as rules or features. When the intent is expressed in natural language — "show me something calming but not what I always look at" — rule-based systems struggle with the combinatorial space.

That's when I started seriously testing Gemini 3.2 Pro's Function Calling capability. What I found changed how I think about AI integration in mobile apps at the architectural level.

Why Function Calling Changes the Architecture, Not Just the Feature

Most AI integrations in mobile apps follow the same pattern: send a prompt, get a text response, parse it into something the UI can display. Function Calling is structurally different, and understanding why matters before writing a line of code.

In a conventional API design, the client is imperative — it tells the server exactly what to do. "Filter wallpapers by style=minimal and mood=calm, return 10 results." The server executes that instruction. The client holds the decision logic.

With Function Calling, you invert part of that relationship. You declare a set of capabilities to the model — "here is what this system can do" — and the model decides which capabilities to invoke based on what the user is trying to accomplish. The model holds the decision logic for routing; your code holds the decision logic for execution.

This matters for indie developers specifically, because it means you can add new capabilities to a deployed app without shipping an update. You add a new function to your backend, update the function declarations you send to the model, and existing users on old app versions get access to the new behavior immediately. The model learns to route to the new function without any change to the client.

That architectural property alone is worth understanding, independent of the personalization benefits. App store review cycles are slow. If your AI feature needs to learn new behaviors based on how users interact with it, Function Calling lets you iterate on the intelligence layer without waiting for a new version to clear review.

Gemini 3.2 Pro is notably more reliable at this than its predecessors. With Gemini 2.5 Pro, I observed that complex function chains (3+ function calls in sequence) would sometimes break down — the model would either abandon the chain partway through or attempt to answer without executing all the functions. With 3.2 Pro and the tool_config set correctly, chained function calling completes reliably even for four or five sequential steps.

For foundational background on Function Calling mechanics, see the Gemini API Function Calling Complete Guide.

Backend Validation: Never Skip This Step

Before touching mobile code, spend time validating the backend logic in Python. Mobile development involves longer feedback loops — compile times, simulator boot times, device provisioning. Getting the core behavior right in a Python REPL first saves you hours of debugging.

My validation routine: define the functions, write stub implementations that return plausible fake data, and test a variety of user message phrasings. The goal is to verify that the model invokes the functions you expect, in the sequence you expect, before you're committing Swift or Kotlin code around those assumptions.

Setup requirements:

  • Python 3.11 or higher
  • Google GenAI SDK: pip install google-genai
  • Gemini API key (Google AI Studio — the free tier is sufficient for validation)

Code Example 1: Python Function Calling with Loop Guard

import google.generativeai as genai
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
 
tools = [
    {
        "function_declarations": [
            {
                "name": "get_user_preferences",
                "description": "Retrieves a user's wallpaper preference history",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "user_id": {"type": "string", "description": "User identifier"}
                    },
                    "required": ["user_id"]
                }
            },
            {
                "name": "filter_wallpapers",
                "description": "Returns wallpapers matching the given criteria",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "style": {
                            "type": "string",
                            "enum": ["nature", "abstract", "minimal", "art"]
                        },
                        "mood": {
                            "type": "string",
                            "enum": ["calm", "energetic", "dark", "bright"]
                        },
                        "exclude_seen": {
                            "type": "boolean",
                            "description": "If true, exclude recently viewed wallpapers"
                        },
                        "limit": {"type": "integer", "description": "Max results (default: 10)"}
                    }
                }
            }
        ]
    }
]
 
model = genai.GenerativeModel(
    model_name="gemini-3.2-pro",
    tools=tools,
    tool_config={"function_calling_config": {"mode": "AUTO"}}
)
 
def get_user_preferences(user_id: str) -> dict:
    # In production: query your database
    return {"preferred_styles": ["minimal", "nature"], "preferred_moods": ["calm"]}
 
def filter_wallpapers(style: str = None, mood: str = None,
                      exclude_seen: bool = False, limit: int = 10) -> list:
    # In production: query your content API
    return [
        {"id": "w001", "title": "Morning Forest", "style": "nature", "mood": "calm"},
        {"id": "w002", "title": "Clean White", "style": "minimal", "mood": "calm"},
    ][:limit]
 
def dispatch_function(function_call):
    name = function_call.name
    args = dict(function_call.args)
    if name == "get_user_preferences":
        return get_user_preferences(**args)
    elif name == "filter_wallpapers":
        return filter_wallpapers(**args)
    return {"error": f"Unknown function: {name}"}
 
def run_chat(user_message: str, user_id: str = "user_001") -> str:
    chat = model.start_chat()
    context = f"User ID: {user_id}. You are an assistant in a wallpaper app."
    response = chat.send_message(f"{context}\n\nUser request: {user_message}")
 
    # The loop guard is essential — without it, you risk infinite function-calling cycles
    max_iterations = 5
    for iteration in range(max_iterations):
        function_calls = [
            part.function_call
            for part in response.candidates[0].content.parts
            if hasattr(part, "function_call") and part.function_call
        ]
        if not function_calls:
            break  # Model returned a text response — exit the loop
 
        responses = [
            {
                "function_response": {
                    "name": fc.name,
                    "response": {"result": dispatch_function(fc)}
                }
            }
            for fc in function_calls
        ]
        response = chat.send_message(responses)
    else:
        # If we hit max_iterations without a text response, return a safe fallback
        return "Sorry, I couldn't process that request. Please try again."
 
    return "".join(
        part.text for part in response.candidates[0].content.parts
        if hasattr(part, "text")
    )
 
if __name__ == "__main__":
    # Test with several phrasings to verify routing behavior
    test_messages = [
        "Show me something calming",
        "I want something different from what I usually look at",
        "Show me the same style I like but something fresh",
    ]
    for msg in test_messages:
        print(f"Input: {msg}")
        print(f"Output: {run_chat(msg)}")
        print("---")

The max_iterations = 5 guard deserves more attention than it usually gets. In production testing, I encountered a pattern where the model would call get_user_preferences, receive the result, decide it needed to call it again with a slightly different parameter, receive that result, and repeat. The loop guard prevents this from becoming a billing incident. Five iterations is generous for legitimate use cases (I've never needed more than four in a real scenario), and stops runaway loops before they matter.

If the else block after the for loop looks unfamiliar: in Python, for...else executes the else block only if the loop completed without hitting a break. This cleanly handles the case where we've exhausted iterations without getting a text response.

For async usage patterns: Gemini API asyncio production patterns.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
How Function Calling shifts mobile design from imperative to intent-driven
Working SwiftUI, Kotlin, and Python code with loop guards, timeouts, and exponential backoff
Production data: ~40% cost reduction, staged rollout, and an 18% session-time uplift
A regression-test harness that catches routing breakage from description or model changes
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API / SDK2026-05-25
Running In-App Help Translation on Gemini 2.5 Flash for Three Months — An Indie Developer's Notes
After three months running my iOS and Android in-app help through a Gemini 2.5 Flash translation pipeline, here are the operational notes — when to fall back to Pro, how glossaries help, and the small lift it added to AdMob revenue.
API / SDK2026-05-05
Never Embed Your Gemini API Key in a Mobile App: Complete Multi-Layer Security Architecture with Firebase App Check
A production-grade guide to securing Gemini API access in mobile apps. Covers Firebase App Check, Cloud Functions proxy, rate limiting, and anomaly detection — with complete iOS and Android code examples.
API / SDK2026-04-03
Gemini API × SwiftUI in Production: Streaming, Multimodal, Error Handling, and App Store Submission
A production-grade guide to integrating the Gemini API into SwiftUI apps at production quality. Covers streaming responses, multimodal input, error handling, test strategies, and App Store submission requirements.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →