๐Ÿค– Android AI and Generative AI

Function Calling and Tool Use in AI-Powered Apps

Function calling lets a model decide that it needs your app's data or actions, such as checking an order, finding a store or creating a reminder, and request them in a structured way. Your code stays in control: it validates, executes and returns results. Here's how to do it safely in Kotlin.

โœ๏ธ Yunok Tech ๐Ÿ“… Updated โฑ 3 min read ๐ŸŽฏ Advanced
On this page
  1. The function-calling loop
  2. Declaring tools (Firebase AI Logic, Kotlin)
  3. Handling calls and returning results
  4. Safety rules for tool use
  5. Use cases
  6. Frequently asked questions
  7. Conclusion

A support chatbot that can only talk is limited. One that can look up your order is useful. Function calling bridges the two: you describe functions to the model, it returns a structured request to call one with arguments, your code runs it, and the model uses the result to answer.

The function-calling loop

Function calling loop The user asks where their order is. The app sends the message plus tool declarations to the model. The model replies with a function call getOrderStatus with orderId A1042. The app validates the arguments and checks authorisation, calls its own API, and sends the function result back to the model. The model writes the final answer for the user. "Where is order A1042?"+ tool declarations Model functionCall:getOrderStatus(A1042) YOUR code: validate, authorise,call your API functionResponseโ†’ model "It's out for delivery,arriving today." The model never executes anything. It only asks; your code decides.
Function calling is structured intent extraction plus a loop. The authority stays with your code.

Declaring tools (Firebase AI Logic, Kotlin)

Tools.ktKotlin
val getOrderStatus = FunctionDeclaration(
    name = "getOrderStatus",
    description = "Get the delivery status of one of the signed-in user's orders.",
    parameters = mapOf("orderId" to Schema.string("Order ID like A1042")),
)

val findNearestStore = FunctionDeclaration(
    name = "findNearestStore",
    description = "Find the nearest store to the user's current city.",
    parameters = mapOf("city" to Schema.string("City name, e.g. Mohali")),
)

val model = Firebase.ai(backend = GenerativeBackend.googleAI()).generativeModel(
    modelName = "gemini-3.8-flash",                      // check current names
    tools = listOf(Tool.functionDeclarations(listOf(getOrderStatus, findNearestStore))),
    systemInstruction = content { text("You help with orders and stores. Use tools for facts; never guess order data.") },
)

Handling calls and returning results

ToolRunner.ktKotlin
class ToolRunner(
    private val orders: OrderRepository,         // only returns the CURRENT user's orders
    private val stores: StoreRepository,
) {
    suspend fun run(call: FunctionCallPart): JsonObject = when (call.name) {
        "getOrderStatus" -> {
            val id = call.args["orderId"]?.jsonPrimitive?.contentOrNull
                ?.takeIf { it.matches(Regex("^[A-Z][0-9]{3,10}$")) }          // validate arguments
                ?: return error("Invalid order ID")
            val order = orders.findForCurrentUser(id) ?: return error("Order not found")
            buildJsonObject { put("status", order.status.name); put("eta", order.eta?.toString()) }
        }
        "findNearestStore" -> {
            val city = call.args["city"]?.jsonPrimitive?.contentOrNull?.take(60) ?: return error("City required")
            val store = stores.nearest(city) ?: return error("No store found")
            buildJsonObject { put("name", store.name); put("address", store.address) }
        }
        else -> error("Unknown function")                                       // allow-list
    }

    private fun error(msg: String) = buildJsonObject { put("error", msg) }
}

suspend fun ask(chat: Chat, runner: ToolRunner, userText: String): String {
    var response = chat.sendMessage(userText)
    repeat(3) {                                                                 // cap the loop
        val calls = response.functionCalls
        if (calls.isEmpty()) return response.text.orEmpty()
        val results = calls.map { FunctionResponsePart(it.name, runner.run(it)) }
        response = chat.sendMessage(content(role = "function") { results.forEach { part(it) } })
    }
    return response.text ?: "Sorry, I couldn't complete that."
}
// val chat = model.startChat()

Class and property names follow the Firebase AI Logic Kotlin SDK. Check the function-calling docs for your version, since details such as role names and part types can change.

Safety rules for tool use

  • Allow-list functions, and never execute arbitrary names or code from the model.
  • Validate every argument (type, format, ranges), just like untrusted user input.
  • Authorise in your code: the tool queries only the signed-in user's data.
  • Confirm sensitive actions: payments, cancellations, sending messages and deleting data need an explicit user confirmation in the UI, not just model intent.
  • Limit the loop: cap iterations and time.
  • Read-only first: start with lookups, and add actions carefully.
Human-in-the-loop for actionsKotlin
sealed interface PendingAction {
    data class CancelOrder(val orderId: String) : PendingAction
}
// The tool doesn't cancel; it returns {"requiresConfirmation": true} and the UI shows:
// "Cancel order A1042? [Cancel order] [Keep order]". Only the button triggers the real API call.

Use cases

Support assistant

Order status, return eligibility and store lookup, all read-only with user scoping.

Voice commands

"Remind me at 6" โ†’ createReminder(time, title) โ†’ confirm โ†’ create.

Smart filters

"Show red shoes under 3000" โ†’ applyFilters(...), which only changes UI state.

Frequently asked questions

Can the model call functions on its own without my code?

No. The model only returns a request. Nothing runs unless your code executes it.

Function calling or structured output?

Structured output returns data in a schema in one step. Function calling lets the model decide whether and which tool to use, often over multiple turns.

How does this relate to Android AppFunctions?

AppFunctions let your app expose functions to system-level AI agents. In-app function calling is your own assistant calling your own code. See AppFunctions.

Conclusion

Function calling turns AI from a talker into a helper: declare narrow tools, validate and authorise every call in your code, return structured results, cap the loop, and require user confirmation for actions. The model proposes, and your app decides.

Next step: measure and improve answer quality with Evaluating AI Responses and Reducing Hallucinations.

Official references

๐Ÿ“ฑ Apps built by Yunok Tech

Real Android apps on Google Play, free to download.

See all our apps โ†’

Ready to build real Android applications?

Every AI feature still runs inside an Android app that has to be fast, reliable and well structured. Strengthen the Kotlin, Jetpack Compose and architecture skills that AI integrations depend on, then apply these AI concepts with confidence.

Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.

Explore the Android course and take your next step as an Android developer.

Share: LinkedIn X / Twitter WhatsApp