<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>El&#39;s Blog</title>
  <subtitle>El is blogging about tech and life.</subtitle>
  <link href="https://2bab.me/en/rss.xml" rel="self"/>
  <link href="https://2bab.me/"/>
  <updated>2026-05-15T00:00:00Z</updated>
  <id>https://2bab.me/</id>
  <author>
    <name>2BAB</name>
  </author>
  
  <entry>
    <title>A2UI, AG-UI, and GenUI Beyond the Chat Box</title>
    <link href="https://2bab.me/en/blog/2026-05-15-a2ui-agui-surface-spec/"/>
    <updated>2026-05-15T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2026-05-15-a2ui-agui-surface-spec/</id>
    <content type="html">&lt;p&gt;I recently took another look at Google&#39;s A2UI and AG-UI. Compared with my first pass last year, my view is clearer now: both are valuable, and they complement each other, but they should not be simplified into &amp;quot;dynamic UI protocols are finally standardized.&amp;quot;&lt;/p&gt;
&lt;p&gt;More precisely, A2UI and AG-UI are still clearly centered around a &lt;strong&gt;chat-like style&lt;/strong&gt; today: agent conversations, copilots, search/chat, and assistant panels inside productivity tools. This is a large market, of course, and it explains why tools and protocols in this direction are moving so fast. But AI application forms cannot be fixed inside the chat box. The many existing apps, especially in content, media, education, consumer products, and communities, still leave a lot of space worth watching.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://storage.googleapis.com/gweb-developer-goog-blog-assets/images/a2ui-blog-1-component-gallery_2.original.png&quot; alt=&quot;A2UI component gallery example&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Image source: Google Developers Blog, component gallery example from &lt;a href=&quot;https://developers.googleblog.com/introducing-a2ui-an-open-project-for-agent-driven-interfaces/&quot;&gt;Introducing A2UI: An open project for agent-driven interfaces&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;spec-and-protocol&quot; tabindex=&quot;-1&quot;&gt;Spec and Protocol&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.googleblog.com/introducing-a2ui-an-open-project-for-agent-driven-interfaces/&quot;&gt;Google introduced A2UI in December 2025&lt;/a&gt;. The core idea is to let an agent return not just text, but a safe, declarative UI description, which the host application then renders with its own components. By the &lt;a href=&quot;https://developers.googleblog.com/a2ui-v0-9-generative-ui/&quot;&gt;A2UI v0.9 update in April 2026&lt;/a&gt;, the direction became clearer: use the existing component catalog from a design system, let the agent &amp;quot;speak UI,&amp;quot; and avoid having the agent directly emit HTML/JS or arbitrary code.&lt;/p&gt;
&lt;p&gt;AG-UI sits on another layer. It is not a GenUI spec, but an interaction protocol between an agent backend and a user-facing frontend. The official docs state this directly: &lt;a href=&quot;https://docs.ag-ui.com/concepts/generative-ui-specs&quot;&gt;A2UI is a generative UI specification, while AG-UI is an Agent-User Interaction protocol&lt;/a&gt;. This is also why AG-UI naturally feels chat-oriented: it deals with long-lived connections, streaming, tool calls, state sync, human-in-the-loop flows, cancel/resume, and the runtime reality of &amp;quot;an agent and a user are working through something together.&amp;quot;&lt;/p&gt;
&lt;p&gt;If we only look at these two protocols:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A2UI describes what UI the agent wants to show, which components to use, what data is bound, and what actions exist.&lt;/li&gt;
&lt;li&gt;AG-UI describes how the agent and frontend keep talking, passing text, state, tool calls, UI intent, and user interactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;who-is-using-this-now&quot; tabindex=&quot;-1&quot;&gt;Who Is Using This Now&lt;/h2&gt;
&lt;p&gt;A2UI&#39;s official landing zone is still mostly agentic apps: Google Opal, Gemini Enterprise, Flutter GenUI SDK, ADK examples, AG2&#39;s A2UIAgent, and CopilotKit/AG-UI integration. The Flutter GenUI SDK is worth paying attention to because it shows Google has at least seriously considered mobile/native rendering, instead of only building browser scenarios. On the A2UI roadmap, Lit, Angular, React, and Flutter are marked stable, while SwiftUI and Jetpack Compose are still planned.&lt;/p&gt;
&lt;p&gt;AG-UI feels more like &amp;quot;agent app plumbing.&amp;quot; It is closely tied to CopilotKit, and the official docs list integrations with LangGraph, CrewAI, Google ADK, Microsoft Agent Framework, Pydantic AI, Agno, and others. Its strength is turning events from different agent frameworks into typed events that a frontend can consume. In other words, if you are building a web-based copilot, agent chat, or workspace, it may be useful. Otherwise, it is probably not the first layer of the answer.&lt;/p&gt;
&lt;p&gt;This is where I think there is still a lot of space. LLMs are NOT only here to make search boxes, chat boxes, and workspaces smarter. &lt;strong&gt;News apps still need reading pages, podcast apps still need show notes, magazines still need features, and children&#39;s stories still need supportive reading and interaction.&lt;/strong&gt; The question is not &amp;quot;should these apps become chatbots?&amp;quot; The question is whether the content surfaces inside existing products can begin to understand the content itself, &lt;strong&gt;and dynamically place layout, comments, sharing, ads, related reading, and action entry points where they make more sense.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;faq&quot; tabindex=&quot;-1&quot;&gt;FAQ&lt;/h2&gt;
&lt;p&gt;I talked with agents about this topic quite a bit. Here are a few of the questions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is the relationship between A2UI and AG-UI?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A2UI describes the UI payload itself. AG-UI describes the interaction process between the agent and the frontend. A2UI is closer to a payload/schema, while AG-UI is closer to a runtime pipe. They can be used together, but they are not the same thing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is A2UI&#39;s JSONL? Can it be parsed incrementally?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JSONL means JSON Lines: each line is a complete JSON object. It is often used with SSE streaming because the client can parse line by line, receiving &lt;code&gt;createSurface&lt;/code&gt;, &lt;code&gt;updateComponents&lt;/code&gt;, and &lt;code&gt;updateDataModel&lt;/code&gt; while updating the local model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why does AG-UI feel so chat-like?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because agent app interaction is rarely a single request/response. The agent streams text, calls tools in the middle, pauses for user confirmation, and continues updating state. AG-UI models these events, so it naturally appears in chat, copilot, and agent workspace products.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do A2UI demos for marketing emails or Twitter/X cards make sense?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes, but mainly during generation and preview, not at the final delivery layer. Email ultimately becomes HTML/MJML. Twitter/X ultimately becomes an image, metadata, or a platform-native card. A2UI can help the admin side preview, approve, rewrite, and schedule through a unified renderer. If the output is only a one-off static artifact, the value of A2UI becomes much thinner.&lt;/p&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.googleblog.com/introducing-a2ui-an-open-project-for-agent-driven-interfaces/&quot;&gt;Introducing A2UI: An open project for agent-driven interfaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.googleblog.com/a2ui-v0-9-generative-ui/&quot;&gt;A2UI v0.9: The New Standard for Portable, Framework-Agnostic Generative UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.googleblog.com/en/developers-guide-to-ai-agent-protocols/&quot;&gt;Developer&#39;s Guide to AI Agent Protocols&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://a2ui.org/&quot;&gt;A2UI official docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ag-ui.com/&quot;&gt;AG-UI official docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ag-ui.com/concepts/generative-ui-specs&quot;&gt;AG-UI: Generative UI Specs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Harness Engineering Essentials: Nine Practices for Taming AI Coding Agents</title>
    <link href="https://2bab.me/en/blog/2026-04-12-harness-engineering-best-practices/"/>
    <updated>2026-04-12T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2026-04-12-harness-engineering-best-practices/</id>
    <content type="html">&lt;p&gt;In late 2025, Anthropic published &lt;a href=&quot;https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents&quot;&gt;Effective Harnesses for Long-Running Agents&lt;/a&gt;, introducing the concept of a harness — using architectural means to constrain an unstable model into a sustainably running engineering system. They later expanded on multi-agent division of labor and evaluation separation in &lt;a href=&quot;https://www.anthropic.com/engineering/harness-design-long-running-apps&quot;&gt;Harness Design for Long-Running Application Development&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The book &lt;a href=&quot;https://harness-books.agentway.dev/book1-claude-code/&quot;&gt;&lt;em&gt;Harness Engineering: Claude Code Design Guide&lt;/em&gt;&lt;/a&gt; takes this idea further by analyzing Claude Code&#39;s source code to systematically break down the engineering design across prompt control planes, execution loops, tool permissions, context governance, error recovery, multi-agent verification, and team adoption. The book contains extensive source-level analysis of Claude Code internals — this article strips that away and retains only the transferable best practices.&lt;/p&gt;
&lt;h2 id=&quot;1.-why-harness-engineering&quot; tabindex=&quot;-1&quot;&gt;1. Why Harness Engineering&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Models don&#39;t deserve inherent trust.&lt;/strong&gt; A talking probability distribution, once given access to terminals and files, escalates risk from the rhetorical level to the execution level. A text-only model that makes mistakes merely adds communication overhead, but a tool-wielding model that makes mistakes leaves real consequences — deleted files, killed processes, rewritten Git history. Agent systems entering real engineering environments must first acknowledge that their core component is unstable. Ignore this, and the problems will eventually surface in logs and incident reports.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Constrained execution is the core capability.&lt;/strong&gt; Models make mistakes, tools amplify their consequences, context bloats, state pollutes the next turn, users interrupt, and failures recur. A system cannot maintain order through &amp;quot;cleverness&amp;quot; alone — only through structure. Structure isn&#39;t as flashy as cleverness, but it&#39;s usually more reliable. A truly usable agent system cannot rely on a single &amp;quot;magic prompt&amp;quot; to solve everything; it must decompose control into layers, and layers into responsibilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A harness is an entire control plane.&lt;/strong&gt; It encompasses prompt constraints, execution loops, tool scheduling, permission approval, error recovery, and more — all converging on one goal: making the model produce tolerable behavior despite being unreliable. High-risk capabilities demand high-density constraints — the more powerful the capability, the finer the control, because the real world won&#39;t automatically forgive an erroneous execution just because the model sounded confident. Taken together, Harness Engineering isn&#39;t mysterious; it simply insists on a few commonly overlooked engineering fundamentals.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2.-prompts-are-a-control-plane%2C-not-a-persona&quot; tabindex=&quot;-1&quot;&gt;2. Prompts Are a Control Plane, Not a Persona&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Persona and control operate on different layers.&lt;/strong&gt; Persona descriptions address &amp;quot;what it resembles&amp;quot;; a control plane addresses &amp;quot;what it can do, when it should act, what happens when it fails, and who backstops it.&amp;quot; A system can have a likable persona while completely lacking discipline at the execution level — such systems tend to seem very sincere when things go wrong, because they&#39;re great at apologizing, but apologies cannot substitute for runtime design. For an agent that reads files, invokes tools, touches the shell, handles permissions, and executes across turns, the prompt is closer to a runtime protocol than a character biography.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompts should be assembled in layers, not written as a single monolithic text.&lt;/strong&gt; Mature systems don&#39;t pin their faith on a single version of a prompt but treat it as a hierarchical configuration system: identity declarations, system-level rules, engineering constraints, and domain behaviors each managed independently. When newly added reminders and prohibitions conflict with each other, system behavior becomes unpredictable — decomposing into layers and responsibilities is the proper approach. A model that automatically &amp;quot;optimizes everything it touches&amp;quot; may seem enthusiastic from a product perspective, but it&#39;s quite dangerous from an engineering perspective, which is why engineering constraints (don&#39;t overstep scope, don&#39;t conceal verification failures, don&#39;t create unnecessary abstractions) must be explicitly specified in the prompt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompts must have a priority mechanism.&lt;/strong&gt; Different contexts (coordinator mode, agent mode, user overrides) should have an explicit priority ordering, rather than last-write-wins. The key principle: new agent instructions can only layer domain behavior on top of default constraints — they cannot replace the entire discipline. Think of it as general regulations plus a job description — the job description can add responsibilities but cannot override the foundational regulations, or the system will quickly devolve into anarchy. Customizability without structure ultimately degenerates into just another form of chaos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompts must also connect to the memory system.&lt;/strong&gt; A mature prompt doesn&#39;t just prescribe &amp;quot;how to execute this turn&amp;quot; — it also prescribes &amp;quot;how long-term memory should be formed&amp;quot;: what to save, what not to save, how to separate indexes from content, and how plans and tasks shouldn&#39;t be misused as memory. Once you reach this point, the prompt can no longer be merely a matter of tone — it necessarily enters the domain of institutional design. It extends the prompt&#39;s responsibility from &amp;quot;constraining current behavior&amp;quot; to &amp;quot;constraining how future knowledge is accumulated,&amp;quot; making it closer to a knowledge governance protocol for runtime participants.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3.-the-query-loop%3A-heartbeat-of-an-agent-system&quot; tabindex=&quot;-1&quot;&gt;3. The Query Loop: Heartbeat of an Agent System&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Agents depend on stateful execution loops, not request-response.&lt;/strong&gt; To gauge whether an agent is mature, first check if it maintains cross-turn execution state: message history, recovery counts, compaction tracking, tool context, turn counters, and more. Once designed this way, the system formally acknowledges that problems left over from the previous turn will enter the next, and the system must be capable of continuing to handle them. Whether a system deserves to be called an agent often depends not on whether it can speak, but on whether it still knows what it&#39;s doing after several turns. Scripts only care whether this step finished; agent systems must also care whether, after this step fails, the next step can pick up the state left behind.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context governance precedes model inference.&lt;/strong&gt; Before invoking the model, the runtime should complete a series of housekeeping tasks: extracting valid messages, trimming tool results, compressing history, and collapsing context. Many systems do the opposite: stuff massive context in first, then hope the model figures out what&#39;s important on its own. That approach seems efficient but is really offloading the runtime&#39;s responsibility onto a probability distribution. Don&#39;t hand the model the job of extracting order from chaos — have the runtime complete governance first, then pass cleaner input to the model. Tidy the scene before starting execution; this approach isn&#39;t glamorous, but it&#39;s usually more dependable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interruptions and recovery must be first-class semantics.&lt;/strong&gt; Whenever a system has committed to an execution sequence, it must settle the books upon interruption — it cannot pretend previous actions never happened just because the user interrupted. Tool calls already dispatched but not yet completed must generate compensating results, ensuring the execution trace remains explainable. Recovery isn&#39;t simple retry but a layered attempt from lowest to highest cost and destructiveness. Stop conditions must also be differentiated: streaming completion, user interruption, prompt-too-long, output truncation, hook blocking — each taking a different path. Distinguishing &amp;quot;retry on failure&amp;quot; from &amp;quot;knowing when not to retry&amp;quot; is a hallmark of a mature system.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4.-tools%2C-permissions%2C-and-interrupts&quot; tabindex=&quot;-1&quot;&gt;4. Tools, Permissions, and Interrupts&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tools are managed execution interfaces, not extensions of model capability.&lt;/strong&gt; Tools are not opinions — tools are actions. Actions leave consequences; consequences touch the real world. The model proposes actions; whether they proceed is decided by the runtime, rules, and the user. Permission outcomes should have at least three states: allow, deny, and ask. The &amp;quot;ask&amp;quot; third state is crucial — it acknowledges that the system itself shouldn&#39;t make every decision on the user&#39;s behalf. Understanding intent does not equal having authorization, let alone having ongoing authorization. The system must separate &amp;quot;being capable&amp;quot; from &amp;quot;being permitted.&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool scheduling must preserve causal order.&lt;/strong&gt; Once a tool system allows concurrency, it must answer an old question: who determines context changes, and in what order do they take effect? The correct approach is that even when execution is concurrent, semantic context evolution maintains a deterministic order — cache modifications first, then replay in original order. Concurrency can improve throughput but must not break causal order. A tool system without scheduling discipline only amplifies the model&#39;s instability into the external world; unconstrained concurrency widens the blast radius.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High-risk tools must be treated differently.&lt;/strong&gt; An interface like Bash, which is virtually unconstrained by domain boundaries, must be treated as a special case — it can directly touch files, processes, networks, and Git repositories, and carries complex shell semantics like redirection and piping. The correct approach is to build dedicated permission checks, command prefix parsing, subcommand count limits, and exhaustive operational rules specifically for it. High-risk capabilities should not receive the same treatment as general capabilities — the more general the capability, the more special oversight it needs. Treating Bash as an ordinary tool is usually just laziness in design.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The tool system protects not just the user, but also the system itself.&lt;/strong&gt; Incomplete execution results, out-of-order context modifications, unbounded concurrent side effects, and unclear interruption semantics — these problems most quickly destroy system consistency. The purpose of constraining tools is to ensure that &amp;quot;what was executed, what wasn&#39;t completed, and why it stopped&amp;quot; always forms a traceable causal chain. Many constraints appear to prevent user errors on the surface, but at a deeper level they prevent the system itself from becoming an inexplicable heap of state fragments. Unexplainable execution traces inevitably become ops problems, audit problems, or long-term liabilities that nobody on the team can untangle.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5.-context-governance%3A-memory-and-compaction&quot; tabindex=&quot;-1&quot;&gt;5. Context Governance: Memory and Compaction&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Context is a budget, not a warehouse.&lt;/strong&gt; &amp;quot;More information makes the system smarter&amp;quot; is a common myth. Context is first and foremost an expensive, bloat-prone, self-contaminating resource — an agent system is not a library, and the model is not a librarian. Long-term rules, persistent memory, session continuity, and ephemeral conversation should be governed in separate layers, not mixed into one pot. Stable team conventions and repository constraints have lifespans far exceeding any single user message; if everything gets stuffed into chat history, you either redundantly inject every turn wasting context, or rely on the model to recall things on its own, which will eventually fail.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory entry points must stay small.&lt;/strong&gt; Index files are naturally loaded frequently, and once frequently-loaded content grows fat, it slowly drags down the entire context system. Long-term memory should be split into &amp;quot;entry points&amp;quot; and &amp;quot;body&amp;quot;: entry points for low-cost addressing, body for high-density content. Entry point files should have hard limits — exceed them and the system truncates with a warning, moving details to separate files. Once an entry point serves as both directory and content, it ends up being neither — just an abandoned summary nobody wants to read a second time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session continuity requires structured summaries, not chat logs.&lt;/strong&gt; Session memory should be distilled into a continuation-ready operating manual: current state, pitfalls encountered, files modified, and what to pick up next. It doesn&#39;t aim to fully replicate the conversation but to compress the essential skeleton needed to keep working. Summary budgets must also be controlled, prioritizing &amp;quot;current state&amp;quot; and &amp;quot;error corrections&amp;quot; — the parts most useful for the next execution step. A truly mature system treats &amp;quot;preserving the most useful parts for continuing work&amp;quot; as a virtue, because context budget is working memory, and working memory&#39;s first duty is to be actionable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compaction&#39;s goal is rebuilding work semantics, not writing a nice summary.&lt;/strong&gt; Post-compaction context must restore plan state, file state, skill constraints, tool attachments, and other runtime environment — the summary is merely an intermediate artifact, and the real goal is to lay flat &amp;quot;the work foundation needed to keep going.&amp;quot; Compaction is therefore more like a controlled restart than a chat summary — old context gets translated into a new work foundation. Systems that only do the first half may &amp;quot;roughly remember&amp;quot; after compaction, but they&#39;ve already lost tool and plan state, and will spend the next several turns finding themselves again. Context systems should prioritize preserving what maintains action semantics over what appears to have the most information density.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6.-errors-and-recovery&quot; tabindex=&quot;-1&quot;&gt;6. Errors and Recovery&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Error recovery must be layered — don&#39;t use one heavy hammer for every problem.&lt;/strong&gt; Judging whether an agent system is mature should not be based on how human it sounds when things go smoothly, but on how systematic it looks when things break. For example, when a prompt is too long, first try draining known backlogs, then attempt heavier full-text compression — don&#39;t immediately rebuild the world. A good recovery system first tries to preserve the finest-grained context, then accepts coarser summary replacements only when necessary. Some errors should be handed to the recovery system for an attempt before deciding whether to surface them to the user — what users truly care about is usually whether the system can keep working.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recovery logic must prevent self-referential loops.&lt;/strong&gt; If compaction doesn&#39;t help, continuing to compact will most likely just replay the same failure in a different pose. The most dangerous errors in a system are when failure branches and recovery branches bite each other and begin infinite self-replication. Any automatic recovery mechanism must be countable, limited, and circuit-breakable — a recovery system that can&#39;t stop is like a car without brakes: both are technically called systems, but neither should be on the road. Even &amp;quot;repair actions&amp;quot; themselves need repair strategies, because in practice, a compaction request can itself fail due to context being too long. At that point, the priority is to restore breathing first and discuss information fidelity later.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The best recovery after truncation is continuation, not summarization.&lt;/strong&gt; When output is truncated, the system should continue directly from the breakpoint rather than first apologizing, recapping, or writing elegant filler. First try raising the token cap and re-running directly; if that&#39;s still insufficient, append an instruction telling the model to continue from where it was cut off, explicitly requesting no apologies and no review. Every post-truncation recap further consumes budget and increases semantic drift, until the system is no longer doing the task itself but round after round of reviewing itself doing the task. An engineering system&#39;s true courtesy lies in not trapping users in a failed state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interruption is also a failure state that requires semantic closure.&lt;/strong&gt; A user interruption isn&#39;t just &amp;quot;I don&#39;t want to watch anymore&amp;quot; — it&#39;s a state transition that requires proper wrap-up. Tool calls already dispatched but not yet completed must generate compensating results, ensuring previously committed actions don&#39;t become dangling debts. What error recovery truly repairs is not just the error itself, but the system&#39;s ability to explain its own behavior — whether the system can articulate &amp;quot;what I just attempted, why it didn&#39;t work, and whether to continue or stop now.&amp;quot; Once explanatory ability breaks, the system degrades from an engineering object into a mystical one.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7.-multi-agent-and-verification&quot; tabindex=&quot;-1&quot;&gt;7. Multi-Agent and Verification&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Forking is first and foremost a runtime economics problem.&lt;/strong&gt; Sub-agents must share cache-critical parameters with the parent (system prompt, context, tool configuration, etc.); otherwise, each fork re-burns the entire token budget, appearing to parallelize for efficiency while actually just parallelizing waste. State isolation is the default ethic — all mutable state is isolated first, and sharing must be explicitly declared, preventing a sub-agent&#39;s local chaos from contaminating the main thread. The most valuable aspect of a sub-agent is precisely that it can avoid polluting the main thread with its local messiness: misguided research, temporarily read file states, and one-off reasoning branches. If all of these write directly back to the main context, you only get faster contamination.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Research can be delegated; synthesis cannot.&lt;/strong&gt; What&#39;s truly scarce in multi-agent systems is synthesis — compressing the local knowledge each worker brings back into clear, executable, verifiable next steps. The coordinator must digest research results before writing specific instructions; subsequent prompts must reference specific files, specific locations, and specific changes, rather than abstractly stating &amp;quot;based on the previous findings.&amp;quot; Without this layer, multi-agent systems quickly degenerate into politely-worded task forwarding machines — every agent is busy, but the system as a whole hasn&#39;t gotten any smarter. This is classic engineering division of labor: research can be distributed, but understanding must be re-converged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verification must be an independent stage.&lt;/strong&gt; Between &amp;quot;I modified the code&amp;quot; and &amp;quot;the code is therefore correct&amp;quot; lies a very wide river, and models are especially good at building paper bridges across it. Implementers naturally tend to believe their changes are &amp;quot;probably fine,&amp;quot; and models even more so — they&#39;ll give you changes, explanations, and even plausible-looking test output, but none of that equals the feature actually standing on its own. Verification must become an independent role: those who implement focus on making changes; those who verify focus on questioning whether those changes deserve to survive. The goal of verification is to prove code works, not merely to confirm code exists — otherwise &amp;quot;done&amp;quot; quickly degrades into &amp;quot;I wrote it and I think it&#39;s fine.&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sub-agents need complete lifecycle management.&lt;/strong&gt; They should be observable at startup, intervenable before shutdown, with traceable transcript paths, and when the parent task aborts, child tasks must follow. Whether output files should be retained, whether cleanup callbacks have leaked, how to handle state residue after an agent ends — all of these require explicit handling. A multi-agent demo that merely achieves &amp;quot;I can spawn another agent&amp;quot; falls far short; agents must be treated as runtime entities that can leak resources, leave residual state, and become orphans when parent processes end. The real value of multi-agent isn&#39;t parallelism for speed — it&#39;s compartmentalizing different kinds of uncertainty into different containers and having the coordinator reassemble them.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;8.-team-adoption&quot; tabindex=&quot;-1&quot;&gt;8. Team Adoption&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start by drawing the minimum controllable boundary.&lt;/strong&gt; Teams don&#39;t need to start with hooks and complex skill directories. First, get four things clear: which tasks agents are allowed to participate in, which changes must go through human review, what verification must run after changes, and which resources are strictly off-limits. These four things matter more than any grand slogan. If the acceptable scope isn&#39;t defined, people will use agents for things that shouldn&#39;t be automated; if review responsibility isn&#39;t defined, nobody knows who&#39;s the last line of defense when things go wrong; if no-go zones aren&#39;t defined, efficiency gains merely widen the blast radius. Many teams ultimately fail not because the agent isn&#39;t powerful enough, but because they skipped this step at the start.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unify verification definitions before expanding skill count.&lt;/strong&gt; The most common failure in adopting an AI coding agent isn&#39;t in the prompt or the model — it&#39;s that the team has no unified definition of &amp;quot;done.&amp;quot; Some think &amp;quot;it runs&amp;quot; is enough; some think &amp;quot;half the tests passed&amp;quot; is fine; some think &amp;quot;the model&#39;s explanation sounded convincing&amp;quot; counts. Under these conditions, even the smartest system will only learn to meet the lowest bar. Skills can replicate workflows, but only verification definitions can replicate quality. First define which tasks require independent verification, what verification must minimally include, and how to mark verification failures — once these three things are unified, even with few skills, the quality floor holds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;http://claude.md/&quot;&gt;CLAUDE.md&lt;/a&gt; is more like a foundation than a bulletin board.&lt;/strong&gt; Team-level instruction files are suited for stable rules: codebase hard constraints, unified verification standards, collaboration discipline, and output style. They&#39;re unsuited for frequently changing temporary processes, operational details used by only a handful of tasks, or steps that belong in scripts or skills. Once written into an encyclopedia, they lose stability and credibility — team members can no longer tell whether it describes current rules or a discussion left over from six months ago. The system will also learn a terrible pattern: treating expired conventions as current law.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layer approvals by risk; introduce hooks last.&lt;/strong&gt; Permission approvals should be layered by irreversibility and environmental sensitivity (read operations &amp;lt; write operations &amp;lt; pushing code / accessing sensitive environments), rather than applying blunt per-tool toggles, because what teams actually need to control is consequences, not button names. Hooks are an advanced automation interface, best introduced after foundational governance has stabilized — otherwise they easily introduce new complexity: scripts nobody maintains, trigger timing nobody can explain, and debugging costs higher than manual operations. The more mature judgment is to first stabilize the floor with review, CI, and minimal documentation, then consider more complex orchestration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;9.-ten-principles&quot; tabindex=&quot;-1&quot;&gt;9. Ten Principles&lt;/h2&gt;
&lt;p&gt;The book closes by condensing everything into ten principles:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Treat the model as an unstable component, not a colleague.&lt;/strong&gt; A model may talk like a colleague, but it won&#39;t automatically acquire stability, accountability, or sustained judgment. The sooner you accept this, the sooner the system starts adding permissions, recovery, verification, and rollback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The prompt is part of the control plane.&lt;/strong&gt; Together with the runtime, tool schemas, memory, and hooks, it forms the control plane. Treat the prompt as a persona setting, and you&#39;ll end up with a system that performs well but isn&#39;t constrained.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The query loop is the agent system&#39;s heartbeat.&lt;/strong&gt; Input governance, streaming consumption, tool scheduling, recovery branches, and stop conditions are all part of this heartbeat. A system without an execution loop may produce demos, but it doesn&#39;t qualify as a runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tools are managed execution interfaces.&lt;/strong&gt; Once a model starts touching shell and filesystem, the question shifts from &amp;quot;can it speak&amp;quot; to &amp;quot;will it leave consequences.&amp;quot; The more dangerous the tool, the less it should be treated as an ordinary capability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context is working memory.&lt;/strong&gt; Being able to stuff something into context doesn&#39;t mean you should. Compaction&#39;s goal is preserving the semantic foundation for continued work — the standard isn&#39;t &amp;quot;enough&amp;quot; but &amp;quot;governable.&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The error path is the main path.&lt;/strong&gt; Prompt-too-long, output truncation, interruptions, hook loops, and compaction failures are all routine weather for long-session agents. Recovery and circuit breaking must exist by design, not be retrofitted after incidents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recovery&#39;s goal is to keep working.&lt;/strong&gt; The best action after truncation is usually continuation; when compaction fails, the top priority is restoring the system&#39;s ability to breathe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-agent&#39;s purpose is partitioning uncertainty.&lt;/strong&gt; Research, implementation, verification, and synthesis go into different containers, with the coordinator converging understanding. The real value of parallelism isn&#39;t speed — it&#39;s clearer responsibility boundaries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verification must be independent — systems cannot grade their own work.&lt;/strong&gt; For any important task, verification should be an independent stage, ideally with an independent role.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team institutions matter more than individual skill.&lt;/strong&gt; An expert can tame an agent through experience alone; a team cannot. Only by institutionalizing individual experience can an agent system become an organizational capability rather than a personal party trick.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Building the Team While Building the Product: Notes from an AI Startup</title>
    <link href="https://2bab.me/en/blog/2026-04-04-knowing-and-doing/"/>
    <updated>2026-04-04T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2026-04-04-knowing-and-doing/</id>
    <content type="html">&lt;p&gt;Over the past few years, I have grown into a hands-on Tech Lead Manager (TLM) role. I still deliver technical work while managing engineers, making product decisions, and building the team. After reading several books, I tried some of their ideas in practice. I wanted to record what worked, what fell short, and what I am still figuring out. These notes cover the role itself, management planning, team building, and day-to-day execution.&lt;/p&gt;
&lt;p&gt;I currently work at a small, geographically distributed vertical AI company. These are personal observations shaped by that specific environment, with no claim to being a universal management playbook. I have omitted identifying details, and the people in the examples below are fictional.&lt;/p&gt;
&lt;h2 id=&quot;defining-the-role&quot; tabindex=&quot;-1&quot;&gt;Defining the Role&lt;/h2&gt;
&lt;p&gt;A lot of people move into technical leadership this way: the previous lead leaves, and someone else has to step up. That was my path. When the Tech Lead at my previous company left, I became the de facto Tech Lead. I took over delivery, technical decisions, mentoring, code reviews, and cross-team coordination, including finishing a half-built external SDK across three client platforms.&lt;/p&gt;
&lt;p&gt;Many of my friends love code for its own sake and care deeply about elegant architecture. About a year after moving to Singapore, I realized I was becoming more of a problem solver. Two and a half years on infrastructure teams let me explore difficult technical problems. I also supported other engineers and the long-term evolution of our products. That experience gave me enough material to write &lt;em&gt;Extending Android Builds&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Later, moving countries and companies widened my path again. Writing the book and becoming a Google Developer Expert shifted my focus as well. I began working more on communication, design, and projects where a broader perspective could help others.&lt;/p&gt;
&lt;p&gt;My technical scope is now wider. It includes Android and iOS architecture, UI/UX systems, evaluation for AI workflows, and data analysis that can reveal product needs. In a first-line startup role, I still stay close to implementation. &amp;quot;Vision&amp;quot; is not a reason to lose touch with the details.&lt;/p&gt;
&lt;p&gt;Today I work as a hands-on Tech Lead Manager and cover part of the Product Owner role. The work moves between collecting data, writing a PRD, and making architecture decisions. As the company evolved, I became part instrumentalist and part conductor: a supportive, highly engaged player-coach.&lt;/p&gt;
&lt;p&gt;Two opposite management failure modes help explain the distinction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One manager called themselves supportive but had little technical context. They could distribute fixed resources from above, yet made no serious attempt to create room for experimentation. Career support remained verbal, and rewards were based on tenure.&lt;/li&gt;
&lt;li&gt;Another manager came from engineering and expected everyone to work at their pace. They sent team-wide messages at three or four in the morning. Their own capacity was limited, and they never learned how to develop people who could take ownership outside their strengths. No one can stay at the front of every problem forever.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Support requires context and concrete action. Involvement should help other people take ownership.&lt;/p&gt;
&lt;p&gt;Early in my current role, I wrote down a few reminders for myself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Finishing a job properly matters more than making it perfect.&lt;/li&gt;
&lt;li&gt;We are here to build a viable business while maintaining a culture where everyone can work with dignity.&lt;/li&gt;
&lt;li&gt;Simplicity, maintainability, and efficiency usually matter more than extreme performance or clever optimization.&lt;/li&gt;
&lt;li&gt;Building a company and sustaining one are both hard. Push hard when it is time to push, and stabilize carefully when it is time to stabilize.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also follow a few quieter rules as an engineering manager:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I will always have rough edges. At the very least, I should not be a jerk.&lt;/li&gt;
&lt;li&gt;Do not compete with your own team for ownership, especially on technical work.&lt;/li&gt;
&lt;li&gt;Give credit loudly, and make sure collaborators know who earned it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Teamwork is the clearest thread running through how I define the role:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maintain backup coverage so people can take leave without pressure. If iOS is short-handed today, I can write some iOS code. If I am away tomorrow, someone else can watch a holiday campaign release.&lt;/li&gt;
&lt;li&gt;Give senior and junior engineers opportunities beyond code, including operational coordination, product design, and data analysis. Durable collaboration helps people see what else they can contribute.&lt;/li&gt;
&lt;li&gt;Share context early, prepare for likely scenarios, and let other people make technical decisions. Listen to ideas and frustrations, find the pain points, and turn them into concrete work.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;management-planning&quot; tabindex=&quot;-1&quot;&gt;Management Planning&lt;/h2&gt;
&lt;p&gt;I organize management planning around four questions: mandate, goals, team, and execution path. Maintaining the Android and iOS products is the baseline mandate of a Mobile App team. I also ask what additional value that team can create:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;While maintaining App A, identify architectural pieces that a future App B could reuse and prepare them early.&lt;/li&gt;
&lt;li&gt;Platform changes can also create shared work. Android 16 or 17 and responsive iPad layouts may require similar UI/UX work across Mobile and Web.&lt;/li&gt;
&lt;li&gt;After a year of learning how to use AI Coding tools, bring that experience into adjacent areas. The team may help with internal AI tools or selected frontend and backend tasks, and discover growth opportunities from a different angle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Goals deserve the same scrutiny. Doing the work correctly is often easier than finding the right work. In many startups, taking the right goal to 80 percent matters more. It often beats improving an established area from 90 to 92 percent. Keeping the mandate flexible and adjusting direction have been some of the most rewarding parts of the job.&lt;/p&gt;
&lt;p&gt;The technical path depends on the team and how the business makes money. Some consumer companies acquire users through paid ads and run a portfolio of products, hoping a few become hits. A vertical subscription business grows through long-term retention. The two need different technical paths.&lt;/p&gt;
&lt;p&gt;A portfolio business may prioritize reuse, low launch costs, and fast experiments. A subscription business may care more about reliability, product quality, and long-term maintainability.&lt;/p&gt;
&lt;p&gt;A popular technology is not always the right choice. The real question is whether it helps the business retain users and stay ahead of competitors.&lt;/p&gt;
&lt;p&gt;When several technologies fit both the business and the team, my checklist looks roughly like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Can we contain a failure? If a cross-platform framework misses an important case, can we fall back at a reasonable cost? If an open-source compiler or dependency breaks, can we find the cause quickly? Access to an expert or an enthusiast on the team changes the risk.&lt;/li&gt;
&lt;li&gt;Who builds and stewards the technology? I trust ecosystems backed by organizations that use the technology in production and maintain it for the long term. It also helps when important work stays in a healthy core project.&lt;/li&gt;
&lt;li&gt;Can we adapt the smaller pieces ourselves? A maintained fork may fit the team&#39;s workflow, while a small adapter can make one capability reusable across internal projects.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Coding Agents do not remove the human side of these decisions. Asking an agent to understand a codebase as large as AOSP and find the right solution remains difficult. High-impact choices still depend on context and access to expertise.&lt;/p&gt;
&lt;p&gt;The remaining question is the team itself: who we need, and how their strengths fit together.&lt;/p&gt;
&lt;h2 id=&quot;building-the-team&quot; tabindex=&quot;-1&quot;&gt;Building the Team&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A team feels more like an orchestra to me. There are different players, principal players, and a conductor, but everyone contributes to the same performance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Large companies often standardize hiring for consistency and scale. Standardized interviews often favor candidates who know how to prepare for them. Strong candidates with less conventional backgrounds may be overlooked. A startup can collect a wider set of signals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A strong academic record and an impressive GitHub project show discipline and sustained learning in different ways.&lt;/li&gt;
&lt;li&gt;Between similar technical backgrounds, the person who understands the product and follows the industry may be better prepared for startup work.&lt;/li&gt;
&lt;li&gt;Ten years on one stack and ten stacks in ten years tell different stories. Either candidate loses credibility if they never look at what the company uses before the interview. &amp;quot;I can learn it after I join&amp;quot; needs evidence.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No one can understand another person in a one-hour call. Three to five focused conversations can provide independent signals, as long as the process respects the candidate&#39;s time. I would build a Mobile App team around complementary people, rather than a checklist of Android, iOS, and Flutter job titles. It might include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An Android engineer with deep technical foundations who has also learned some iOS and shipped a small iOS app independently.&lt;/li&gt;
&lt;li&gt;A mobile lead who has worked across very different organizational cultures and practices UI/UX design as a serious hobby.&lt;/li&gt;
&lt;li&gt;A former English teacher who became a programmer, built several independent products, and contributed many pull requests to open-source projects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Working in multinational companies broadened my view of what a team could look like. Demographic representation matters, and so do differences in professional background, product exposure, language, and problem-solving habits. Together, they affect team resilience and the range of options available during planning. Hiring can never be perfectly objective, but team composition can still be deliberate.&lt;/p&gt;
&lt;p&gt;Experience is also harder to read now. One candidate may have eight years of professional experience but resist AI-assisted development. A recent graduate may have eight years of programming experience. Similar trade-offs appear between technical depth and breadth, or between business knowledge and familiarity with the current stack. AI can produce a lot of code. An experienced specialist can still spot a weak signal and act on years of context. I value people who can see what they need to learn and close the gap to a useful 60 percent quickly. They also need to work with the untidy constraints of real products.&lt;/p&gt;
&lt;p&gt;A friend uses an interview question I like: &amp;quot;Tell me about a product you think is well made, and explain why.&amp;quot; I browse Mobbin for design references for the same reason. Real products expose judgment in a way an abstract question cannot. For some roles, another standard interview round may not tell us much. A paid, time-boxed trial or a short pair-working exercise can reveal more. So can asking a candidate to build a small tool with a Coding Agent.&lt;/p&gt;
&lt;h2 id=&quot;back-to-the-work&quot; tabindex=&quot;-1&quot;&gt;Back to the Work&lt;/h2&gt;
&lt;p&gt;Most experienced technical professionals already have the basic discipline to manage tasks, and clear SOPs can improve it over time. I think task management and Agile receive more attention than they deserve in small, experienced teams. Coordination roles lose leverage when they are detached from product and technical context. In a large, regulated, or dependency-heavy organization, a strong TPM or Scrum Master may still be essential. The test is whether the role resolves real constraints or maintains reporting machinery.&lt;/p&gt;
&lt;p&gt;Even if a particular practice makes one step in an SOP five or ten percent more efficient, the returns eventually flatten. In practice, the process still has to fit the project. A mobile app rarely ships a new version every day, while a web product may ship several times a day.&lt;/p&gt;
&lt;p&gt;Teams still need clear priorities, dependencies, and timelines, but heavier tracking does not create ownership. Avoid changing direction in a morning meeting and expecting results that evening. A written plan, agreed timing, and enough context remove a great deal of friction.&lt;/p&gt;
&lt;p&gt;Day-to-day delivery is shaped by both team routines and what we expect from individual engineers. The 10x engineer is often misunderstood as someone who writes code ten times faster. I think the real multiplier is learning speed. An engineer who can enter an unfamiliar area and quickly become a domain expert may deliver far more than ten times faster. Coding Agents already generate code quickly, so raw coding speed is no longer a useful comparison. What matters is how fast someone can find the right problem, build the necessary context, and turn that understanding into a working result.&lt;/p&gt;
&lt;p&gt;Many people spend years searching for the perfect to-do app and end up using the one built into their phone. At work, try a blank document: list what the team needs to do next, grouped by release, week, or sprint. Let the project determine the process, then return your attention to the work in front of you.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Responsive Layout Fundamentals</title>
    <link href="https://2bab.me/en/blog/2026-03-16-responsive-layout-fundamentals/"/>
    <updated>2026-03-16T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2026-03-16-responsive-layout-fundamentals/</id>
    <content type="html">&lt;p&gt;Getting started with responsive design really comes down to two things: where to put the navigation, and how to arrange content. As screens get larger, each of these has a well-established set of patterns. Android&#39;s developer docs have the most thorough summary, complete with ready-made components. iOS and Web follow similar ideas but require more manual work.&lt;/p&gt;
&lt;h2 id=&quot;1.-navigation&quot; tabindex=&quot;-1&quot;&gt;1. Navigation&lt;/h2&gt;
&lt;p&gt;The core question: as the screen gets wider, where does the navigation go?&lt;/p&gt;
&lt;h3 id=&quot;four-navigation-patterns&quot; tabindex=&quot;-1&quot;&gt;Four Navigation Patterns&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Bottom Bar&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The most common mobile navigation. 3-5 icons lined up at the bottom of the screen, within thumb reach. The default choice for phones in portrait.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌───────────┐
│           │
│  Content  │
│           │
├──┬──┬──┬──┤
│🏠│📋│🔍│⚙️│
└──┴──┴──┴──┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Top Bar&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The traditional Web approach. Logo on the left, nav items on the right. Works well when there aren&#39;t too many items and brand presence matters. Rarely used as the primary navigation on native mobile apps, but extremely common on the Web — marketing sites, docs, SaaS products.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──────────────────────┐
│ Logo  Home  About  ▾ │
├──────────────────────┤
│                      │
│      Content         │
│                      │
└──────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On narrow screens, the Top Bar typically collapses into a hamburger menu that opens as a full or half-screen overlay. Web developers know this drill well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rail&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A narrow sidebar with just icons and short labels, pinned to the left edge. The go-to choice for tablets and foldables — makes better use of horizontal space than a Bottom Bar, without eating as much area as a Drawer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──┬───────────┐
│🏠│           │
│📋│  Content  │
│🔍│           │
│⚙️│           │
└──┴───────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Drawer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A full sidebar with icons and complete labels. Can hold many items, supports grouping and nesting. The standard for large screens and desktops. Some implementations support expand/collapse.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──────────┬─────────┐
│ 🏠 Home   │         │
│ 📋 Orders │ Content │
│ 🔍 Search │         │
│ ⚙️ Settings│         │
└──────────┴─────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;switching-logic&quot; tabindex=&quot;-1&quot;&gt;Switching Logic&lt;/h3&gt;
&lt;p&gt;As screen width increases:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Phone        Tablet/Foldable    Desktop/Large
Bottom Bar  →    Rail       →   Drawer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Web has an additional path:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Phone              Tablet          Desktop
Hamburger(☰)  →  Top Bar    →  Top Bar (more items)
  or               or              or
Bottom Bar    →  Rail        →  Drawer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which path to take on the Web depends on the product type. Tool-oriented products (admin panels, email, docs) go the Rail/Drawer route. Content-oriented products (blogs, marketing sites, e-commerce) go the Top Bar route. Native apps almost always follow the Bottom Bar → Rail → Drawer path.&lt;/p&gt;
&lt;h3 id=&quot;platform-support&quot; tabindex=&quot;-1&quot;&gt;Platform Support&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Android&lt;/th&gt;
&lt;th&gt;iOS&lt;/th&gt;
&lt;th&gt;Web&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bottom Bar&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NavigationBar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TabView&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Roll your own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top Bar&lt;/td&gt;
&lt;td&gt;Uncommon&lt;/td&gt;
&lt;td&gt;Uncommon&lt;/td&gt;
&lt;td&gt;Roll your own (trivial to find)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rail&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NavigationRail&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No native component&lt;/td&gt;
&lt;td&gt;Roll your own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drawer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NavigationDrawer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No native component&lt;/td&gt;
&lt;td&gt;Roll your own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-switch&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NavigationSuiteScaffold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;None, manually check Size Class&lt;/td&gt;
&lt;td&gt;None, use CSS breakpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Android is probably the only platform with a turnkey solution. &lt;code&gt;NavigationSuiteScaffold&lt;/code&gt; takes your nav items and automatically switches between the three forms based on window size — one line of code. iOS and Web both require you to build this switching logic yourself, though Web often leverages Tailwind breakpoints and mature component libraries to get there.&lt;/p&gt;
&lt;h2 id=&quot;2.-content-area-(canonical-layouts)&quot; tabindex=&quot;-1&quot;&gt;2. Content Area (Canonical Layouts)&lt;/h2&gt;
&lt;p&gt;Once navigation is settled, the remaining question is how the content area adapts to different screen sizes. Google identified three canonical layouts that cover the vast majority of cases.&lt;/p&gt;
&lt;h3 id=&quot;list-detail&quot; tabindex=&quot;-1&quot;&gt;List-Detail&lt;/h3&gt;
&lt;p&gt;On small screens, the list and detail are separate pages — tap an item to navigate to its detail. On large screens, they sit side by side.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Small:                   Large:
┌──────────┐            ┌─────┬────────┐
│ Item A   │            │ A   │        │
│ Item B   │    →       │ B ● │ Detail │
│ Item C   │            │ C   │ of B   │
└──────────┘            └─────┴────────┘
  Tap → full-screen        Side by side
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The most classic responsive pattern. Email clients, chat apps, settings pages, file managers — all List-Detail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Platform implementations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Android: &lt;code&gt;ListDetailPaneScaffold&lt;/code&gt;, works out of the box&lt;/li&gt;
&lt;li&gt;iOS: &lt;code&gt;NavigationSplitView&lt;/code&gt;, functional but limited in customization&lt;/li&gt;
&lt;li&gt;Web: CSS Grid/Flexbox + media queries, or simply &lt;code&gt;grid-template-columns: 300px 1fr&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;feed&quot; tabindex=&quot;-1&quot;&gt;Feed&lt;/h3&gt;
&lt;p&gt;Content comes in cards. Single column on small screens, multi-column grid on large ones.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Small:             Large:
┌────────┐        ┌────┬────┬────┐
│ Card A │        │ A  │ B  │ C  │
│ Card B │   →    │ D  │ E  │ F  │
│ Card C │        │ G  │ H  │    │
└────────┘        └────┴────┴────┘
  Single col        Multi-col grid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Social feeds, news, image galleries, product listings. The key is letting the column count adapt to screen width rather than hardcoding it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Platform implementations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Android: &lt;code&gt;LazyVerticalGrid&lt;/code&gt; + &lt;code&gt;GridCells.Adaptive(minSize)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;iOS: &lt;code&gt;LazyVGrid&lt;/code&gt; + &lt;code&gt;GridItem(.adaptive(minimum:))&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Web: &lt;code&gt;grid-template-columns: repeat(auto-fill, minmax(280px, 1fr))&lt;/code&gt; — one line of CSS, and Web actually wins here in simplicity&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;supporting-pane&quot; tabindex=&quot;-1&quot;&gt;Supporting Pane&lt;/h3&gt;
&lt;p&gt;The main content takes the majority of space, with supplementary info alongside. Unlike List-Detail, this isn&#39;t a master-detail relationship — it&#39;s main content plus auxiliary information.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Small:                   Large:
┌──────────┐            ┌────────┬─────┐
│          │            │        │     │
│   Main   │    →       │  Main  │ SP  │
│          │            │        │     │
├──────────┤            └────────┴─────┘
│ Support  │              Main + side panel
└──────────┘
 Below or in a sheet
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maps + search results, video + comments, code editor + properties panel. On small screens, the supporting pane typically sits below, collapses into a bottom sheet, or becomes an expandable overlay.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Platform implementations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Android: &lt;code&gt;SupportingPaneScaffold&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;iOS: No equivalent component, build with &lt;code&gt;HStack&lt;/code&gt; + &lt;code&gt;GeometryReader&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Web: CSS Grid or Flexbox, straightforward&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;combining-layouts&quot; tabindex=&quot;-1&quot;&gt;Combining Layouts&lt;/h3&gt;
&lt;p&gt;In practice, these three often nest inside each other. Take an email client: the overall structure is List-Detail, the list itself is a Feed (email cards), and the detail view might have a Supporting Pane on the side showing attachments or contact info. Once you understand these three base patterns, every variation is just a combination.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Responsive layout = Navigation adaptation + Content area adaptation

Navigation:
  Bottom Bar → Rail → Drawer (native apps)
  Hamburger → Top Bar (Web content sites)

Content area (Canonical Layouts):
  List-Detail      List and detail, separate pages on small screens, side by side on large
  Feed             Card stream, single column on small, multi-column on large
  Supporting Pane  Main content + auxiliary panel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Android has the most complete story here, with dedicated Material 3 components for each pattern. iOS only has &lt;code&gt;NavigationSplitView&lt;/code&gt; and &lt;code&gt;LazyVGrid&lt;/code&gt; roughly covering List-Detail and Feed — the rest is manual. Web has the most flexibility — CSS Grid was designed for exactly these scenarios — but lacks high-level turnkey components, so you assemble them yourself.&lt;/p&gt;
&lt;p&gt;That&#39;s the full picture. When building a screen, first identify which Canonical Layout it falls into, then decide which navigation form to use. The rest is platform API details.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Some Personal Takes on Metro DI</title>
    <link href="https://2bab.me/en/blog/2026-03-15-metro-di-intro/"/>
    <updated>2026-03-15T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2026-03-15-metro-di-intro/</id>
    <content type="html">&lt;p&gt;Metro is a compile-time DI framework for Kotlin Multiplatform, created by Zac Sweers (formerly Android Infra at Slack). Cash App and Vinted are already using it in production. This post is aimed at developers who have some DI experience or are coming from other frameworks — not a comprehensive guide, just notes from my own exploration with some comparisons along the way.&lt;/p&gt;
&lt;h2 id=&quot;core-concept%3A-binding&quot; tabindex=&quot;-1&quot;&gt;Core Concept: Binding&lt;/h2&gt;
&lt;p&gt;At its core, any DI framework maintains a binding table: &lt;strong&gt;when someone asks for type A, give them instance B&lt;/strong&gt;. Metro is no different — all its annotations are essentially adding entries to this table.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RepoA        → RepoAImpl
RepoB        → RepoBImpl
HttpClient   → HttpClient(CIO) { ... }
Json         → Json { ignoreUnknownKeys = true }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you internalize this, the number of annotations starts to feel more manageable. Starts to.&lt;/p&gt;
&lt;h2 id=&quot;the-simplest-case%3A-%40inject&quot; tabindex=&quot;-1&quot;&gt;The Simplest Case: @Inject&lt;/h2&gt;
&lt;p&gt;Add &lt;code&gt;@Inject&lt;/code&gt; to your own class, and Metro knows how to create it. Dependencies in the constructor are automatically resolved and injected:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Inject&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ArticleRepositoryImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; httpClient&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HttpClient&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; json&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Json&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ArticleRepository&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One annotation covers both &amp;quot;I can be created&amp;quot; and &amp;quot;I need these dependencies.&amp;quot; Compare this with Koin:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Koin requires a separate registration&lt;/span&gt;&lt;br /&gt;module &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    factory &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ArticleRepositoryImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, &lt;code&gt;@Inject&lt;/code&gt; creates a factory — a new instance every time. Add &lt;code&gt;@SingleIn&lt;/code&gt; to make it a singleton:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Inject&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@SingleIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppScope&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ArticleRepositoryImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ArticleRepository&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unlike Koin&#39;s separate &lt;code&gt;single {}&lt;/code&gt; and &lt;code&gt;factory {}&lt;/code&gt; functions, the only difference here is whether &lt;code&gt;@SingleIn&lt;/code&gt; is present. A clean design choice.&lt;/p&gt;
&lt;h2 id=&quot;aggregation&quot; tabindex=&quot;-1&quot;&gt;Aggregation&lt;/h2&gt;
&lt;p&gt;This is arguably Metro&#39;s biggest improvement over Dagger.&lt;/p&gt;
&lt;p&gt;With traditional Dagger, you manually wire Modules into Components and maintain a large module list. Koin is similar — you need &lt;code&gt;startKoin { modules(a, b, c, ...) }&lt;/code&gt; somewhere to chain everything together, or use its KCP plugin (or roll your own tool — I made one called &lt;a href=&quot;https://github.com/2BAB/Koncat&quot;&gt;Koncat&lt;/a&gt;). Some argue that this list becomes unwieldy in large projects; I&#39;d say that&#39;s mostly a symptom of poorly structured modules =.= leading to oversized individual modules.&lt;/p&gt;
&lt;p&gt;Metro&#39;s compiler-driven auto-collection looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// In the feature-article-domain module&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Inject&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@SingleIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppScope&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@ContributesBinding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppScope&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ArticleRepositoryImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; httpClient&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HttpClient&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ArticleRepository&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@ContributesBinding(AppScope::class)&lt;/code&gt; means: contribute the binding &lt;code&gt;ArticleRepositoryImpl → ArticleRepository&lt;/code&gt; to the &lt;code&gt;AppScope&lt;/code&gt; graph. When the compiler processes the module where the graph is defined, it automatically scans all dependent modules for classes annotated with &lt;code&gt;@ContributesTo&lt;/code&gt; or &lt;code&gt;@ContributesBinding&lt;/code&gt; and wires them in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Declare and register in one place, no manual aggregation needed.&lt;/strong&gt; This is a significant productivity gain for multi-module projects — you annotate the class and you&#39;re done, no need to update a registration file elsewhere. It&#39;s also more AI-coding friendly: an AI only needs to modify one file when adding a class, rather than remembering to register it in a separate module file.&lt;/p&gt;
&lt;h2 id=&quot;graph-and-scope&quot; tabindex=&quot;-1&quot;&gt;Graph and Scope&lt;/h2&gt;
&lt;p&gt;Metro&#39;s scope doesn&#39;t bind to any lifecycle by itself — it&#39;s just a tag. One scope corresponds to one graph (DI container), roughly equivalent to a &lt;code&gt;Koin&lt;/code&gt; instance.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Declare a scope&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Scope&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;annotation&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; AppScope&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Declare a graph&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@DependencyGraph&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppScope&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; AppGraph&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The lifecycle is entirely determined by when you create and destroy the graph instance: put it in &lt;code&gt;Application&lt;/code&gt; and it lives with the app process; put it in a login session holder and it lives with the user session. Unlike Hilt, Metro doesn&#39;t provide predefined scopes like &lt;code&gt;@ActivityScoped&lt;/code&gt; or &lt;code&gt;@ViewModelScoped&lt;/code&gt; that automatically bind to Android components — more flexibility, but more responsibility.&lt;/p&gt;
&lt;p&gt;Graphs can have parent-child relationships via &lt;code&gt;GraphExtension&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@DependencyGraph&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;UserScope&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; UserGraph &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GraphExtension&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;AppGraph&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Child graphs can access parent bindings, but not vice versa. This is how you implement nested scopes like App → User → Feature.&lt;/p&gt;
&lt;h2 id=&quot;cross-module-dependencies%3A-why-export-modules-exist&quot; tabindex=&quot;-1&quot;&gt;Cross-Module Dependencies: Why Export Modules Exist&lt;/h2&gt;
&lt;p&gt;Say feature-a needs data from feature-b, and vice versa. Direct dependencies would create a cycle, so you extract the interfaces:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;feature-a-export/   ← interfaces only: RepoA
feature-a-domain/   ← implementation: RepoAImpl, depends on feature-b-export
feature-b-export/   ← interfaces only: RepoB
feature-b-domain/   ← implementation: RepoBImpl, depends on feature-a-export
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Feature modules know nothing about each other&#39;s implementations at compile time. In a KMP setup, the assembly happens when the &lt;code&gt;shared&lt;/code&gt; module compiles — since &lt;code&gt;shared&lt;/code&gt; depends on all feature modules, Metro&#39;s compiler plugin can see the complete dependency graph there and wire all contributed bindings into the &lt;code&gt;AppScope&lt;/code&gt; graph. For a standard Android app, this would typically be the app module.&lt;/p&gt;
&lt;h2 id=&quot;trade-offs-worth-noting&quot; tabindex=&quot;-1&quot;&gt;Trade-offs Worth Noting&lt;/h2&gt;
&lt;h3 id=&quot;bindingcontainer-and-%40provides&quot; tabindex=&quot;-1&quot;&gt;BindingContainer and @Provides&lt;/h3&gt;
&lt;p&gt;For your own classes, &lt;code&gt;@Inject&lt;/code&gt; + &lt;code&gt;@ContributesBinding&lt;/code&gt; is concise. But for third-party classes where you can&#39;t add annotations, you need &lt;code&gt;@Provides&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@ContributesTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppScope&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@BindingContainer&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; NetworkBindings &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@Provides&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@SingleIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppScope&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;httpClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HttpClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;HttpClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CIO&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ContentNegotiation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@Provides&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@SingleIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppScope&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Json &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Json &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ignoreUnknownKeys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You need to create an interface, annotate it with &lt;code&gt;@BindingContainer&lt;/code&gt; and &lt;code&gt;@ContributesTo&lt;/code&gt;, then write &lt;code&gt;@Provides&lt;/code&gt; functions inside.&lt;/p&gt;
&lt;p&gt;Compare with Koin:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;module &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    single &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;HttpClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CIO&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ContentNegotiation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    single &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Json &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ignoreUnknownKeys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Koin feels more intuitive for this particular scenario. The name &lt;code&gt;@BindingContainer&lt;/code&gt; is also less immediately obvious than Dagger&#39;s &lt;code&gt;@Module&lt;/code&gt; — though &lt;code&gt;@Module&lt;/code&gt; isn&#39;t exactly self-explanatory either. This is boilerplate you&#39;ll need to memorize.&lt;/p&gt;
&lt;h3 id=&quot;assistedinject&quot; tabindex=&quot;-1&quot;&gt;AssistedInject&lt;/h3&gt;
&lt;p&gt;When a ViewModel needs runtime parameters (e.g., an ID), you use assisted injection:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@AssistedInject&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DetailViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@Assisted&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;           &lt;span class=&quot;token comment&quot;&gt;// provided at call site&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; repo&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DetailRepository&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// provided by DI&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@AssistedFactory&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; Factory &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DetailViewModel&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compare with Koin:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DetailViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; repo&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DetailRepository&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Registration&lt;/span&gt;&lt;br /&gt;module &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    viewModel &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DetailViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Usage&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; vm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; koinViewModel&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;DetailViewModel&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parametersOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;some-id&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Metro requires &lt;code&gt;@AssistedInject&lt;/code&gt;, &lt;code&gt;@Assisted&lt;/code&gt;, &lt;code&gt;@AssistedFactory&lt;/code&gt;, plus a Factory interface. The mental model shifts from &amp;quot;how do I create a ViewModel with parameters&amp;quot; to &amp;quot;I need to declare a factory interface.&amp;quot; On the other hand, Metro&#39;s approach is type-safe — &lt;code&gt;create(id: String)&lt;/code&gt; uses named parameters, while Koin&#39;s &lt;code&gt;parametersOf(...)&lt;/code&gt; relies on positional arguments where ordering mistakes won&#39;t be caught at compile time.&lt;/p&gt;
&lt;p&gt;In the age of AI-assisted coding, the extra boilerplate is less of a concern since AI handles it well — and type safety arguably matters more when AI is generating the code too.&lt;/p&gt;
&lt;h3 id=&quot;%40viewmodelkey-redundancy&quot; tabindex=&quot;-1&quot;&gt;@ViewModelKey Redundancy&lt;/h3&gt;
&lt;p&gt;Registering a ViewModel into the DI map requires:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Inject&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@ViewModelKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ArticleListViewModel&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@ContributesIntoMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppScope&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ArticleListViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@ViewModelKey(ArticleListViewModel::class)&lt;/code&gt; placed on &lt;code&gt;class ArticleListViewModel&lt;/code&gt; — the key is the class itself, yet you must spell it out explicitly. I filed an &lt;a href=&quot;https://github.com/ZacSweers/metro/issues/1994&quot;&gt;issue&lt;/a&gt; suggesting default inference, but it was declined — the rationale being that special-casing ViewModel keys would set a precedent for other map keys. A reasonable stance, though it does mean repeating this line for every ViewModel.&lt;/p&gt;
&lt;h2 id=&quot;overall-impressions&quot; tabindex=&quot;-1&quot;&gt;Overall Impressions&lt;/h2&gt;
&lt;p&gt;Aggregation combined with &lt;code&gt;@Inject&lt;/code&gt; + &lt;code&gt;@ContributesBinding&lt;/code&gt; is where Metro shines — multi-module projects benefit from automatic registration, and missing bindings are caught at compile time.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;@Provides&lt;/code&gt; + &lt;code&gt;@BindingContainer&lt;/code&gt; and &lt;code&gt;@AssistedInject&lt;/code&gt; patterns carry more ceremony. The annotation count inherits the complexity of the Dagger lineage, which can feel like a steep learning curve for developers less familiar with that ecosystem. Zac Sweers comes from the Dagger/Anvil world, so this is a natural evolution for him — for others, it&#39;s a new set of concepts to absorb.&lt;/p&gt;
&lt;p&gt;Metro is currently at 0.11.x, not yet 1.0, but development has been quite active.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Teaching AI Coding Agents with Submodules</title>
    <link href="https://2bab.me/en/blog/2026-03-15-ai-skills-submodule-pattern/"/>
    <updated>2026-03-15T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2026-03-15-ai-skills-submodule-pattern/</id>
    <content type="html">&lt;p&gt;Coding agents have knowledge cutoff dates — they tend to hallucinate when dealing with new frameworks or newer API versions. Even Context 7&#39;s documentation knowledge is roughly a month-old snapshot, which can already be outdated for fast-moving frameworks. So &amp;quot;how to feed AI the correct framework knowledge&amp;quot; is a practical problem.&lt;/p&gt;
&lt;p&gt;I recently came across antfu&#39;s &lt;a href=&quot;https://github.com/antfu/skills&quot;&gt;skills&lt;/a&gt; repo. The idea is to process official documentation into AI-consumable skill files and load them into Claude Code as context. It&#39;s still at the proof-of-concept stage, but the architecture is worth learning from. I tested a similar approach in a KMP project.&lt;/p&gt;
&lt;h2 id=&quot;antfu&#39;s-approach&quot; tabindex=&quot;-1&quot;&gt;antfu&#39;s Approach&lt;/h2&gt;
&lt;p&gt;He categorizes skills by source into three types:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hand-maintained&lt;/strong&gt;: Personal coding preferences — Composition API, TypeScript, pnpm workspace, etc. Pure taste files, no automation involved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generated from official docs&lt;/strong&gt;: vue, nuxt, vite, vitest, etc. Official documentation repos are pulled in as submodules, then scripts extract and distill them into &lt;a href=&quot;http://skill.md/&quot;&gt;SKILL.md&lt;/a&gt; files. This is the main focus.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vendored from external projects&lt;/strong&gt;: Projects like slidev and vueuse maintain their own &lt;code&gt;skills/&lt;/code&gt; directories, which are synced over directly. This is essentially pushing an ecosystem norm — encouraging framework authors to maintain AI-facing documentation themselves. vueuse has already set up a dedicated &lt;code&gt;vueuse/skills&lt;/code&gt; repo for this.&lt;/p&gt;
&lt;p&gt;The core mechanism is submodules. &lt;code&gt;.gitmodules&lt;/code&gt; has two categories of references:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# sources — official doc repos, used to generate skills&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;submodule &quot;sources/vue&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key attr-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;sources/vue&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key attr-name&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;https://github.com/vuejs/docs&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# vendor — skills maintained by external projects, synced directly&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;submodule &quot;vendor/slidev&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key attr-name&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;vendor/slidev&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key attr-name&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;https://github.com/slidevjs/slidev&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output directory structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;skills/vue/
├── SKILL.md              # distilled framework knowledge, the main file for AI
├── GENERATION.md         # metadata: source commit SHA, generation date
└── references/           # supplementary details
    ├── advanced-patterns.md
    └── core-new-apis.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generated skills have &lt;code&gt;GENERATION.md&lt;/code&gt;, vendored ones have &lt;code&gt;SYNC.md&lt;/code&gt; — both for version tracking with exact commit SHAs. You can tell at a glance which version of the official docs the current skill corresponds to, and whether it&#39;s outdated.&lt;/p&gt;
&lt;h2 id=&quot;putting-it-into-practice&quot; tabindex=&quot;-1&quot;&gt;Putting It into Practice&lt;/h2&gt;
&lt;p&gt;As an example, I have a project using &lt;a href=&quot;https://github.com/ZacSweers/metro&quot;&gt;Metro&lt;/a&gt; (a compile-time DI framework for KMP — more details in my &lt;a href=&quot;https://2bab.me/en/blog/2026-03-15-metro-di-intro&quot;&gt;previous post&lt;/a&gt;). AI knows almost nothing about it. Following the approach above, I set up something similar:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;skills/
├── sources/
│   └── metro/                   # git submodule → ZacSweers/metro
├── metro/
│   ├── metro-core.md            # @DependencyGraph, @Provides, createGraph...
│   ├── metro-injection.md       # @Inject, @AssistedInject...
│   ├── metro-aggregation.md     # @ContributesTo, @ContributesBinding...
│   ├── metro-scopes.md          # @Scope, @SingleIn, AppScope...
│   ├── metro-multiplatform.md   # KMP graph patterns...
│   └── metro-viewmodels.md      # ViewModelKey, MetroViewModelFactory...
├── update-skills.sh
└── README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The submodule is pinned to the tag matching the Metro version declared in &lt;code&gt;libs.versions.toml&lt;/code&gt;. The skill files are then referenced in &lt;code&gt;AGENTS.md&lt;/code&gt; (Claude Code&#39;s project instruction file), giving the AI correct API references when working with DI code.&lt;/p&gt;
&lt;p&gt;Two additional things worth mentioning:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A Gradle version-check plugin&lt;/strong&gt;. A convention plugin that compares the Metro version in &lt;code&gt;libs.versions.toml&lt;/code&gt; against the submodule&#39;s git tag, and warns on mismatch. It&#39;s hooked to the debug build&#39;s package task, so it naturally triggers during daily development.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An update script&lt;/strong&gt;. &lt;code&gt;update-skills.sh&lt;/code&gt; reads the version from the toml file, checks out the submodule to the corresponding tag, diffs the documentation changes, and invokes Claude CLI to update the skill files.&lt;/p&gt;
&lt;h2 id=&quot;why-this-works-well&quot; tabindex=&quot;-1&quot;&gt;Why This Works Well&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Docs as data source&lt;/strong&gt;. Instead of manually copying documentation into prompts, you reference the official repo directly. Submodules ensure you&#39;re working with first-party content, no second-hand drift.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Traceable&lt;/strong&gt;. You can verify exactly which version a skill file corresponds to. When it&#39;s outdated, run the script to update — no need to manually review changelogs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extensible&lt;/strong&gt;. Adding skills for another framework is straightforward: add a submodule, write a few markdown files, reference them in &lt;a href=&quot;http://agents.md/&quot;&gt;AGENTS.md&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;limitations&quot; tabindex=&quot;-1&quot;&gt;Limitations&lt;/h2&gt;
&lt;p&gt;antfu&#39;s approach is centered around the frontend ecosystem (Vue/Vite/Nuxt), with skill installation via pnpm — not directly applicable to other tech stacks. My adapted version is simpler — submodule + hand-written skill files + shell script, without the meta.ts and vendor sync machinery. But for the scenario of &amp;quot;a project using two or three frameworks that AI doesn&#39;t know well,&amp;quot; it gets the job done.&lt;/p&gt;
&lt;p&gt;Skill file quality matters a lot. Too long and the AI may not read it all; too short and you miss critical APIs. I currently keep each file under 200 lines, split by topic (core / injection / aggregation / scopes / multiplatform / viewmodels). In practice, Claude Code has been able to find and reference the right files on demand.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Automated AI Workflow - We Localized Popular Documentation for the Community</title>
    <link href="https://2bab.me/en/blog/2025-04-10-open-ai-doc-intro/"/>
    <updated>2025-04-10T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2025-04-10-open-ai-doc-intro/</id>
    <content type="html">&lt;p&gt;Recently, together with community partners, I built a site called &lt;a href=&quot;https://openaidoc.org/&quot;&gt;Open AIDoc&lt;/a&gt;. It&#39;s a &lt;strong&gt;public welfare&lt;/strong&gt; project powered by Gemini, aimed at &lt;strong&gt;tracking and translating popular open-source library documentation into Simplified/Traditional Chinese, Japanese, Korean, and multiple other languages&lt;/strong&gt;. In today&#39;s v0.1.0 release, we&#39;ve provided synchronized translations of the latest &lt;strong&gt;Kotlin&lt;/strong&gt; and &lt;strong&gt;Koin&lt;/strong&gt; documentation. Plans already include expanding to &lt;strong&gt;SqlDelight&lt;/strong&gt;, &lt;strong&gt;Ktor&lt;/strong&gt;, and other libraries we&#39;re familiar with, and continuously adding documentation translation sync services for &lt;strong&gt;different domains&lt;/strong&gt; in the future.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202504101349493.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The story began when I was helping &lt;a href=&quot;https://insert-koin.io/&quot;&gt;Koin&lt;/a&gt; build a Chinese community recently, and found that some new members&#39; first question was &amp;quot;Is there Chinese documentation?&amp;quot; This piqued my interest, and soon I found similar questions in some Kotlin community groups. I tried to understand the demand for localized documentation in the AI era, because I know quite a few browser plugins can already achieve high-quality translation. But digging deeper, I found this might still be a widespread problem, especially for novice engineers in East Asia. On one hand, East Asia overall has relatively strong language systems of their own. Although English learning has been added to compulsory education in most regions, there are numerous native language materials in the programming field, and many developers still prefer native language materials. On the other hand, just because browser plugins exist doesn&#39;t mean everyone uses them. AI development is still in its early stages. Documentation is a webpage that gets &lt;strong&gt;repeatedly viewed&lt;/strong&gt;, and having to &lt;strong&gt;wait a few seconds for translation&lt;/strong&gt; each time, plus &lt;strong&gt;repeatedly spending credits&lt;/strong&gt; for this, is indeed not a great scenario.&lt;/p&gt;
&lt;p&gt;Since this is the case, why not solve the several documentation problems I&#39;ve encountered around me at once using AI? The generated content is stored on a site for repeated reading, which is very &lt;strong&gt;eco-friendly&lt;/strong&gt; :) So I found a like-minded partner, zhuoxuan, to start pushing forward. Of course, we soon discovered things were a bit more troublesome than expected — we needed to add some sync update and format conversion steps. So we built a workflow that roughly looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202504101142559.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The biggest advantage of choosing Gemini 2.0 Flash is that it&#39;s &lt;strong&gt;fast and affordable&lt;/strong&gt;. Even with room for prompt optimization, it already shows good translation results. Following the principle of &amp;quot;talk about completion before perfection,&amp;quot; we &lt;strong&gt;went live&lt;/strong&gt; with the v0.1.0 you see now in about a week.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202504101351461.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202504101351378.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Although it still has some issues, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A small number of very long files have the very last bit truncated, limited by gemini 2.0 flash --&amp;gt; will switch to 2.5 pro later&lt;/li&gt;
&lt;li&gt;A small number of custom formats are difficult to make compatible with the docusaurus engine using regex replacement, currently manually fixed a few --&amp;gt; will switch to vitepress/next.js which is more flexible and tolerant later&lt;/li&gt;
&lt;li&gt;All heading links (with # signs) are broken because names don&#39;t match at all after translation --&amp;gt; still looking for solutions&lt;/li&gt;
&lt;li&gt;Regex replacement scripts can&#39;t guarantee that all parts work properly after replacement --&amp;gt; need to optimize scripts or use other libraries to assist (via AST or similar)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But overall we&#39;ve achieved the goal we wanted from the start: first solve the problems around us, then slowly upgrade and iterate. At the same time, we&#39;re also collecting community feedback:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Which documentation is English-only, or has outdated Chinese versions — we&#39;ll prioritize the high-demand ones;&lt;/li&gt;
&lt;li&gt;Which Prompts aren&#39;t well-written — organize contribution guidelines for everyone to submit PRs to help fix together;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Additionally: Gemini costs are currently paid personally. Thanks here to the &lt;a href=&quot;https://developers.google.com/program&quot;&gt;Google Developer Program Premium Membership&lt;/a&gt; (including GCP Credits) gifted by the Google Developer Expert program. It&#39;s natural to give back these Credits to the community. After the Credits expire, we&#39;ll consider other methods. Website deployment costs including domain, servers, and traffic will be sponsored by my personal consulting and solutions company &lt;a href=&quot;https://binarytape.com/&quot;&gt;BinaryTape&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since it&#39;s a public welfare project, we can&#39;t guarantee time to do too much documentation adaptation. We hope more like-minded partners will join to build together. Everyone is also welcome to &lt;strong&gt;share, like, and forward&lt;/strong&gt; more, so more developers can &lt;strong&gt;enjoy the latest localized documentation services&lt;/strong&gt; and get updates on documentation changes.&lt;/p&gt;
&lt;p&gt;Website: &lt;a href=&quot;https://openaidoc.org/&quot;&gt;openaidoc.org&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Four Ways to Optimize SDK Initialization Callbacks in Kotlin</title>
    <link href="https://2bab.me/en/blog/2025-01-02-kotlin-sdk-callback-optimization/"/>
    <updated>2025-01-02T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2025-01-02-kotlin-sdk-callback-optimization/</id>
    <content type="html">&lt;p&gt;In Android development, we often need to carefully handle the initialization and callbacks of various SDKs, especially when asynchronous operations are involved. In this article, we&#39;ll discuss four ways to optimize these callbacks in Kotlin, primarily using several of Google&#39;s Android SDKs as examples like Firebase, AdMob, etc.&lt;/p&gt;
&lt;h2 id=&quot;problems-with-the-traditional-callback-mechanism&quot; tabindex=&quot;-1&quot;&gt;Problems with the Traditional Callback Mechanism&lt;/h2&gt;
&lt;p&gt;First, let&#39;s revisit the traditional callback mechanism. In Java, callback functions are commonly used to handle the results of asynchronous tasks, but this method has several drawbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Poor code readability&lt;/strong&gt;: Excessive callback nesting can easily lead to &amp;quot;callback hell.&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex state management&lt;/strong&gt;: Manually maintaining initialization states increases code complexity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inability to await&lt;/strong&gt;: It&#39;s impossible to await for initialization to complete when calling methods; polling or blocking are the only options.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Kotlin, although we have powerful tools like coroutines, callback issues still exist due to the need to maintain compatibility with a large amount of Java code and legacy projects. Taking Google&#39;s SDKs as an example, although some have provided KTX extension packages, they have not optimized the initialization process.&lt;/p&gt;
&lt;h2 id=&quot;four-optimization-methods&quot; tabindex=&quot;-1&quot;&gt;Four Optimization Methods&lt;/h2&gt;
&lt;h3 id=&quot;1.-using-atomicboolean-to-simply-record-state&quot; tabindex=&quot;-1&quot;&gt;1. Using &lt;code&gt;AtomicBoolean&lt;/code&gt; to Simply Record State&lt;/h3&gt;
&lt;p&gt;The most straightforward method is to use an &lt;code&gt;AtomicBoolean&lt;/code&gt; to record whether the SDK has been successfully initialized.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; sdkInitialized &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AtomicBoolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;MobileAds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; InitializationStatus &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Check if all adapters are ready&lt;/span&gt;&lt;br /&gt;    sdkInitialized&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;adapterStatusMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;values&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;initializationState &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; AdapterStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;State&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;READY&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The issue is&lt;/strong&gt;: This method requires manually checking &lt;code&gt;sdkInitialized&lt;/code&gt; every time you call other SDK methods; it cannot await for initialization to complete (while yield the CPU resources).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;prepareNextRewardedAd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sdkInitialized&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Proceed with normal initialization&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Show error message&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;pros-and-cons&quot; tabindex=&quot;-1&quot;&gt;Pros and Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simple to implement, suitable for situations where the initialization process is short.&lt;/li&gt;
&lt;li&gt;Thread-safe, avoiding concurrency issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Requires manual state checks wherever the SDK is used, increasing code redundancy.&lt;/li&gt;
&lt;li&gt;Cannot await for initialization to complete, which may cause some functions to be unavailable when needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2.-using-completabledeferred-to-await&quot; tabindex=&quot;-1&quot;&gt;2. Using &lt;code&gt;CompletableDeferred&lt;/code&gt; to Await&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CompletableDeferred&lt;/code&gt; allows us to suspend a coroutine until a task is completed, making it ideal for scenarios where we need to wait for SDK initialization.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; isSDKInitialized &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; CompletableDeferred&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Unit&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;billingClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BillingClientStateListener &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onBillingSetupFinished&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;billingResult&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BillingResult&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;billingResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responseCode &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; BillingClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;BillingResponseCode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OK&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// Initialization successful&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// Initialization failed&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Mark as complete regardless of success or failure to avoid the coroutine being suspended indefinitely&lt;/span&gt;&lt;br /&gt;        isSDKInitialized&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Unit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onBillingServiceDisconnected&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// You can attempt to reconnect&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;queryMerchandise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Dispatchers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IO&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Wait for initialization to complete&lt;/span&gt;&lt;br /&gt;    isSDKInitialized&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    billingClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryProductDetails&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;pros-and-cons-1&quot; tabindex=&quot;-1&quot;&gt;Pros and Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can await for initialization to complete, making the code more concise.&lt;/li&gt;
&lt;li&gt;Avoids the need for manual state checks, improving code readability.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If initialization never completes, the coroutine will remain suspended, so you need to handle timeouts and other potential issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3.-using-channel-to-handle-producer-consumer&quot; tabindex=&quot;-1&quot;&gt;3. Using &lt;code&gt;Channel&lt;/code&gt; to Handle Producer-Consumer&lt;/h3&gt;
&lt;p&gt;When you need to handle a callback result in a single producer-consumer scenario, &lt;code&gt;Channel&lt;/code&gt; is a good choice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: Loading a rewarded ad from AdMob SDK&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; rewardedAdChannel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Channel&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;RewardedAd&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;RewardedAd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    activity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    adUnitId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    adRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;RewardedAdLoadCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onAdLoaded&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ad&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RewardedAd&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            rewardedAdChannel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trySend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ad&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isSuccess&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onAdFailedToLoad&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;adError&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LoadAdError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            rewardedAdChannel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trySend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isSuccess&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showSingleRewardedAd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;activity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Activity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Wait for the ad to load&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; ad &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rewardedAdChannel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ad &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        ad&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;activity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; rewardItem &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// Handle reward logic&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Handle loading failure&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;pros-and-cons-2&quot; tabindex=&quot;-1&quot;&gt;Pros and Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Suitable for handling one-time or continuous producer-consumer single result, avoiding callback nesting.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Requires manual management of the &lt;code&gt;Channel&lt;/code&gt; lifecycle to prevent memory leaks.&lt;/li&gt;
&lt;li&gt;If there are no consumers, the &lt;code&gt;Channel&lt;/code&gt; may block; you need to set an appropriate capacity or handle timeouts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4.-using-sharedflow-to-handle-recurring-events&quot; tabindex=&quot;-1&quot;&gt;4. Using &lt;code&gt;SharedFlow&lt;/code&gt; to Handle Recurring Events&lt;/h3&gt;
&lt;p&gt;When you need to handle multiple initializations or state updates, &lt;code&gt;SharedFlow&lt;/code&gt; is very suitable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: Suppose we have a network status change that needs to be monitored multiple times&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; networkStatusFlow &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MutableSharedFlow&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;NetworkStatus&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;replay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;startNetworkMonitoring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    networkMonitor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setOnNetworkStatusChangedListener&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;        networkStatusFlow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tryEmit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;observeNetworkStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    lifecycleScope&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        networkStatusFlow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                NetworkStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Available &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Network is available&lt;/span&gt;&lt;br /&gt;                NetworkStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Unavailable &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Network is unavailable&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;pros-and-cons-3&quot; tabindex=&quot;-1&quot;&gt;Pros and Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Suitable for scenarios where events need to be sent and received multiple times.&lt;/li&gt;
&lt;li&gt;Subscribers can share the same data source, avoiding redundant work.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Need to pay attention to backpressure strategies to prevent event backlog.&lt;/li&gt;
&lt;li&gt;Requires manual management of subscriptions and cancellations to avoid memory leaks.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Depending on your specific needs, you can choose different methods to optimize callback handling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single use, single initialization&lt;/strong&gt;: Use &lt;code&gt;AtomicBoolean&lt;/code&gt; for simple initialization state recording.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple uses, single initialization&lt;/strong&gt;: Use &lt;code&gt;CompletableDeferred&lt;/code&gt; when you need to wait for initialization to complete within coroutines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single use, multiple initializations&lt;/strong&gt;: Use &lt;code&gt;Channel&lt;/code&gt; for one-time result callbacks, such as ad loading.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple uses, multiple initializations&lt;/strong&gt;: Use &lt;code&gt;SharedFlow&lt;/code&gt; when you need to monitor multiple state updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope this article provides some insights into handling SDK initialization callbacks. When you encounter similar issues in the future, you can try choosing the most suitable method to improve your code&#39;s readability and maintainability.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Adapting MediaPipe Demos for Kotlin Multiplatform: Object Detection</title>
    <link href="https://2bab.me/en/blog/2024-10-04-on-device-model-integration-kmp-2/"/>
    <updated>2024-10-04T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2024-10-04-on-device-model-integration-kmp-2/</id>
    <content type="html">&lt;p&gt;Following our previous exploration of porting Mediapipe&#39;s LLM Inference, this article delves into migrating the Object Detection Demo. By reading this, you&#39;ll learn:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;How to port Mediapipe&#39;s official Object Detection Android Demo to Kotlin Multiplatform (KMP), enabling it to run on iOS.&lt;/strong&gt; Project link: &lt;a href=&quot;https://github.com/2BAB/MediaPiper/tree/object-detection&quot;&gt;https://github.com/2BAB/MediaPiper/tree/object-detection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrating Compose Multiplatform with iOS native controls (Camera Preview), including handling permission requests.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tips for injecting iOS controls using dependency injection in KMP (based on Koin).&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An introduction to the two Object Detection algorithms used in the Demo.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20241004-object-detection-camera-ppt.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;object-detection-android-sample&quot; tabindex=&quot;-1&quot;&gt;Object Detection Android Sample&lt;/h2&gt;
&lt;p&gt;First, let&#39;s examine the original Object Detection project. You&#39;ll notice that the Android portion includes both an &lt;code&gt;android.view.View&lt;/code&gt; implementation and a Jetpack Compose version. Following our previous approach, we&#39;ll directly port the Jetpack Compose version to KMP.&lt;/p&gt;
&lt;p&gt;Upon closer inspection, you&#39;ll find that this app is more complex. In the LLM Inference example, the SDK only provided text inference interfaces, which we could easily wrap in Kotlin to abstract platform-specific details (though we ultimately used an alternative due to some &lt;code&gt;cinterop&lt;/code&gt; support issues). The UI was entirely reusable. However, Object Detection is based on real-time image processing and includes three modes in the demo: real-time camera detection, local video detection, and local image detection. &lt;strong&gt;Camera preview&lt;/strong&gt; typically &lt;strong&gt;relies heavily on platform-specific implementations&lt;/strong&gt;, and players rarely use custom rendering (i.e., they use native platform solutions).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/Screenshot%202024-10-05%20at%201.25.14%E2%80%AFPM.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In summary, from the outset, we need to &lt;strong&gt;reserve parts that are challenging to implement with Compose Multiplatform (CMP)&lt;/strong&gt; (such as the &lt;code&gt;CameraView&lt;/code&gt; shown above) and &lt;strong&gt;abstract them into separate &lt;code&gt;expect&lt;/code&gt; Composable functions for each platform to implement individually&lt;/strong&gt;. To simplify the learning process and reduce the demo&#39;s scale, we&#39;ll focus solely on implementing the &lt;code&gt;CameraView&lt;/code&gt; component, leaving the Gallery (Video + Image) sections for you to explore. In essence, once you grasp how to embed a Camera Preview, you can implement the other two parts similarly, including Compose and UIKit interactions and iOS permission requests.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20241004-object-detection-options-layers3.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;By cross-referencing the iOS version of the demo, we&#39;ve broken down the UI layers related to &lt;code&gt;CameraView&lt;/code&gt; into four components, as illustrated above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Camera Preview layer must be implemented separately on each platform.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ResultOverlay&lt;/code&gt;, which draws bounding boxes and other results, could be implemented in the common layer. However, &lt;strong&gt;due to complexities like matching the overlay with the Camera Preview (since the preview size can vary depending on the camera&#39;s aspect ratio) and coordinate transformations&lt;/strong&gt;, we&#39;ll delegate this component to platform-specific implementations in this demo.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Scaffold&lt;/code&gt; and &lt;code&gt;Inference Time Label&lt;/code&gt; will be implemented in the common layer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;migration-process&quot; tabindex=&quot;-1&quot;&gt;Migration Process&lt;/h2&gt;
&lt;h3 id=&quot;porting-the-main-ui-and-data-structures&quot; tabindex=&quot;-1&quot;&gt;Porting the Main UI and Data Structures&lt;/h3&gt;
&lt;p&gt;Building on the foundation from the previous section, we&#39;ll add a new folder named &lt;code&gt;objectdetection&lt;/code&gt; to the &lt;code&gt;Mediapiper&lt;/code&gt; project. With our prior experience, we realize that much of the UI content isn&#39;t overly complex—except for the focal point of this section, the camera preview interface. Therefore, we can proceed by migrating all files except for &lt;code&gt;camera&lt;/code&gt; and &lt;code&gt;gallery&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202410121605950.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The necessary modifications can be categorized into two areas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data and Logic:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extract the &lt;code&gt;ObjectDetectionResult&lt;/code&gt; declarations from original SDKs and create a common version as a data class. This allows both SDKs to convert their results into the common version, facilitating the display of inference time, unified logging, and even paving the way to potentially move &lt;code&gt;ResultOverlay&lt;/code&gt; to the common layer in the future.&lt;/li&gt;
&lt;li&gt;Move utility classes and default value enumerations to the common layer with minimal changes—mainly replacing references to the inference result classes with the common version.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;UI Components:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make uniform changes similar to the previous section, such as replacing &lt;code&gt;R&lt;/code&gt; references with &lt;code&gt;Res&lt;/code&gt;, adopting the unified theme, and adjusting some import packages.&lt;/li&gt;
&lt;li&gt;Notably, this demo doesn&#39;t use the CMP version of Navigation, so switching between the Home and Option pages is handled with a simple &lt;code&gt;if...else...&lt;/code&gt; at the top level.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point, we can run an application that doesn&#39;t include camera functionality. The images below demonstrate how these CMP codes run on iOS across two pages.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20241004-object-detection-no-cameras.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;integrating-cameraview-functionality&quot; tabindex=&quot;-1&quot;&gt;Integrating CameraView Functionality&lt;/h3&gt;
&lt;p&gt;As previously analyzed, we need to extract the &lt;code&gt;CameraView&lt;/code&gt; component for native implementation. Therefore, in the common &lt;code&gt;CameraView&lt;/code&gt;, we&#39;ll use two &lt;code&gt;expect&lt;/code&gt; Composable functions: &lt;code&gt;CameraPermissionControl&lt;/code&gt; and &lt;code&gt;CameraPreview&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CameraView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    threshold&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Float&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    maxResults&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    delegate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    mlModel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    setInferenceTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newInferenceTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    CameraPermissionControl &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;CameraPreview&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            threshold&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            maxResults&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            delegate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            mlModel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            setInferenceTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            onDetectionResultUpdate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; detectionResults &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;expect&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CameraPermissionControl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PermissionGrantedContent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@UiComposable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;expect&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CameraPreview&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    threshold&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Float&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    maxResults&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    delegate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    mlModel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    setInferenceTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newInferenceTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    onDetectionResultUpdate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ObjectDetectionResult&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;android-implementation&quot; tabindex=&quot;-1&quot;&gt;Android Implementation&lt;/h4&gt;
&lt;p&gt;The Android side is straightforward—we can directly copy the original Jetpack Compose code:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Android implementation&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@OptIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ExperimentalPermissionsApi&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CameraPermissionControl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    PermissionGrantedContent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@UiComposable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; storagePermissionState&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PermissionState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;rememberPermissionState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Manifest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;permission&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CAMERA&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;LaunchedEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Unit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;storagePermissionState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hasPermission&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            storagePermissionState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launchPermissionRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;storagePermissionState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hasPermission&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;No Storage Permission!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;PermissionGrantedContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CameraPreview&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Define properties&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;DisposableEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Unit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        onDispose &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            active &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;br /&gt;            cameraProviderFuture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unbindAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Describe the UI of this camera view&lt;/span&gt;&lt;br /&gt;    BoxWithConstraints &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; cameraPreviewSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFittedBoxSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            containerSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxWidth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxHeight&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            boxSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; frameWidth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFloat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; frameHeight&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFloat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            Modifier&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cameraPreviewSize&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cameraPreviewSize&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// Use AndroidView to integrate CameraX, as there&#39;s no prebuilt composable in Jetpack Compose&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;AndroidView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                factory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ctx &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; previewView &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PreviewView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; executor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ContextCompat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMainExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                    cameraProviderFuture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; cameraProvider &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cameraProviderFuture&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; preview &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Preview&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;also&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                            it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSurfaceProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;previewView&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;surfaceProvider&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;                        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; cameraSelector &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; CameraSelector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requireLensFacing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CameraSelector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LENS_FACING_BACK&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;                        &lt;span class=&quot;token comment&quot;&gt;// Instantiate an image analyzer for frame transformations before object detection&lt;/span&gt;&lt;br /&gt;                        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; imageAnalyzer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ImageAnalysis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTargetAspectRatio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AspectRatio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RATIO_4_3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setBackpressureStrategy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ImageAnalysis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;STRATEGY_KEEP_ONLY_LATEST&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setOutputImageFormat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ImageAnalysis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OUTPUT_IMAGE_FORMAT_RGBA_8888&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;                        &lt;span class=&quot;token comment&quot;&gt;// Execute object detection in a new thread&lt;/span&gt;&lt;br /&gt;                        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; backgroundExecutor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Executors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newSingleThreadExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;                        backgroundExecutor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                            &lt;span class=&quot;token comment&quot;&gt;// Use ObjectDetectorHelper to abstract Mediapipe specifics&lt;/span&gt;&lt;br /&gt;                            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; objectDetectorHelper &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AndroidObjectDetector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                                context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                                threshold &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; threshold&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                                currentDelegate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; delegate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                                currentModel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mlModel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                                maxResults &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; maxResults&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                                objectDetectorListener &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ObjectDetectorListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                                    onErrorCallback &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _ &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                                    onResultsCallback &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                                        &lt;span class=&quot;token comment&quot;&gt;// Set frame dimensions upon receiving results&lt;/span&gt;&lt;br /&gt;                                        frameHeight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputImageHeight&lt;br /&gt;                                        frameWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputImageWidth&lt;br /&gt;&lt;br /&gt;                                        &lt;span class=&quot;token comment&quot;&gt;// Update results and inference time if the view is active&lt;/span&gt;&lt;br /&gt;                                        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;active&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                                            results &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;results&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                                            &lt;span class=&quot;token function&quot;&gt;setInferenceTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inferenceTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                                        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;                                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;                                &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                                runningMode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; RunningMode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LIVE_STREAM&lt;br /&gt;                            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;                            &lt;span class=&quot;token comment&quot;&gt;// Set the analyzer and start detecting objects from the live stream&lt;/span&gt;&lt;br /&gt;                            imageAnalyzer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAnalyzer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                                backgroundExecutor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                                objectDetectorHelper&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;detectLivestreamFrame&lt;br /&gt;                            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;                        &lt;span class=&quot;token comment&quot;&gt;// Unbind any currently open camera and bind our own&lt;/span&gt;&lt;br /&gt;                        cameraProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unbindAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                        cameraProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindToLifecycle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                            lifecycleOwner&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                            cameraSelector&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                            imageAnalyzer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                            preview&lt;br /&gt;                        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; executor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token comment&quot;&gt;// Return the preview view from the AndroidView factory&lt;/span&gt;&lt;br /&gt;                    previewView&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                modifier &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Modifier&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fillMaxSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// Display the results overlay if there are current results&lt;/span&gt;&lt;br /&gt;            results&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token function&quot;&gt;ResultsOverlay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                    results &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    frameWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; frameWidth&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    frameHeight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; frameHeight&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;ios-implementation&quot; tabindex=&quot;-1&quot;&gt;iOS Implementation&lt;/h4&gt;
&lt;p&gt;The iOS side requires a bit more effort. For camera permission control, we&#39;ll directly call iOS&#39;s &lt;code&gt;platform.AVFoundation&lt;/code&gt; APIs within this Composable function, asynchronously request permissions, and display appropriate messages based on the result—loading, failure, or success, where we show the camera preview. You&#39;ll notice that our iOS implementation is quite comprehensive, covering all three scenarios.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; platform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AVFoundation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; kotlin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coroutines&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;resume&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; kotlin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coroutines&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;suspendCoroutine&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CameraPermissionControl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PermissionGrantedContent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt; &lt;span class=&quot;token annotation builtin&quot;&gt;@UiComposable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; hasCameraPermission &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; remember &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; mutableStateOf&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Boolean&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;LaunchedEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Unit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        hasCameraPermission &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestCameraAccess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hasCameraPermission&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;PermissionGrantedContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Camera permission denied. Please grant access from settings.&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Requesting camera permission...&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestCameraAccess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; suspendCoroutine &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; continuation &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; authorizationStatus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AVCaptureDevice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;authorizationStatusForMediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AVMediaTypeVideo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authorizationStatus&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        AVAuthorizationStatusNotDetermined &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            AVCaptureDevice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestAccessForMediaType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AVMediaTypeVideo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; granted &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;                continuation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;granted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        AVAuthorizationStatusRestricted&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; AVAuthorizationStatusDenied &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            continuation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        AVAuthorizationStatusAuthorized &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            continuation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            continuation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let&#39;s tackle the core camera preview functionality. According to the &lt;a href=&quot;https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-uikit-integration.html&quot;&gt;CMP documentation&lt;/a&gt;, we can embed a UIKit view within a Composable function using &lt;code&gt;UIKitView&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Example 1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;UIKitView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    factory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MKMapView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    modifier &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Modifier&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Example 2&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@OptIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ExperimentalForeignApi&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;UseUITextField&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modifier&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Modifier &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Modifier&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; message &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; remember &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mutableStateOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, World!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;UIKitView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        factory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; textField &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;UITextField&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CGRectMake&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token annotation builtin&quot;&gt;@ObjCAction&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;editingChanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                    message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            textField&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTarget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; textField&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                action &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NSSelectorFromString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;textField&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;editingChanged&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                forControlEvents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; UIControlEventEditingChanged&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            textField&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        modifier &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; modifier&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fillMaxWidth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        update &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; textField &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;            textField&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; message&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These examples applied some default iOS components, not custom ones. The corresponding reference headers are already converted to Kotlin by JetBrains, such as &lt;code&gt;platform.UIKit.UITextField&lt;/code&gt;, which can be directly imported into the KMP project&#39;s iOS target.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202410131137759.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In our case, we want to reuse a custom &lt;code&gt;CameraPreview&lt;/code&gt; view with recognition capabilities. However, the &lt;code&gt;app.framework&lt;/code&gt; produced by KMP is a shared layer upon which the iOS native code depends. &lt;strong&gt;Due to the dependency hierarchy, we can&#39;t directly call &lt;code&gt;CameraPreview&lt;/code&gt; defined in the iOS app&#39;s source code&lt;/strong&gt;. There are generally two solutions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Package the relevant code into a separate module, producing &lt;code&gt;cameraview.framework&lt;/code&gt;, which the KMP&#39;s &lt;code&gt;app&lt;/code&gt; can depend on.&lt;/li&gt;
&lt;li&gt;When initializing &lt;code&gt;app.framework&lt;/code&gt; in the iOS app, pass a lambda that initializes and returns a &lt;code&gt;UIView&lt;/code&gt; to &lt;code&gt;app&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We&#39;ll opt for the second solution, defining &lt;code&gt;IOSCameraPreviewCreator&lt;/code&gt; as the protocol for interaction between the two sides.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Definition&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;typealias&lt;/span&gt; IOSCameraPreviewCreator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    threshold&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Float&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    maxResults&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    delegate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    mlModel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    setInferenceTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newInferenceTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    callback&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IOSCameraPreviewCallback&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; UIView&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;typealias&lt;/span&gt; IOSCameraPreviewCallback &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ObjectDetectionResult&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Inject the implementation from the iOS side during startup and add it to Koin&#39;s definitions&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onStartup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iosCameraPreviewCreator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IOSCameraPreviewCreator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    Startup&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; koinApp &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;        koinApp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;module &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                single &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LLMOperatorFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;                single&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;IOSCameraPreviewCreator&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; iosCameraPreviewCreator &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// In the implementation of CameraPreview, &lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// we inject and invoke this function to obtain a UIView instance&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; androidx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;compose&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ui&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewinterop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UIKitView&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; platform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UIKit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UIView&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CameraPreview&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    threshold&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Float&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    maxResults&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    delegate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    mlModel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    setInferenceTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newInferenceTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    onDetectionResultUpdate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ObjectDetectionResult&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; iOSCameraPreviewCreator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; koinInject&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;IOSCameraPreviewCreator&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Similar to how Android integrates the native Camera View&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;UIKitView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        factory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; iosCameraPreview&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UIView &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;iOSCameraPreviewCreator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                threshold&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                maxResults&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                delegate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                mlModel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                setInferenceTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                onDetectionResultUpdate&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            iosCameraPreview&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        modifier &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Modifier&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fillMaxSize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        update &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _ &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above code leverages Koin for dependency injection, simplifying the interaction process. Now, let&#39;s follow the injection of startup parameters to check the iOS side.&lt;/p&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;MainKt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onStartup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iosCameraPreviewCreator&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; threshold&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maxResults&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delegate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mlModel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onInferenceTimeUpdate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; resultCallback &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOSCameraView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        frame&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CGRectMake&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        modelName&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;truncating&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mlModel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;EfficientDet-Lite0&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;EfficientDet-Lite2&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        maxResults&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;truncating&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; maxResults&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        scoreThreshold&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;truncating&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; threshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        onInferenceTimeUpdate&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; onInferenceTimeUpdate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        resultCallback&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; resultCallback&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;IOSCameraView&lt;/code&gt; here is essentially the &lt;code&gt;CameraViewController&lt;/code&gt; from the original iOS demo. We&#39;ve modified some initialization and lifecycle content and simplified parameter change listeners to highlight the core migration aspects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lifecycle Handling&lt;/strong&gt;: &lt;code&gt;ViewController&lt;/code&gt; uses methods like &lt;code&gt;viewDidLoad&lt;/code&gt;, while &lt;code&gt;UIView&lt;/code&gt; uses &lt;code&gt;didMoveToWindow&lt;/code&gt; to handle logic when the view is added or removed. &lt;code&gt;ViewController&lt;/code&gt; initializes through its lifecycle, whereas &lt;code&gt;UIView&lt;/code&gt; provides custom initializers to pass in models and detection parameters.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Subview Setup&lt;/strong&gt;: &lt;code&gt;ViewController&lt;/code&gt; uses &lt;code&gt;@IBOutlet&lt;/code&gt; and Interface Builder, while &lt;code&gt;UIView&lt;/code&gt; directly creates and adds subviews via a &lt;code&gt;setupView&lt;/code&gt; method, manually setting constraints with AutoLayout and handling tap events.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Callbacks and Delegates&lt;/strong&gt;: &lt;code&gt;ViewController&lt;/code&gt; uses delegates, while &lt;code&gt;UIView&lt;/code&gt; adds closure callbacks like &lt;code&gt;onInferenceTimeUpdate&lt;/code&gt; and &lt;code&gt;resultCallback&lt;/code&gt;. These are passed during initialization and set up for type conversion, facilitating callbacks to the KMP layer.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20241004-object-detection-controller-to-view.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We also retain &lt;code&gt;OverlayView&lt;/code&gt;, &lt;code&gt;CameraFeedService&lt;/code&gt;, &lt;code&gt;ObjectDetectorService&lt;/code&gt;, and parts of &lt;code&gt;DefaultConstants&lt;/code&gt;, without modifying their code. Notably, &lt;code&gt;ObjectDetectorService&lt;/code&gt; encapsulates the Object Detection SDK. If you examine its API calls, you&#39;ll find it&#39;s closely coupled with iOS&#39;s Camera APIs (like &lt;code&gt;CMSampleBuffer&lt;/code&gt;), indicating the difficulty of abstracting it into the common layer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202410131208100.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With this, we can run the camera preview with Object Detection on iOS.&lt;/p&gt;
&lt;h2 id=&quot;simple-testing&quot; tabindex=&quot;-1&quot;&gt;Simple Testing&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20241004-object-detection-overview-gif.gif?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The above GIF showcases the performance of EfficientDet-Lite0 running in CPU mode on an iPhone 13 mini. In official tests using the Pixel 6&#39;s CPU/GPU, switching to GPU execution can slightly improve performance. It&#39;s evident that the real-time performance is sufficient for production environments, and the accuracy is acceptable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202410131403617.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The demo comes with two optional models:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EfficientDet-Lite0&lt;/strong&gt;: Uses 320x320 input, balancing latency and accuracy, suitable for lightweight applications. The demo includes its float32 version by default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EfficientDet-Lite2&lt;/strong&gt;: Uses 448x448 input, offering higher accuracy at the cost of speed, suitable for scenarios demanding greater precision. The demo includes its float32 version by default.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both models are trained on a dataset containing 1.5 million instances and 80 object labels.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Traditional ML models on mobile devices are relatively mature and can handle many specific scenarios. The two models discussed are only 13–25 MB in size. Compared to LLM models that are often over 1 GB, these models are much more practical for deployment.&lt;/li&gt;
&lt;li&gt;Embedding UIKit views within Compose Multiplatform allows us to address high-performance needs that require native APIs and hardware.&lt;/li&gt;
&lt;li&gt;We kept &lt;code&gt;ResultOverlay&lt;/code&gt; in the common layer, and the iOS side set up result callbacks to the KMP layer. However, on iOS, we still used native views for implementation due to the cost of migration. In real-world scenarios, we can further consider:
&lt;ul&gt;
&lt;li&gt;If the business scenario is simple, such as rectangle recognition with a full-screen camera preview, we can easily reuse &lt;code&gt;ResultOverlay&lt;/code&gt; at the Compose layer.&lt;/li&gt;
&lt;li&gt;For complex business scenarios, like facial recognition with sticker selection and rendering during video chats, the high complexity of the business logic makes reusing the same &lt;code&gt;StickerOverlay&lt;/code&gt; highly valuable. In this case, regardless of the camera preview size, the cost of adaptation becomes acceptable. Moreover, there&#39;s potential for optimization in calculations within &lt;code&gt;StickerOverlay&lt;/code&gt;, such as using sampled calculations and interpolated animations.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Complex dependency management scenarios, including UI view injection, can be  simplified using dependency injection frameworks like Koin.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Adapting MediaPipe Demos for Kotlin Multiplatform: LLM Inference</title>
    <link href="https://2bab.me/en/blog/2024-09-01-on-device-model-integration-kmp/"/>
    <updated>2024-09-01T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2024-09-01-on-device-model-integration-kmp/</id>
    <content type="html">&lt;p&gt;By reading this post, you&#39;ll learn about:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Porting the MediaPipe &amp;quot;LLM Inference&amp;quot; Android demo to Kotlin Multiplatform (KMP) to support iOS.&lt;/strong&gt; Project repository: &lt;a href=&quot;https://github.com/2BAB/mediapiper&quot;&gt;https://github.com/2BAB/mediapiper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two common ways of calling iOS SDKs in KMP:&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Directly invoking third-party libraries added via Cocoapods in Kotlin.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Calling third-party libraries from the iOS project in Kotlin.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tips for dependency injection in KMP across platforms (based on Koin).&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A brief background on On-Device Models and the Gemma 1&#39;s 2B LLM model.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-model-screenshot.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;on-device-model&quot; tabindex=&quot;-1&quot;&gt;On-Device Model&lt;/h2&gt;
&lt;p&gt;Large Language Models (LLMs) have been a hot topic for quite some time, and this year, the trend has reached mobile devices. Companies like Google have deeply integrated on-device model functionalities into their latest smartphones and operating systems. Google&#39;s current public strategy regarding On-Device Models involves two main types of LLMs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Gemini Nano:&lt;/strong&gt; Not open source, limited support for specific devices (some are accelerated by specific chips like Tensor G4), and demonstrates extremely strong performance. It&#39;s currently available on desktop platforms (Chrome) and some Android phones (Pixel 8/9 and Samsung S23/24, etc.). It will be available to more developers later  for use and testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gemma:&lt;/strong&gt; Open source and supports all devices that meet the minimum requirements, with impressive performance as well. It uses the similar technology as Nano for training with primarily-English data from web documents, mathematics, and code. It can be experienced on multiple platforms (Android/iOS/Desktop). The MediaPipe adaptation for Gemma2 will also be available soon according to the announcement.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since most developers do not yet have access to Gemini Nano, our focus today is on the 2B version of Gemma 1. To use Gemma directly on mobile platforms, Google has provided an out-of-the-box tool: MediaPipe. MediaPipe is a cross-platform framework that packages a series of pre-built on-device machine learning models and tools, supporting tasks like real-time gesture recognition, face detection, and more. It can also be used in applications like image generation and chatbots. For those interested, you can try the web version &lt;a href=&quot;https://mediapipe-studio.webapps.google.com/&quot;&gt;demo&lt;/a&gt; and explore the relevant &lt;a href=&quot;https://ai.google.dev/edge/mediapipe/solutions/guide&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/on-device-model-mediapipe-intro.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Among its features, the LLM Inference API (first row in the table above) is a component for running large language model inferences, supporting models like Gemma 2B/7B, Phi-2, Falcon-RW-1B, StableLM-3B, and more. Pre-converted models for Gemma (based on TensorFlow Lite) can be downloaded from Kaggle &lt;a href=&quot;https://www.kaggle.com/models/google/gemma/tfLite/gemma-1.1-2b-it-gpu-int4&quot;&gt;here&lt;/a&gt; and loaded into MediaPipe later.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-model-gemma-download.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;llm-inference-android-sample&quot; tabindex=&quot;-1&quot;&gt;LLM Inference Android Sample&lt;/h2&gt;
&lt;p&gt;The official &lt;a href=&quot;https://github.com/google-ai-edge/mediapipe-samples/tree/main/examples/llm_inference/android&quot;&gt;LLM Inference Demo&lt;/a&gt; from MediaPipe includes support for Android, iOS, and Web platforms.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-demo-android-sample.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Opening the Android repository reveals several characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pure Kotlin&lt;/strong&gt; implementation.&lt;/li&gt;
&lt;li&gt;The UI is implemented entirely with &lt;strong&gt;Jetpack Compose&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The LLM Task SDK it depends on is highly encapsulated, exposing only three methods.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, let&#39;s check out the iOS version:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The UI is implemented with SwiftUI, performing the same tasks as Compose but with some simplified elements (e.g., no top bar or send button).&lt;/li&gt;
&lt;li&gt;The LLM Task SDK it relies on is also highly encapsulated, exposing the same three methods.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This led to an interesting idea: &lt;strong&gt;The Android version has a foundation that allows it to be ported to iOS. Porting would make the code on both platforms highly consistent,  reducing maintenance costs, with the core implementation only requiring a bridge to the LLM Inference SDK on iOS.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;kotlin-multiplatform&quot; tabindex=&quot;-1&quot;&gt;Kotlin Multiplatform&lt;/h2&gt;
&lt;p&gt;The technology used for the porting project is called Kotlin Multiplatform (KMP), developed by the Kotlin team to support cross-platform development. KMP allows developers to use the same codebase to build applications for Android, iOS, Web, and other platforms. By sharing business logic code, KMP can significantly reduce development time and maintenance costs while preserving native performance and experience for each platform. At this year’s I/O conference, Google also announced first-class support for KMP, migrating some Android libraries and tools to the multiplatform, enabling KMP developers to use it conveniently on iOS and other platforms.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-model-kmp-1.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-model-kmp-2.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Although MediaPipe supports multiple platforms, this time we mainly focus on Android and iOS.&lt;/p&gt;
&lt;h2 id=&quot;porting-process&quot; tabindex=&quot;-1&quot;&gt;Porting Process&lt;/h2&gt;
&lt;h3 id=&quot;initialization&quot; tabindex=&quot;-1&quot;&gt;Initialization&lt;/h3&gt;
&lt;p&gt;Start by creating a basic KMP project using IntelliJ IDEA or Android Studio. You can use the KMP Wizard or templates from third-party KMP apps. If you&#39;re unfamiliar with KMP, you&#39;ll find its structure is quite similar to an Android project, except this time, we place the iOS container project in the root directory and configure iOS dependencies in the app module&#39;s &lt;code&gt;build.gradle.kts&lt;/code&gt; with the KMP Gradle Plugin.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-model-proj-3.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;wrapping-and-calling-llm-inference&quot; tabindex=&quot;-1&quot;&gt;Wrapping and Calling LLM Inference&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;commonMain&lt;/code&gt;, we abstract a simple interface based on the characteristics of the MediaPipe LLM Task SDK, written in Kotlin to cater to both Android and iOS. This interface replaces the &lt;code&gt;InferenceModel.kt&lt;/code&gt; class in the original repository.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// app/src/commonMain/.../llm/LLMOperator&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; LLMOperator &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt;     * To load the model into the current context.&lt;br /&gt;     * @return 1. null if successful, 2. an error message if failed.&lt;br /&gt;     */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt;     * To calculate the token size of a string.&lt;br /&gt;     */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sizeInTokens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt;     * To generate response for a given inputText in a synchronous way.&lt;br /&gt;     */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt;     * To generate response for a given inputText in an asynchronous way.&lt;br /&gt;     * @return A flow with partial response in String and completion flag in Boolean.&lt;br /&gt;     */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateResponseAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Flow&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Pair&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Boolean&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On Android, since the LLM Task SDK was originally implemented in Kotlin, aside from initializing the model file, most of the functionality is essentially a proxy for the original SDK.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LLMInferenceAndroidImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; ctx&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LLMOperator &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;lateinit&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; llmInference&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LlmInference&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; initialized &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AtomicBoolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; partialResultsFlow &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MutableSharedFlow&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Pair&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Boolean&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initialized&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; modelPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modelPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Model not found at path: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;modelPath&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;loadModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modelPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            initialized&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Exception&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modelPath&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; LlmInference&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LlmInferenceOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setModelPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modelPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxTokens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setResultListener&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; partialResult&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; done &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token comment&quot;&gt;// Transforming the listener to flow,&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token comment&quot;&gt;// making it easy on UI integration.&lt;/span&gt;&lt;br /&gt;                partialResultsFlow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tryEmit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partialResult &lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt; done&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        llmInference &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; LlmInference&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFromOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sizeInTokens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; llmInference&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sizeInTokens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; llmInference&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateResponseAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Flow&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Pair&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Boolean&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;        llmInference&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateResponseAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; partialResultsFlow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asSharedFlow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For iOS, we first attempt the direct invocation of libraries added via Cocoapods. In the app module, include the Cocoapods plugin and add the MediaPipe LLM Task library:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// app/build.gradle.kts&lt;/span&gt;&lt;br /&gt;plugins &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;libs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plugins&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cocoapods&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;cocoapods &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    ios&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deploymentTarget &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;15&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;pod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;MediaPipeTasksGenAIC&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0.10.14&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        extraOpts &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-compiler-option&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-fmodules&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;pod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;MediaPipeTasksGenAI&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0.10.14&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        extraOpts &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-compiler-option&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-fmodules&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the addition of the &lt;code&gt;-fmodules&lt;/code&gt; compiler option in the above configuration to generate Kotlin references correctly (&lt;a href=&quot;https://kotlinlang.org/docs/native-cocoapods-libraries.html#support-for-objective-c-headers-with-import-directives&quot;&gt;reference link&lt;/a&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some Objective-C libraries, specifically those that serve as wrappers for Swift libraries, have @import directives in their headers. By default, cinterop doesn&#39;t provide support for these directives. To enable support for @import directives, specify the -fmodules option in the configuration block of the pod() function.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Afterward, in &lt;code&gt;iosMain&lt;/code&gt;, you can directly import the relevant library code and replicate the Android proxy approach:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Note these imports start with cocoapods&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; cocoapods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MediaPipeTasksGenAI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MPPLLMInference&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; cocoapods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MediaPipeTasksGenAI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MPPLLMInferenceOptions&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; platform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Foundation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NSBundle&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; LLMOperatorIOSImpl&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LLMOperator &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; inference&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MPPLLMInference&lt;br /&gt;    &lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; modelPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; NSBundle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mainBundle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pathForResource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bin&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MPPLLMInferenceOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modelPath&lt;span class=&quot;token operator&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setModelPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modelPath&lt;span class=&quot;token operator&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxTokens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2048&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTopk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setTemperature&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.8f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setRandomSeed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;102&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// NPE was thrown here right after it printed the success initialization message internally.&lt;/span&gt;&lt;br /&gt;        inference &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MPPLLMInference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateResponseAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, we weren&#39;t as lucky this time. An NPE was thrown immediately after &lt;code&gt;MPPLLMInference&lt;/code&gt; finished initializing. The likely issue is that since Kotlin&#39;s current interop target is Objective-C, the &lt;code&gt;MPPLLMInference&lt;/code&gt; constructor has an extra error parameter compared to the Swift version, to which we passed &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; cocoapods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MediaPipeTasksGenAI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MPPLLMInferenceOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;br /&gt;  error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CPointer&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ObjCObjectVar&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;platform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Foundation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NSError&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Various attempts with different pointer inputs did not solve the problem:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// One of the attempts&lt;/span&gt;&lt;br /&gt;memScoped &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; pp&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CPointerVar&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ObjCObjectVar&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;NSError&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;allocPointerTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; inference &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MPPLLMInference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    Napier&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thus, we had to adopt a different approach: calling the third-party library from the iOS project.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 1. Declare an interface similar to LLMOperator for easier iOS SDK adaptation.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// app/src/iosMain/.../llm/LLMOperator.kt&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; LLMOperatorSwift &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modelName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sizeInTokens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateResponseAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partialResponse&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        completion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;completeResponse&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// 2. Implement this interface in the iOS project&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// iosApp/iosApp/LLMInferenceDelegate.swift&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; LLMOperatorSwiftImpl&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LLMOperatorSwift &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; llmInference&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LlmInference&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;    func &lt;span class=&quot;token function&quot;&gt;loadModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modelName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; async throws &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        let path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Bundle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;forResource&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; modelName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ofType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bin&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;br /&gt;        let llmOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;  LlmInference&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Options&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modelPath&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        llmOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxTokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4096&lt;/span&gt;&lt;br /&gt;        llmOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;temperature &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.9&lt;/span&gt;&lt;br /&gt;        &lt;br /&gt;        llmInference &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LlmInference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; llmOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;    func &lt;span class=&quot;token function&quot;&gt;generateResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; async throws &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; String &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; llmInference&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; inputText&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;    func &lt;span class=&quot;token function&quot;&gt;generateResponseAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token label symbol&quot;&gt;@escaping&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Void&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; completion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token label symbol&quot;&gt;@escaping&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Void&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; async throws &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; llmInference&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateResponseAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; inputText&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; partialResponse&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// progress&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; let e &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#92;(self.errorTag) &#92;(e)&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token function&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;localizedDescription&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; let partial &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; partialResponse &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token function&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partial&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; completion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;completion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;    &lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// 3. iOS then passes back the delegated (initialization-focused) object to Kotlin&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// iosApp/iosApp/iosApp.swift&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; AppDelegate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UIResponder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; UIApplicationDelegate &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    func &lt;span class=&quot;token function&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;）&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;        let delegate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LLMOperatorSwiftImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        MainKt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onStartup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;llmInferenceDelegate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; delegate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;        &lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// 4. The initial implementation object for iOS in KMP &lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;//  are directly delegated to it (injected via constructor)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LLMOperatorIOSImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; delegate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LLMOperatorSwift&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LLMOperator &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;   &lt;br /&gt;   &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might notice that the Impl instances on both platforms require different constructor parameters. This issue is generally resolved using KMP&#39;s &lt;code&gt;expect&lt;/code&gt; and &lt;code&gt;actual&lt;/code&gt; keywords. In the following code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We take advantage of the fact that the &lt;code&gt;expect&lt;/code&gt; class does not require constructor parameters, adding a layer of encapsulation (similar to an interface).&lt;/li&gt;
&lt;li&gt;We use &lt;a href=&quot;https://insert-koin.io/&quot;&gt;Koin&lt;/a&gt; to inject the necessary parameters for each platform, then uniformly inject the created interface instance into the Common layer as needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Common&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;expect&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; LLMOperatorFactory &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LLMOperator&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; sharedModule &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; module &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;token comment&quot;&gt;// Create the LLMOperator required by the Common layer from different LLMOperatorFactory implementations&lt;/span&gt;&lt;br /&gt;	single&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;LLMOperator&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;LLMOperatorFactory&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Android&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LLMOperatorFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LLMOperator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LLMInferenceAndroidImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; androidModule &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; module &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Android injects the App&#39;s Context&lt;/span&gt;&lt;br /&gt;    single &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LLMOperatorFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;androidContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// iOS&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LLMOperatorFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; llmInferenceDelegate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LLMOperatorSwift&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LLMOperator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LLMOperatorIOSImpl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;llmInferenceDelegate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;module &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// iOS injects the delegate passed in the onStartup function&lt;/span&gt;&lt;br /&gt;    single &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LLMOperatorFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;llmInferenceDelegate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In summary, this case study gave us a taste of &lt;strong&gt;deep interaction between Kotlin and Swift&lt;/strong&gt;. By leveraging the &lt;code&gt;expect&lt;/code&gt; and &lt;code&gt;actual&lt;/code&gt; keywords along with Koin&#39;s dependency injection, we made the overall solution &lt;strong&gt;smoother and more automated&lt;/strong&gt;, achieving the goal of calling Android and iOS native SDKs from the Common module in KMP.&lt;/p&gt;
&lt;h3 id=&quot;porting-ui-and-viewmodel&quot; tabindex=&quot;-1&quot;&gt;Porting UI and ViewModel&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;InferenceMode&lt;/code&gt; in the original project has been replaced by the &lt;code&gt;LLMOperator&lt;/code&gt; from the previous section, so we copy the remaining five classes excluding Activity:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-model-copy-structure.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, we make a few modifications to allow Jetpack Compose code to migrate easily to Compose Multiplatform.&lt;/p&gt;
&lt;p&gt;First, the &lt;code&gt;ViewModel&lt;/code&gt;. In the KMP version, I used &lt;a href=&quot;https://github.com/adrielcafe/voyager&quot;&gt;Voyage&lt;/a&gt;, replacing it with &lt;code&gt;ScreenModel&lt;/code&gt;. While an official ViewModel solution is also in the works, which you can refer to in this &lt;a href=&quot;https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-viewmodel.html&quot;&gt;document&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Android version&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ChatViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; inferenceModel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; InferenceModel&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// KMP version, converts ViewModel to ScreenModel and modifies the input object&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ChatViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; llmOperator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LLMOperator&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ScreenModel &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Correspondingly, the ViewModel initialization method is also changed to the ScreenModel method:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Android version&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;internal&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ChatRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    chatViewModel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ChatViewModel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;viewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        factory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ChatViewModel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;LocalContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;applicationContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;ChatScreen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// KMP version, initialized externally and passed in&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;internal&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ChatRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    chatViewModel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ChatViewModel&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Here we use the default parameter injection solution for decoupling.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// koinInject() is a method provided by Koin for @Composable function injection in Compose.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Composable&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AiScreen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;llmOperator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;LLMOperator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;koinInject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Use the remember method from ScreenModel&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt;&lt;br /&gt;&lt;br /&gt; chatViewModel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rememberScreenModel &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ChatViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;llmOperator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    Column &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;showLoading&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token function&quot;&gt;ChatRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;chatViewModel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The corresponding LLM functionality calls within the ViewModel also need to be replaced:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Android version&lt;/span&gt;&lt;br /&gt;inferenceModel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateResponseAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fullPrompt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;inferenceModel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;partialResults&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collectIndexed&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partialResult&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; done&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// KMP version, moves Flow&#39;s return to the front, compatible with SDK design on both platforms&lt;/span&gt;&lt;br /&gt;llmOperator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateResponseAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fullPrompt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collectIndexed&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partialResult&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; done&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, adapt resource loading methods specific to Compose Multiplatform, replacing &lt;code&gt;R&lt;/code&gt; with &lt;code&gt;Res&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Android version&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringResource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;R&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chat_label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// KMP version, this reference is mapped from xml by the plugin&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// (commonMain/composeResources/values/strings.xml)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; mediapiper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generated&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;resources&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chat_label&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringResource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chat_label&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, we have completed the main UI and functionality migration of &lt;code&gt;ChatScreen&lt;/code&gt; and &lt;code&gt;ChatViewModel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, there are a few minor modifications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For &lt;code&gt;LoadingScreen&lt;/code&gt;, we replicate the approach of passing in &lt;code&gt;LLMOperator&lt;/code&gt; for initialization (replacing the original &lt;code&gt;InferenceModel&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ChatMessage&lt;/code&gt; requires only a single line API change for UUID (which won&#39;t be needed after Kotlin 2.0.20).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ChatUiState&lt;/code&gt; does not require any changes.&lt;/li&gt;
&lt;li&gt;The remaining minor tweaks include changing log library and other small details.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, setting aside log and R file replacing, &lt;strong&gt;the core changes less than 20 lines&lt;/strong&gt;, allowing the &lt;strong&gt;entire UI to function as expected&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;simple-testing&quot; tabindex=&quot;-1&quot;&gt;Simple Testing&lt;/h2&gt;
&lt;p&gt;So, how does the performance of Gemma 2B measure up? Let&#39;s look at some simple examples with Pixel 4a and iOS emulators. Here, we primarily test three versions of the model, defined in &lt;code&gt;me.xx2bab.mediapiper.llm.LLMOperator&lt;/code&gt; (refer to the project README for deploying models on both platforms):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gemma-2b-it-gpu-int4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gemma-2b-it-cpu-int4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gemma-2b-it-cpu-int8&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Key points to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;it&lt;/strong&gt; indicates an Instruction Tuned variant, better suited for conversational use because they are fine-tuned to better understand instructions and generate more relevant responses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;int4/8&lt;/strong&gt; refers to model quantization, which converts floating-point numbers in the model to lower-precision integers, thereby reducing the model&#39;s size and computation, making it suitable for small local devices like phones. However, the model&#39;s precision and response accuracy might decrease.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cpu&lt;/strong&gt; and &lt;strong&gt;gpu&lt;/strong&gt; refer to the target hardware platforms, allowing devices with weak or no GPUs to choose CPU execution. From the test results below, you&#39;ll find that CPU versions often outperform on current mobile devices, due to the small model size, simple computation (dialogue), and integer quantization favor CPU instruction execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, we test a simple logic: &amp;quot;Is asparagus an animal?&amp;quot; As shown in the image below, the CPU version provides a more reasonable answer than both GPU versions (iOS and Android). The next test is translating the answer into Chinese, which doesn&#39;t perform well across all three attempts, but this is expected.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-model-test-1.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, we elevate the complexity of the question to word classification between animals and plants: both GPU and CPU versions perform well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-model-test-2.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Raising the complexity further, asking it to output the answer in JSON format, leads to apparent issues:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The first image does not output a complete snippet, missing the closing three dots ```.&lt;/li&gt;
&lt;li&gt;The second image shows a classification error, placing mangosteen under animals, and sunflowers appear twice under plants.&lt;/li&gt;
&lt;li&gt;The third image mirrors the second error, and none of the three instances strictly outputs a JSON, failing to rigorously follow the role of a JSON Responder.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-model-test-3.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Lastly, this isn&#39;t the limit. Using the cpu-int8 version can answer the above questions with higher accuracy. Moreover, if you send the entry code for this demo&#39;s iOS version for analysis, it performs quite well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202408-on-device-model-test-4.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Testing the Gemma 1&#39;s 2B version reveals that its inference capabilities still have room for improvement, but it excels in response speed. In fact, the 2B version of Gemma 2 was released recently, and according to official tests, its overall performance has surpassed GPT 3.5. This means that on a small mobile phone, local inference can now achieve the results of mainstream models from a year and a half ago. However, it has yet to be adapted to TFLite (on which MediaPipe is based).It&#39;s on the roadmap but without a specific date, you can track the following issues for the latest updates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google-ai-edge/mediapipe/issues/5570&quot;&gt;https://github.com/google-ai-edge/mediapipe/issues/5570&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google-ai-edge/mediapipe/issues/5594&quot;&gt;https://github.com/google-ai-edge/mediapipe/issues/5594&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Migrating this local chat demo and conducting tests provided us with some firsthand experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The development of On-Device Models for LLMs is progressing rapidly. With the help of Google&#39;s infrastructure, third-party mobile app developers can quickly integrate related features across Android and iOS platforms.&lt;/li&gt;
&lt;li&gt;Considering the current situation, On-Device Models for LLMs are likely to reach a preliminary usable state this year. Inference speed is already good, but accuracy still requires further testing (e.g., Gemma 2&#39;s 2B version + MediaPipe).&lt;/li&gt;
&lt;li&gt;Adopting the &amp;quot;Kotlin First&amp;quot; strategy and boldly using Compose shows great promise—under a well-developed infrastructure, a small chat module of Android can be ported to iOS with just a handful of changes.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>KotlinConf 2024 Experience</title>
    <link href="https://2bab.me/en/blog/2024-05-31-kotlin-conf-24-exp/"/>
    <updated>2024-05-31T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2024-05-31-kotlin-conf-24-exp/</id>
    <content type="html">&lt;p&gt;The annual Kotlin festival has arrived again. I mentioned &amp;quot;Next year, see you again&amp;quot; in last year&#39;s &lt;a href=&quot;https://2bab.me/en/blog/2023-04-15-kotlin-conf-23-exp/&quot;&gt;KotlinConf 2023 Experience&lt;/a&gt; and delivered on that promise. This time I posted the ticket release information in some Android groups in mainland China at the first opportunity and naturally got myself a super early bird ticket. This article is also a quick note on my phone, so please forgive simplification or omission of some technical term explanations and formatting issues.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-1.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This year&#39;s venue is at Bella Center in Copenhagen, Denmark, with a much larger overall scale than last year (2000 people vs 1000 people). Although last year&#39;s dining menu was fancier and the venue building had a particularly historic feel, making this year seem slightly inferior, the event management level is still very high.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-2.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-3.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-4.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-5.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-6.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I suggest everyone follow the on-site quick tour video recorded together with Kotlin official evangelist Shengyou and several friends (expected to be released in two or three days), as well as the next episode&#39;s on-site special of &amp;quot;Kotlin Fireside Chat.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-7.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;(Image shows last year&#39;s on-site recording published on JetBrains China&#39;s Bilibili account)&lt;/p&gt;
&lt;p&gt;Due to limited space, this article&#39;s content will focus on my personal perspective.&lt;/p&gt;
&lt;h2 id=&quot;core-agenda&quot; tabindex=&quot;-1&quot;&gt;Core Agenda&lt;/h2&gt;
&lt;p&gt;This year&#39;s core theme is ostensibly Kotlin 2.0 and K2, but actually leans more toward KMP. After KT2 was announced last year, the official has been publicly sharing various progress. So the features were actually all released before this conference, including the binary packages. The more important part of the conference is announcing the arrival of KT2&#39;s official version, marking that Kotlin has truly matured (according to former Kotlin language designer Roman), and the beginning of more teams and projects adapting. On the language itself, they&#39;re already talking about what will be beta in 2.1 and 2.2, looking ahead to the future.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-8.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-9.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;But I must say, everyone has been waiting for KT2 for a long time. Whether it&#39;s the significant performance improvement of the Compiler, or some syntactic sugar that should have been there long ago, all can affect the actual development experience. The design changes for KCP, KSP, etc., are also more convenient for us plugin developers.&lt;/p&gt;
&lt;p&gt;On the KMP side, it directly took over 20 sessions of related topic sharing, including some official sessions from JetBrains and Google, as well as best practices from the community. Google mentioned WorkSpace suite migration to KMP last year, and this year brought it out as a key case study, corresponding to the major KMP support announced at this year&#39;s I/O. In addition, other tools in Jetpack, especially Room, also have detailed migration sharing this year. Whether or not you&#39;re a Room user, you can find some useful information from their library migration experience, including how to simultaneously support KMP and the Java ecosystem on Android, how to update and solve testing issues, how to bridge underlying native libraries (sqlite) on the iOS platform, etc. KMP&#39;s open-source libraries have reached several thousand (for specifics, check John O&#39;Reilly&#39;s KMP Libraries sharing replay), with growth rate no less than other cross-platform tools in their early stages.&lt;/p&gt;
&lt;p&gt;On the Compose side, besides some regular performance optimization and common multi-platform practice sharing, the most interesting might be Jake&#39;s sharing on using Compose for embedded system UI (I haven&#39;t watched it yet, several friends highly recommend it).&lt;/p&gt;
&lt;p&gt;Of course, I definitely went to listen to the Gradle content. This year&#39;s core marketing point is Gradle Declarative DSL, whose approach is to start from the needs of most developers and separate the build configuration part (only supports configuring some basic data types) from the logic part (complex build customization, with plugins as carriers and tasks as units). Currently only EAP is released, requiring a bunch of nightly versions to use together, and will continuously improve related functions and tools to enter beta by the end of this year.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-10.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Just looking at the above two sentences of explanation is still a bit abstract. Actually, there&#39;s quite complex work behind this. Based on Kotlin&#39;s syntax, they extracted a subset of KTS, which can basically be understood as only supporting current Gradle Extension configuration. The new configuration filename is &amp;quot;build.gradle.dcl&amp;quot;, dcl being short for declarative. The benefit is being able to customize a parser to parse and handle this file instead of the standard complex kotlin compiler, achieving huge performance improvements in the Configuration phase (about 1/6 the time of KTS). When chatting with Slack people on-site, I mentioned that Configuration Cache is not fast enough for large projects — even if one module takes 0.3s, the time for a thousand modules (300s) is a bit too long. I believe after this new configuration file is supported, there will also be obvious improvements.&lt;/p&gt;
&lt;p&gt;Additionally, this year I also paid attention to some more soft-skill oriented Talks, including &amp;quot;Tools &amp;amp; Techniques for Java to Kotlin Migration&amp;quot; on the first day afternoon, and &amp;quot;The best programmer I know&amp;quot; on the second morning. The first one would be very instructive for many friends wanting to join big companies — you can hear how various projects with millions of lines of code do this kind of technical migration step by step. How to fight for the internal funding and resource support needed for this, how to communicate with people and groups who resist, why to regularly collect feedback from internal developers, how to find the value of new technology, etc. The second one I personally think is more like Golden Rules for indie developers — if you haven&#39;t read this type of blog or production mode introduction, it would be quite instructive.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-11.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;All edited videos will be released in a week or two. Currently you can watch previous live replays through this replay list: &lt;a href=&quot;https://www.reddit.com/r/Kotlin/comments/1d08rv8/is_it_possible_to_watch_the_recordings_of_the/&quot;&gt;https://www.reddit.com/r/Kotlin/comments/1d08rv8/is_it_possible_to_watch_the_recordings_of_the/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;deep-conversations&quot; tabindex=&quot;-1&quot;&gt;Deep Conversations&lt;/h2&gt;
&lt;p&gt;I spent more time on deep conversations this year, with fewer shallow introductions to new friends. On one hand, I added many people last year, and when we meet again there&#39;s always more content to chat about. On the other hand, my throat was inflamed those two days (sad), so I saved my limited voice for some in-depth topics. Below, I&#39;ll excerpt a few interesting pieces of content.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-12.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The first piece is a conversation with Jeffrey, one of Google&#39;s language and developer tools leaders. I mainly asked why Google didn&#39;t mention Compose Multiplatform in this I/O blog post, instead summarizing Google&#39;s current cross-platform support on the client side as &amp;quot;use KMP for shared logic, use Flutter for both shared UI and shared logic.&amp;quot; His answer was very interesting, summarized in mainly two points: 1. Compose still has a lot of development space. At this stage, we&#39;re still working hard to provide better support for Android and various performance improvements. Not mentioning it now doesn&#39;t mean never — we&#39;ll see when it&#39;s more mature. 2. Flutter is very convenient when doing unified projects, while KMP has its own characteristics, including more seamless interop with Native (like uikit bindings). CMP can actually achieve a lot on this basis.&lt;/p&gt;
&lt;p&gt;I think what he said is very reasonable, including the imagination for several integration modes of kmp and cmp now. For example, you can integrate only the logic part like early KMP, used to develop data repository and infra, including some SDKs. Another example is cashapp&#39;s redwood which doesn&#39;t use cmp&#39;s UI components, only uses the underlying composition part to build virtual structures, with actual rendering mapped to Native, even supporting hot deployment for kt/js. The migration appeal of KMP is actually very obvious: native performance, progressive integration. Going forward you can integrate CMP (partial functions or pages, partial package levels, of course all can go on too), and when stepping back you can keep using it as a native SDK while other parts use some new technology in the future. It&#39;s hard for situations like a startup company going all-in on Flutter, then deciding to rewrite most features or even the entire app when wanting to support better performance and advanced requirements.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-13.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The second piece is a conversation with Gradle&#39;s Principal Engineer, Paul. We discussed the community&#39;s general view that Gradle is &amp;quot;very hard to learn.&amp;quot; Actually, on the Android side, the problem isn&#39;t just Gradle — mainly AGP and a bunch of packaging and compilation tools including aapt2, r8, ksp, etc., which also don&#39;t have much public documentation and detailed user guides. For Gradle itself, I wrote last year that documentation needs improvement. They are actually working on this, including new organizational forms, more course sharing, and working with Android to promote recipes and cross-linking references. Gradle&#39;s open-source build tool side has only 18 core team members. From public channels we can see that new employees are mostly invested in enterprise projects (develocity), so there&#39;s quite a bit of pressure for supporting new requirements. Bragging a bit: he proactively said he&#39;s too busy working at the booth these two days, and will read my book in a while and give me some feedback.&lt;/p&gt;
&lt;p&gt;Additionally, I also chatted with him and Sterling about Declarative DSL&#39;s tooling support. The Compose Desktop tool they demonstrated on-site was amazing, able to view all configurations of related Software Types under a dcl. And this tool is just a demo. I suggested they could merge it into the IDE plugin (after all, compose is also a technology supported by idea plugins). This tool will also be integrated with more other source code navigation support in the future — you can click to see documentation and declarations, and also link to the plugin source code behind.&lt;/p&gt;
&lt;p&gt;The third piece is chatting with Slack&#39;s Boon for over an hour across multiple times. Boon is from Singapore and currently works on some Android business architecture at Slack in Seattle. Slack Android has over a thousand modules and has been using Kotlin and Compose for many years. Interestingly, their infrastructure and developer experience team (which feels more tool-oriented) are separate. After the company was acquired by SF, there&#39;s still support for many open-source projects. And recent pain points include Compose performance issues and compilation build execution time. It&#39;s quite emotional and consistent with my observations that Singapore has a relatively weak local tech atmosphere because many talents have gone to the US due to the h1b1 agreement with America. He attends tech conferences year-round — I/O, DroidCon, KotlinConf all go — and helped identify many experts on-site, like DroidCon&#39;s CEO, former and current Kotlin language designers, Uber&#39;s Ty Smith, etc. Including later when I went to chat with a table of cashapp people, it was because he tipped me off early that John, the paparazzi maintainer, was also on-site.&lt;/p&gt;
&lt;p&gt;As a side note, I later went to the CashApp table to chat with John, Ty, Mohit and others for half an hour, and discovered John and Jake were still working on-site, merging PRs — true community legends.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-14.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-15.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;extending-android-builds-(eab)&quot; tabindex=&quot;-1&quot;&gt;Extending Android Builds (EAB)&lt;/h2&gt;
&lt;p&gt;EAB is the English version of my book &amp;quot;Android Build and Architecture in Practice,&amp;quot; which was just published last month. Last year at the KotlinConf conference, I was somewhat green asking people to help review the &#39;Extending Android Builds&#39; table of contents and manuscript. Thanks to that conference, the book&#39;s content became more complete. This year I was much more confident, going directly with the newly released paper book and poster.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/Screenshot%202024-04-02%20at%209.09.51%E2%80%AFAM.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At 8 am on the first day when I just arrived at the venue, I went to Gradle&#39;s booth. Gradle officially &lt;a href=&quot;https://newsletter.gradle.org/2024/04&quot;&gt;recommended my new book in their Newsletter last month&lt;/a&gt;, and coming this time to gift the book to the Gradle team on-site was like fulfilling a wish. We chatted about many things, and when they asked for a QR code for trial reading content, I gave them the poster just right.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-16.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then, they actually &lt;strong&gt;put the poster at Gradle&#39;s booth and helped promote it for two days. When people passing by gathered around, they would also introduce this book&lt;/strong&gt;! And Gradle team&#39;s enthusiasm didn&#39;t stop there — I was also invited to &lt;strong&gt;participate in a Gradle interview&lt;/strong&gt;, discussing the book creation process, suggestions for newcomers, outlook on Gradle&#39;s future, etc. The video will be released on their YouTube channel later.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-17.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When I got to Google&#39;s booth, I immediately recognized Chris Willington from the AGP team. If I heard correctly, he said he&#39;s now the Manager of the AGP team in London. I said I&#39;ve watched his previous sharing videos at ADS many times — what a late meeting. Ivan&#39;s help last year gave me a lot of help, and Chris this year similarly gave me a lot of confidence, affirming the coverage scope of the book. Unfortunately there was no opportunity to put the book or poster at Google&#39;s booth haha. After giving the book to him, he also shared it with other colleagues.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-18.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Besides that, Google&#39;s Jeffrey and Uber&#39;s Ty also flipped through the book and gave some suggestions that are very inspiring for the second edition update. Special thanks to all the experts for their help, and definitely cherish the few opportunities to chat in person.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Finally, what makes this year happier than last year is that the Chinese developer team has grown again. Besides me and Shengyou, there&#39;s also Android GDE and KUG Shanghai organizer Yuang, KMP expert Yinlong, as well as high school student Zhuoxuan in Sweden and SiaoJie in London.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-19.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-20.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-21.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kotlin-conf-2024-22.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Hope to meet again at next year&#39;s KotlinConf!&lt;/p&gt;
&lt;p&gt;See you soon!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>2023 Global Odyssey: My Six &#39;Firsts&#39; in the Tech Community</title>
    <link href="https://2bab.me/en/blog/2024-01-31-recap-2023/"/>
    <updated>2024-01-31T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2024-01-31-recap-2023/</id>
    <content type="html">&lt;p&gt;Having navigated through the Covid-19 pandemic, I believe many of us share a renewed emphasis on the importance of life experiences. In 2023, my second year as an Android GDE, I ventured into several &amp;quot;firsts&amp;quot; in tech community involvement. These experiences not only recharged me with new energy but also sparked fresh ideas, which I&#39;m eager to share with everyone.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;April at Kotlin Conf in Amsterdam&lt;/strong&gt;: In April 2023, I attended Kotlin Conf in Amsterdam, marking my &lt;strong&gt;first journey outside Asia&lt;/strong&gt;. Every detail of this trip, from the conference agenda and booth setups to the high-quality attendees and presentations, left a lasting impression on me. Beurs van Berlage, a venue brimming with history, I witnessed a tech event that masterfully blended the past with the modern. I delved into themes like Kotlin Multiplatform, Kotlin Compiler, Compose for iOS, and ecosystem tools—cutting-edge topics that enriched my knowledge and connected me with like-minded friends, a rarity in the Android or mobile development circles at the time. Networking with peers from Europe added a valuable international dimension to my professional circle. Interestingly, conversations about my E-ink phone sparked connections beyond tech, discussing various gadgets and innovations for our file. At the booth area, I had the opportunity to engage in-depth with some Google staffs, who offered precious advice on my book, &lt;em&gt;Extending Android Builds&lt;/em&gt;. &lt;img src=&quot;https://2bab-images.lastmayday.com/2024-six-firsts-01.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;May&#39;s Online &amp;quot;Community Talks - AGP Special&amp;quot; Event&lt;/strong&gt;: In May, I took a significant step in volunteer sharing by organizing a special event titled &amp;quot;Community Talks - AGP Compilation &amp;amp; Build.&amp;quot; This was my &lt;strong&gt;first dual role&lt;/strong&gt; as a host and speaker and my initial foray into planning and executing an entire event. I experimented with a roundtable forum, a format seldom seen in China&#39;s GDG, discussing technical topics with several experts. This event bolstered my willingness to try new things and revealed my potential in organizing and conveying technical knowledge.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;July&#39;s &amp;quot;Internationalized App Design&amp;quot; at Google Export Day in Hangzhou&lt;/strong&gt;: In July, I shared insights on internationalized app design at an event in Hangzhou. This was my &lt;strong&gt;first time addressing a non-programming topic&lt;/strong&gt; at a large offline event, focusing on how culture, behaviors, and legal regulations impact app design. Using vivid examples from around the globe, I challenged both my knowledge and public speaking skills.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;September at Google IO Connect and the Greater China Community Mixer&lt;/strong&gt;: In September, I participated in the Google IO Connect and the Greater China Community Mixer in Shanghai, my &lt;strong&gt;first attendance at a community event of this scale&lt;/strong&gt;. Conversations with community members from Mainland China, Hong Kong, and Taiwan provided diverse perspectives and inspirations. This also marked my first insight into the operations of different chinese-speaking communities, greatly broadening my horizon. It represents a valuable chance for us, aiming to encourage increased technological dialogue among diverse areas. &lt;img src=&quot;https://2bab-images.lastmayday.com/2024-six-firsts-03.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;September&#39;s Google Devfest - Experts BootCamp&lt;/strong&gt;: Another September event took me to the Devfest - Experts BootCamp in Silicon Valley, my &lt;strong&gt;first participation in a global GDE gathering&lt;/strong&gt;. It offered a close-up on the design philosophies behind various Google technologies and face-to-face interactions with official Google engineers. Discussions on industry-specific development pain points provided a fresh perspective on challenges faced by developers in specific business sectors. &lt;img src=&quot;https://2bab-images.lastmayday.com/2024-six-firsts-04.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;December&#39;s Google Devfest Taipei&lt;/strong&gt;: In December, at Devfest Taipei, I presented &amp;quot;Ten Years into Android Gradle Plugin: Unraveling the Mysteries of Gradle.&amp;quot; My initial programming project began a decade ago with learning Android development, where Gradle and AGP were notably challenging for beginners. Yet, I found these experiences intriguing. I aimed to consolidate ten years of experience and technical evolution to share my learning techniques with more people. This was &lt;strong&gt;my first full presentation using the technical terminology of the Taiwan community&lt;/strong&gt;, a step outside my comfort zone. Preparing the presentation took weeks, including additional checks and note organization. Speaking at National Taiwan University, I felt the enthusiasm and excitement of a full classroom, enhancing my adaptability and deepening my connection with Taiwan&#39;s tech community. &lt;img src=&quot;https://2bab-images.lastmayday.com/2024-six-firsts-05.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In conclusion, I extend my gratitude to all event organizers and passionate developers worldwide, whether from Singapore, Europe, the United States, Mainland China, Taiwan, or elsewhere, for adding vibrant colors to my life and tech evangelism spectrum.&lt;/p&gt;
&lt;p&gt;Feel free to share your &amp;quot;first&amp;quot; experiences in the tech community in the comments. I&#39;m looking forward to exploring new life experiences in 2024. Let&#39;s strive for greatness together!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>I/O 23 and AGP&#39;s 10th Anniversary: Why Can&#39;t I Still Learn Gradle?</title>
    <link href="https://2bab.me/en/blog/2023-06-05-agp-10years/"/>
    <updated>2023-06-06T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2023-06-05-agp-10years/</id>
    <content type="html">&lt;p&gt;It&#39;s been ten years since Google first released Android Gradle Plugin (AGP) 0.1 at I/O 2013. However, even experienced Android developers may feel confused about using and deeply mastering Gradle and AGP. I&#39;ve written many Gradle/AGP related articles, done online sharing, and even written a book and a booklet. But through daily communication, I find it&#39;s still hard for most people to find a learning path, and even after investing time, there&#39;s no obvious progress. So on AGP&#39;s tenth anniversary, I decided to record some observations about &amp;quot;why I still can&#39;t learn Gradle/AGP&amp;quot; in brief words.&lt;/p&gt;
&lt;p&gt;This article will review Gradle&#39;s evolution, especially past difficulties and recent improvements over the past two to three years. Finally, I&#39;ll provide some learning strategies to help beginners and experienced engineers better master this build system.&lt;/p&gt;
&lt;p&gt;Disclaimer: Everything covered in this article is publicly available material, mainly from official public sharing or community voices.&lt;/p&gt;
&lt;p&gt;Gradle itself has many difficulties, including but not limited to changing DSL, lifecycle details, many compatibility issues, and difficulties in debugging, locating, and optimizing. Overall, implementing an extension plugin requires mastering many concepts at once.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202306031646569.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;(Why not ask the magic conch)&lt;/p&gt;
&lt;p&gt;But having many concepts and difficulties isn&#39;t the problem. Frameworks like React.js and Jetpack Compose are also quite complex conceptually, so why is getting started not that hard for people?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202306031712664.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In fact, many common frameworks and tools can help developers quickly find applicable scenarios in the early stages of learning, while learning Gradle and AGP requires investing a lot of time before seeing results. For most beginners, even if your English is good and you can understand Gradle&#39;s official User Guide, you can&#39;t quickly find the right usage scenarios, let alone combine related components for best practices. And from another angle, we may have forgotten that as Android developers, this tool fundamentally needs to work in the Android App build process to maximize its value, and learning AGP is neglected by many people.&lt;/p&gt;
&lt;p&gt;The above observations lead to two thoughts in this article:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Gradle&#39;s official documentation consists of scattered knowledge points, lacking practical tutorials oriented toward beginners, i.e., Tutorial-type documentation.&lt;/li&gt;
&lt;li&gt;AGP&#39;s public APIs and extensibility have certain limitations. AGP&#39;s goals are long-term, with its primary purpose being to promote the overall development of the Android build ecosystem, but to balance maintenance costs, it&#39;s impossible to open all APIs.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;gradle&#39;s-documentation-problem&quot; tabindex=&quot;-1&quot;&gt;Gradle&#39;s Documentation Problem&lt;/h2&gt;
&lt;p&gt;First point, the documentation problem. &amp;quot;Scattered&amp;quot; refers to two aspects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Many necessary knowledge points lack guidance documentation: For example, everyone has used the &lt;code&gt;android.buildTypes{...}&lt;/code&gt; definition, which is essentially basic use of the &lt;code&gt;NamedDomainObjectContainer&lt;/code&gt; class and its series of parent classes. What you can find is API annotation documentation, but you can&#39;t find guidance on the User Guide, such as comparison of multiple similar APIs, CRUD in Groovy DSL, CRUD in Kotlin, how to migrate and use between the two DSLs, etc. The earliest reference material from Google searches on this is Mr.HAKi&#39;s &lt;a href=&quot;https://blog.mrhaki.com/2016/02/gradle-goodness-create-objects-with-dsl.html&quot;&gt;&lt;code&gt;Gradle Goodness&lt;/code&gt; series articles&lt;/a&gt;, which also has many other API use cases in the blog series.&lt;/li&gt;
&lt;li&gt;The documentation has some 0-to-1 content, such as lifecycle and task basics. But a lot of other common components, API techniques, and how to combine and apply multiple tools are not detailed enough, or may be placed in obscure corners. The comprehensive reading experience is somewhat lacking: you clearly seem to have seen it somewhere, but can&#39;t remember, and even keyword searching is somewhat difficult. Users lack a learning roadmap of their own.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From this perspective, some well-known Gradle video series follow the design approach of official documentation and are not conducive to beginners getting started quickly. Of course, in terms of knowledge coverage, they remain a powerful supplement to official documentation.&lt;/p&gt;
&lt;p&gt;But here we must insert a turning point: most framework/tool documentation, even with sufficient knowledge points, still has problems of being scattered and without priority, with specific reasons behind it. For many excellent framework developers, writing good API comments in each file, plus a lot of testing, constitutes a rigorous documentation that other developers can learn from on their own. And the exported API documentation is the core &amp;quot;documentation&amp;quot; — it doesn&#39;t require a lot of cases and guidance materials to be called &amp;quot;documentation&amp;quot;; that&#39;s the icing on the cake, which is Tutorials.&lt;/p&gt;
&lt;p&gt;This is also why many tech companies and tech products hire special developer advocates, technical writers, etc. Guidance documentation and open-source Sample repositories are mostly released through their hands. A typical example is &lt;a href=&quot;https://github.com/android/nowinandroid&quot;&gt;Now-In-Android&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Without certain complete guidance documentation, what remains most in Gradle documentation are Script Plugin examples. Due to DSL API reasons, although the documentation looks neat, once you need to write your own binary plugin (i.e., a plugin published after compilation, containing one or more &lt;code&gt;Plugin&lt;/code&gt; implementations internally — libraries we reference are usually published as binary plugins), you often need native APIs. And in the previous seven or eight years due to the Groovy-dominated script ecosystem, finding corresponding native APIs from some DSLs was somewhat difficult — &lt;code&gt;NamedDomainObjectContainer&lt;/code&gt; again becomes a typical case of this problem. The find-and-add integrated API implemented through Groovy&#39;s dynamic features means users actually don&#39;t know whether they&#39;re using &lt;code&gt;create()&lt;/code&gt; or &lt;code&gt;getByName()&lt;/code&gt; or &lt;code&gt;named()&lt;/code&gt; etc. behind the scenes. This is an advantage when writing &lt;code&gt;build.gradle&lt;/code&gt; configuration, but a disadvantage when really writing binary plugins. In my observation, Groovy amplified the difficulty for ordinary people wanting to master this tool; but for many industry pioneers, proficient Groovy skills made them feel Gradle was flexible and easy to use, and until K2 was released, Groovy kept configuration phase script compilation faster.&lt;/p&gt;
&lt;p&gt;Finally, because of Gradle&#39;s product stance, it won&#39;t give too many Android ecosystem examples. For AGP, refer to the next issue&#39;s discussion. Unfortunately, due to some special reasons, many monitoring APIs haven&#39;t been released either. We&#39;ve even seen some third-party plugins already calling private APIs, yet there&#39;s still no explanation for this part of the issue. This includes the deprecation of most public lifecycle hooks (due to Configuration Cache), and there&#39;s no complete explanation for alternatives to these public APIs. Some official best practices can only be seen from Gradle forums or Slack discussion groups.&lt;/p&gt;
&lt;h2 id=&quot;agp&#39;s-openness-problem&quot; tabindex=&quot;-1&quot;&gt;AGP&#39;s Openness Problem&lt;/h2&gt;
&lt;p&gt;Gradle has a steep learning curve, but &amp;quot;learning a lot of basics&amp;quot; yet not being able to produce things is more because of lack of understanding of the ecosystem. Gradle as an underlying foundation supports not just Android, but also Java, Kotlin, Scala and other language ecosystems, as well as the nearly largest JVM backend framework ecosystem including Spring, etc. It&#39;s not hard to imagine that for Android developers, understanding the various details of Android Gradle Plugin (AGP) and related compilation tools is a core part of the packaging process.&lt;/p&gt;
&lt;p&gt;In the first five or six years, AGP rarely publicized its development plans and community development support, and it was hard to find related technical documentation. In 2018 and 2019, it gradually spoke up at some important official conferences, understood third-party plugin developers&#39; pain points and synchronized its progress on public API construction, and fully released major changes related to AGP 7.0 at the I/O conference in 2021, including &lt;a href=&quot;https://developer.android.com/build/extend-agp&quot;&gt;documentation&lt;/a&gt; and &lt;a href=&quot;https://github.com/android/gradle-recipes&quot;&gt;Sample&lt;/a&gt; support.&lt;/p&gt;
&lt;p&gt;From the issue tracker situation, although P1/S1 issues have been resolved, there are still many &amp;quot;secondary&amp;quot; issues to be improved. I personally found some problems while trying to help promote new APIs, such as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Currently only a dozen or so public Artifact APIs, while private APIs have over 200 types.&lt;/li&gt;
&lt;li&gt;Public bytecode modification Artifact API differs from private implementation, causing &lt;a href=&quot;https://issuetracker.google.com/issues/276431893&quot;&gt;cache invalidation&lt;/a&gt; when there are multiple transformation tasks.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Based on the above objective content, developers have no intuitive path to learning AGP. Many articles directly list its task list and analyze one by one when introducing AGP, which is also a helpless choice. But the learning effect of this approach may... not be too good. Since there&#39;s no documentation and architecture design, systematic learning requires reading source code and understanding overall design paradigms. The reading process needs to be accompanied by thinking about multiple single-point problems encountered in practice, such as what paradigm AGP&#39;s task registration process has, how the chain structure behind the new Artifact API helps data transmission and final API exposure, etc. And the prerequisite for having these questions is that you, as an efficiency-focused developer, or putting yourself in that position, write one plugin after another that may not conform to Gradle and AGP &amp;quot;specifications,&amp;quot; see problems from them, read source code, solve problems, and eventually grow. I call the plugins written this way &amp;quot;Android ecosystem cooperative plugins,&amp;quot; which are worlds apart from some platform-independent plugins (like plugins that analyze Gradle dependencies).&lt;/p&gt;
&lt;p&gt;Most people can&#39;t or don&#39;t get the opportunity to go through the above process, so the market only has two types of people: beginners and experts. And the middle levels have obvious gaps. This is also reflected in the distribution of topic exposure — over the past ten years, AGP topic sharing has often been mixed in with AS or Android development tool updates. It wasn&#39;t until recently that AGP finally has its own &lt;a href=&quot;https://developer.android.com/build&quot;&gt;sub-tab&lt;/a&gt; in the documentation area (independent navigation).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/202306032220148.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;recent-improvements-and-recommended-learning-strategies&quot; tabindex=&quot;-1&quot;&gt;Recent Improvements and Recommended Learning Strategies&lt;/h2&gt;
&lt;p&gt;Although the various problems mentioned above once made learning and applying Gradle difficult, there have been some major improvements in the past two to three years:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kotlin became the default scripting language&lt;/strong&gt;: Kotlin is now Gradle&#39;s default scripting language. For Android developers, this is good news because they can use the same language for both app development and build script development. Compared to Groovy, Kotlin is easier to get started with.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;New features in AGP&lt;/strong&gt;: AGP has followed Gradle&#39;s Lazy Property, Configuration Cache, Work Executor and other features; provided truly public APIs, including a complete developer-facing Variant lifecycle, Artifact exposure mechanism, and related Sample documentation. Developers can more easily extend AGP&#39;s functionality.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Compilation speed improvements&lt;/strong&gt;: Collaboration between Google, JetBrains, and Gradle, for example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Various caching mechanisms, parallel execution mechanisms, and lazy registration mechanisms have been introduced to AGP.&lt;/li&gt;
&lt;li&gt;Kotlin&#39;s compilation speed continues to improve.&lt;/li&gt;
&lt;li&gt;KSP symbol processing tool developed by Google has been introduced to various Android and Kotlin development tools, greatly improving symbol processor efficiency.&lt;/li&gt;
&lt;li&gt;Android Studio&#39;s various Gradle peripheral function support, including AGP API migration, performance analysis tools, etc.&lt;/li&gt;
&lt;li&gt;Future new Kotlin compiler K2 and complete compiler plugin ecosystem toolchain will bring more extreme performance improvements.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Based on the above improvements, if I were to give some learning advice to beginners and intermediate-advanced engineers, I believe it would be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Strategies for &lt;strong&gt;beginners&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.1 First learn basic concepts of Kotlin and Gradle, such as DSL, project organization structure, task mechanism, etc.&lt;/li&gt;
&lt;li&gt;1.2 Then learn common plugins and their Extension usage, such as Android, Java, Kotlin, Maven publishing, etc.&lt;/li&gt;
&lt;li&gt;1.3 Finally gradually delve into some advanced topics, such as simple script modifications, simple task writing, basic compilation problem locating, etc.&lt;/li&gt;
&lt;li&gt;1.4 &lt;a href=&quot;https://koge.2bab.me/#/&quot;&gt;KOGE&lt;/a&gt;, an open-source booklet I organized, covers most of the above basic concepts. Welcome to read &amp;amp; provide suggestions &amp;amp; participate in writing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Strategies for &lt;strong&gt;intermediate-advanced engineers&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2.1 Centered around &amp;quot;enhancing Android Gradle Plugin functionality,&amp;quot; learn to write plugins that cooperate with the Android ecosystem. This is an important threshold for improving all-around compilation and build quality that must be firmly crossed. Driven by goals, discover problems, read source code, solve problems, repeat this cycle, and continuously improve understanding of ecosystem cooperative plugins.&lt;/li&gt;
&lt;li&gt;2.2 Apply plugin technology to all aspects of production, such as resource modification, source code modification, bytecode modification, etc. Not only learn related compilation toolchains, but also think about problems from multiple angles, combining compile-time and runtime solutions to complement each other&#39;s advantages.&lt;/li&gt;
&lt;li&gt;2.3 Learn more about AGP&#39;s core ideas and architecture design, optimize the above various plugin solutions, and bring excellent architectural ideas into the App&#39;s efficiency architecture, bringing the team a development experience of &amp;quot;low cost, low compilation time, high performance, high maintainability.&amp;quot;&lt;/li&gt;
&lt;li&gt;For friends who want to accelerate learning, you can also get my new book &amp;quot;Extending Android Builds&amp;quot; to systematically learn Gradle plugin development and AGP core design.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In summary, although the early learning process of Gradle and AGP may seem daunting, with recent improvements and the right learning strategies, developers can better master this powerful build tool to improve productivity and code quality.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>KotlinConf 2023 Experience</title>
    <link href="https://2bab.me/en/blog/2023-04-15-kotlin-conf-23-exp/"/>
    <updated>2023-04-15T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2023-04-15-kotlin-conf-23-exp/</id>
    <content type="html">&lt;p&gt;Just finished the KotlinConf 2023 journey, my heart is surging with excitement. While the feeling is still fresh, I&#39;m recording what I saw and heard. In just two days, I gained numerous life experiences I&#39;d never had before. The conference&#39;s agenda arrangement, booth setup, high-quality attendees and content sharing, even the peripheral support like food, coffee, and Party band were all top-tier.&lt;/p&gt;
&lt;p&gt;Note: This is a quick note written on my phone, so please forgive any formatting issues.&lt;/p&gt;
&lt;h2 id=&quot;venue&quot; tabindex=&quot;-1&quot;&gt;Venue&lt;/h2&gt;
&lt;p&gt;This year is the second time KotlinConf is held in Amsterdam, Netherlands. The last time was before the pandemic. The location is Beurs van Berlage, a historical building that has been renovated and now serves as a venue for conference rentals.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-01.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-02.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-03.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The main hall blending history and modernity carries substantial content and memories.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-04.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Many people from other countries said this was the most beautiful venue with the best food. Of course, since it&#39;s held in Europe, most of the on-site audience, including booth staff, came from European countries like UK, France, Germany, Czech Republic, Belgium, Italy, etc. The rest felt like they came from North America, plus a small number from Asia, Africa, and Latin America. Overall, there were various languages being spoken on site, and since most people aren&#39;t native English speakers, there&#39;s not much pressure when chatting in English together.&lt;/p&gt;
&lt;h2 id=&quot;agenda&quot; tabindex=&quot;-1&quot;&gt;Agenda&lt;/h2&gt;
&lt;p&gt;Kotlin language is naturally the theme of the conference, but the Kotlin ecosystem has developed to the point today where it&#39;s far more than traditional JVM content. We see topics like Android, Backend, Multiplatform, Infrastructure &amp;amp; Tools, QA and automation, etc. Besides the Opening Keynotes every morning, the rest of the time has five or six events running in parallel, requiring you to make choices between multiple Sessions — this is actually a good thing, because judging conference quality is extremely subjective. When topic sharers find the right audience and audiences find the content most suited to their taste, the best chemistry happens.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-05.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;(Some bookmarked topics)&lt;/p&gt;
&lt;p&gt;For example, I mostly chose topics related to Kotlin Multiplatform, Kotlin Compiler, Compose for iOS, and ecosystem tools, leaning toward the novel or hardcore.&lt;/p&gt;
&lt;p&gt;Two most important takeaways from listening to sessions on-site:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;As often said, you can chat more with the speaker after they finish. For example, after listening to K2 Compiler Plugins, I chatted a bit more with Mikhail privately. He explained that &amp;quot;documentation has been released&amp;quot; mainly refers to API docs, not Tutorial docs. Then he opened Kotlin source code on-site and we looked together at where the public interfaces he just mentioned are located — the code is well-standardized with clear comments, so the generated documentation is naturally good. Combined with this session&#39;s video and several official real cases, I believe KCP will spawn more community ecosystem under K2&#39;s boost.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-06.png?imageslim&quot; alt=&quot;&quot; /&gt;
2. As mentioned earlier, parallel sessions have already diverted the crowd, so the people you meet in this breakout room must be well-matched to this topic. Taking K2 Compiler Plugins as an example again, although the topic is niche and the breakout room isn&#39;t large, when the Speaker did research asking how many people had written a Compiler Plugin, surprisingly 30-40% raised their hands, and there were four or five people raising hands during the Q&amp;amp;A session. So this small room has many new friends worth meeting — seize the opportunity!&lt;/p&gt;
&lt;h2 id=&quot;offline-networking&quot; tabindex=&quot;-1&quot;&gt;Offline Networking&lt;/h2&gt;
&lt;p&gt;Continuing from the previous sharing, as the last questioner, due to limited English skills my expression was a bit wordy, and the speaker seemed a bit confused — so we ended that session. After he stepped down, we discussed with several other developers nearby about the pros and cons of K2&#39;s KCP plan to cancel the Gradle SubPlugin setting. Among them, I met a young guy from Belgium who had just graduated but was already quite skilled discussing these topics. We successfully exchanged contact information on the way back to the main hall.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-07.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Right after that, at the detekt session I met two friends who felt like kindred spirits — Nicola and Chao from the detekt core maintenance team. Chao and I even went to participate in recording &amp;quot;Kotlin Fireside Chat&amp;quot; together just an hour after meeting.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-08.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
(Nicola, Chao, me, Shengyou)&lt;/p&gt;
&lt;p&gt;Besides extending connections from breakout rooms, I also started chatting with people in the hall or lounge starting from breakfast on the first day. In fact, you can even sacrifice a bit of session time to participate in this once-a-year face-to-face moment. For example, if you&#39;ve listened to too many sessions today, leave the remaining hour&#39;s session for later when the replay is online!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-09.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-10.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-12-1.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-11.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-13.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-14.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-14-1.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The people above are both industry experts and one-way online friends I&#39;ve followed for years. The last two are even co-presenters. Since social media profiles use real photos, they&#39;re easy to recognize on-site. Get some water, approach, first listen to what others are chatting about, then slowly introduce yourself and learn more. For example, topics I first heard included: Maven Central publishing process being cumbersome, cycling from Paris to Amsterdam, side projects they&#39;re working on, etc. During second and third encounters for deeper chats, you can discuss more like career planning, technical questions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-15.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
(My E-ink phone attracted many people&#39;s attention / opened conversations)&lt;/p&gt;
&lt;p&gt;The third way to approach people is going directly to Booths. For example, after Google&#39;s sharing, Jeffrey took me to Google&#39;s booth.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-16.png?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-17.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-18.png?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-19.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-20.png?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-21.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There he introduced me to Ivan. Ivan is one of the developers of AS &amp;amp; AGP tools. He answered many of my questions about current API design, version iteration, bug submission and fixing. Most importantly, he looked at the English table of contents of the book I&#39;m writing on-site, and after discussion gave me multiple practical suggestions.&lt;/p&gt;
&lt;p&gt;Then I saw Marton at Google&#39;s booth. He&#39;s one of the organizers of Android Worldwide conference, and we had online exchanges before when I was a speaker there. Recently he&#39;s been organizing and publishing KSP and Android development information (as shown above), so we even searched together on-site for my KSP sharing video from back then (forgot the sharing title, we searched for a minute or two, almost awkward).&lt;/p&gt;
&lt;p&gt;There were also evangelists like Florina on-site. Due to limited time, we just said hello and shared what work we&#39;re doing, etc.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-22.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Booths usually have lots of swag. Here&#39;s a portion of stickers, pins and other swag I got. Later I hope to share them with local developers at other events, passing on KotlinConf&#39;s grand occasion and enthusiasm. Of course, there&#39;s also Kotlin&#39;s booth and swag, plus their staff and engineers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-23.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-24.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-25.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-26.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-27.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Special thanks to Shengyou for helping me meet more friends from JetBrains, and for attending the evening Party together where we took photos with several German friends and chatted for nearly an hour. For more conversations with Shengyou, follow Kotlin&#39;s official account and &amp;quot;Kotlin Fireside Chat&amp;quot; podcast!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-28.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The final Easter egg is — a photo with Jake Wharton:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-29.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
(YOU KNOW WHO)&lt;/p&gt;
&lt;h2 id=&quot;others&quot; tabindex=&quot;-1&quot;&gt;Others&lt;/h2&gt;
&lt;p&gt;I haven&#39;t attended many offline conferences before. Earlier years I attended some GDG events in mainland China, and in &#39;18, &#39;19, &#39;22 I attended several Google and DroidCon events in Singapore. From my discussions with friends on-site about tech conferences, this KotlinConf is still noteworthy among many conferences.&lt;/p&gt;
&lt;p&gt;Communicating with the most interesting people in the same industry worldwide, meeting new friends, absorbing new knowledge and generating inspiration in a relaxed atmosphere. After experiencing Covid-19, I think everyone cherishes opportunities to talk face-to-face more. If I hadn&#39;t booked this early bird ticket a year in advance, I would definitely regret it.&lt;/p&gt;
&lt;p&gt;Next year, see you again!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-30.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-31.jpg?imageslim&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://2bab-images.lastmayday.com/kt-conf-23-32.jpg?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Build Guide #12 Enabling Plugin Features Based on Variant</title>
    <link href="https://2bab.me/en/blog/2021-12-21-enable-feature-by-variant/"/>
    <updated>2021-12-21T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2021-12-21-enable-feature-by-variant/</id>
    <content type="html">&lt;p&gt;Anyone who has used third-party Gradle plugins in Android development should have encountered this problem: &lt;strong&gt;I only want to apply this plugin in certain buildTypes or flavors, but I can&#39;t find a suitable way.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The root cause of this problem is that Variant (buildType + flavor) is not a Gradle concept, but comes from Android Gradle Plugin (AGP)&#39;s multi-channel configuration. So from Gradle&#39;s platform perspective, it&#39;s difficult to provide convenient support upward: plugin introduction within a project is global and platform-level, not changed by the will of any particular plugin (AGP).&lt;/p&gt;
&lt;p&gt;OK, but our problem still needs to be solved, hence the four solutions below (fog). We&#39;ll explain the ideas in order from ineffective to highly effective.&lt;/p&gt;
&lt;h2 id=&quot;applying-plugins-based-on-variant-(ineffective-x)&quot; tabindex=&quot;-1&quot;&gt;Applying Plugins Based on Variant (Ineffective X)&lt;/h2&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;flavorDimensions &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;server&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;productFlavors &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;production&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        dimension &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;server&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        applicationIdSuffix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.production&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        versionNameSuffix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-production&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        versionCode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;xxx&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Ineffective&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Many Android or Gradle beginners think, can&#39;t I just put &lt;code&gt;apply(...)&lt;/code&gt; here? (Of course, you can&#39;t use &lt;code&gt;plugins { id(&amp;quot;xxx&amp;quot;) }&lt;/code&gt;). The misconception here is that &lt;strong&gt;the &lt;code&gt;apply(...)&lt;/code&gt; method has no association with the current context object&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20211221205845.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20211221210042.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The closure context of &lt;code&gt;create(&amp;quot;...&amp;quot;){}&lt;/code&gt; is &lt;code&gt;this:ApplicationProductFlavor&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apply(...)&lt;/code&gt; is an extension method mounted on PluginAware (in &lt;code&gt;build.gradle.kts&lt;/code&gt;), acting on the entire Project, so your plugin will still be introduced globally.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A similar classic misconception:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;production&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    dimension &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;server&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    applicationIdSuffix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.production&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    versionNameSuffix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-production&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    versionCode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt;    packagingOptions &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        jniLibs &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            excludes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20211221212514.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You might think this sets separate &lt;code&gt;packagingOptions&lt;/code&gt; for this flavor, but actually this method is a method of the &lt;code&gt;CommonExtension&lt;/code&gt; interface, not belonging to &lt;code&gt;ApplicationProductFlavor&lt;/code&gt;. That is, it sets for the entire Android Gradle Plugin, not a single flavor. If you make different settings in each flavor, the last setting will override all previous ones. For solutions to this problem, refer to the &amp;quot;Secondary Configuration&amp;quot; section in my previous &lt;a href=&quot;https://www.bilibili.com/video/BV1WP4y1G71h&quot;&gt;GDG sharing&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;applying-plugins-based-on-command-(partially-effective-x)&quot; tabindex=&quot;-1&quot;&gt;Applying Plugins Based on Command (Partially Effective X)&lt;/h2&gt;
&lt;p&gt;The problem we&#39;re discussing can also be found in some StackOverflow Q&amp;amp;As, with many answers about &amp;quot;parsing the entered command&amp;quot; for judgment.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gradle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startParameter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;taskRequests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;production&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach can solve situations where your entered command is &lt;code&gt;./gradlew clean assembleProductionRelease&lt;/code&gt; etc., because such commands only contain information for one Variant (i.e., Production + Release). However, if the entered command is &lt;code&gt;./gradlew clean assembleRelease&lt;/code&gt;, which packages both &lt;code&gt;Staging&lt;/code&gt; and &lt;code&gt;Production&lt;/code&gt; flavors simultaneously, the &lt;code&gt;if(...)&lt;/code&gt; condition above won&#39;t hold at all, and your build will be broken.&lt;/p&gt;
&lt;p&gt;In most cases, &lt;code&gt;taskRequests&lt;/code&gt; should not be obtained and used by third-party Android build plugin developers. Like the first misconception, it&#39;s Gradle&#39;s platform API, not optimized for AGP&#39;s upper-level Variant concept.&lt;/p&gt;
&lt;h2 id=&quot;disabling-corresponding-variant&#39;s-plugin-tasks-(effective-y)&quot; tabindex=&quot;-1&quot;&gt;Disabling Corresponding Variant&#39;s Plugin Tasks (Effective Y)&lt;/h2&gt;
&lt;p&gt;Of course, there are also correct, effective, and universal approaches mentioned on SO — &lt;strong&gt;don&#39;t dynamically disable (or enable) plugins, disable the Tasks within the plugin instead.&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;plugins &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.example.ua&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// or apply(&quot;com.example.ua&quot;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;whenTaskAdded&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;production&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;release&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;UploadArchive&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        enabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suppose there&#39;s an upload build result plugin called &lt;code&gt;com.example.ua&lt;/code&gt;. No matter what situation, we reference it in &lt;code&gt;build.gradle.kts&lt;/code&gt;. But looking below, we made a similar &lt;code&gt;if(...)&lt;/code&gt; judgment as the previous misconception based on &lt;code&gt;whenTaskAdded {...}&lt;/code&gt;, then set the matching task&#39;s &lt;code&gt;enabled&lt;/code&gt; property to &lt;code&gt;false&lt;/code&gt;. Why doesn&#39;t the previous method work while this one does?&lt;/p&gt;
&lt;p&gt;Let&#39;s review this Gradle script and &lt;code&gt;android{}&lt;/code&gt; DSL rule: no matter what command you enter in shell, whether &lt;code&gt;clean&lt;/code&gt; or &lt;code&gt;help&lt;/code&gt;, this script will be completely executed, and all Variant Tasks will be registered (though nowadays it&#39;s usually lazy registration with &lt;code&gt;register(...)&lt;/code&gt; instead of &lt;code&gt;create()&lt;/code&gt;). &lt;strong&gt;And &lt;code&gt;whenTaskAdded{...}&lt;/code&gt; is a Gradle platform API. It doesn&#39;t care whether your upper-level registered Tasks are divided by Variant — it just provides a callback timing for developers for all registered Tasks that will definitely be added to the run graph (which is a DAG). Meanwhile, almost all Android ecosystem cooperative plugins name their Tasks based on Variant name (if it needs to be a VariantAware Task), such as &amp;quot;UploadArchiveWithLogForProductionRelease&amp;quot;. This unwritten rule gives us the opportunity for string matching, which is the code you see above.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As for how to find all corresponding Tasks for a plugin, there&#39;s currently no automated way, nor an API (though in a recent AGP Team Q&amp;amp;A, they mentioned they&#39;re working with Gradle to advance this feature). What you can do is: &lt;strong&gt;print the Task List before introducing the plugin, print again after introducing it, and find the Diff between them.&lt;/strong&gt; Of course, for some simpler plugins, you can just look at the documentation or source code to organize which Tasks there are.&lt;/p&gt;
&lt;p&gt;So this solution can be summarized in one sentence: always reference the plugin, but disable unneeded Tasks.&lt;/p&gt;
&lt;h2 id=&quot;intercepting-at-task-registration-(highly-effective-y)&quot; tabindex=&quot;-1&quot;&gt;Intercepting at Task Registration (Highly Effective Y)&lt;/h2&gt;
&lt;p&gt;I mentioned in the previous article &lt;a href=&quot;https://2bab.me/2021/12/19/bundle-tool-plugin&quot;&gt;Build Guide #11 BundleTool Gradle Plugin&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;bundle-tool-gradle-plugin supports enabling plugin features by different variant channels.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Just hearing this sounds similar to the previous solution — could it be hiding &lt;code&gt;enabled = false&lt;/code&gt; inside the Plugin, then exposing a DSL switch configuration for users to choose? Actually no, the solution approach is already very similar. But thinking a bit more, there are two areas for improvement:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Since we want to control the switch inside the plugin, why use &lt;code&gt;task.enabled = false&lt;/code&gt;? &lt;strong&gt;Just skip the registration process directly&lt;/strong&gt; (of course, this approach isn&#39;t suitable for all situations; sometimes dynamic disabling is still needed).&lt;/li&gt;
&lt;li&gt;For most Android ecosystem cooperative plugins, their DSL is generally Variant-unrelated static configuration. If you want to design a Variant-related configuration, you might only be able to pass in a bunch of custom rules (as in the code below):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Static DSL configuration&lt;/span&gt;&lt;br /&gt;bundletool &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    enableByVariantRules &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;debugStaging&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            enabledFeature1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;br /&gt;            enabledFeature2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;br /&gt;            enabledFeature3 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;debugProduction&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The complexity of such rules is hard to control. In the above code, it&#39;s a Cartesian product of 2 * 2 * 3, making configuration very inconvenient. The solution is also easy to think of: &lt;strong&gt;if it could be passed as a Kotlin Lambda (or Groovy Closure), wouldn&#39;t that be convenient? Just like the &lt;code&gt;onVariants(...) {...}&lt;/code&gt; method we use for secondary configuration, we need something &amp;quot;dynamic,&amp;quot; something users can interact with, to reduce configuration complexity.&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// &quot;Dynamic&quot; DSL configuration, based on Kotlin Lambda and Groovy Closure&lt;/span&gt;&lt;br /&gt;bundleTool &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    enableByVariant &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; feature &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;debug&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; feature &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; BundleToolFeature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GET_SIZE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;variant&lt;/code&gt; parameter is &lt;code&gt;com.android.build.api.variant.Variant&lt;/code&gt;, the Variant object from Variant API v2 we normally use; the &lt;code&gt;feature&lt;/code&gt; parameter is a custom enum class, convenient for each plugin to customize. Now let&#39;s look at the implementation:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt; * Extract `enableByVariant(...)` function, can be reused in other plugins.&lt;br /&gt; * Currently the Lambda and Closure are defined by raw types, they can be encapsulated&lt;br /&gt; * by [Property] as well to fulfill &quot;lazily produced/consumed&quot; purpose.&lt;br /&gt; */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; EnableByFeatureExtension&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; kotlinEnableByVariant&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EnableByVariant&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; groovyEnableByVariant&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Closure&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Boolean&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// For Gradle Kotlin DSL&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;enableByVariant&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EnableByVariant&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        kotlinEnableByVariant &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; selector&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// For Gradle Groovy DSL&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;enableByVariant&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Closure&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Boolean&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        groovyEnableByVariant &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; selector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dehydrate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;internal&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isFeatureEnabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;variant&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Variant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        kotlinEnableByVariant &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            kotlinEnableByVariant&lt;span class=&quot;token operator&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;variant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        groovyEnableByVariant &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            groovyEnableByVariant&lt;span class=&quot;token operator&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;variant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;internal&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typealias&lt;/span&gt; EnableByVariant&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;variant&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Variant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Boolean&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; BundleToolExtension&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EnableByFeatureExtension&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BundleToolFeature&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; buildApks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NamedDomainObjectContainer&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BuildApksRule&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; getSize&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NamedDomainObjectContainer&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;GetSizeRule&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; BundleToolFeature &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// It&#39;s currently a predecessor for GET_SIZE,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// and the first job that plugin does,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// disable it will cause the task registry got removed.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// The work action that transforms .aab to .apks using `build-apks` command.&lt;/span&gt;&lt;br /&gt;    BUILD_APKS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// The work action that gets the transformed .apks file size using `get-size total` command.&lt;/span&gt;&lt;br /&gt;    GET_SIZE&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we defined Closure/Lambda for both Groovy and Kotlin Gradle DSL. They accept &lt;code&gt;Variant&lt;/code&gt; and a custom generic parameter, returning a Boolean value indicating whether to enable the corresponding feature. Then we defined the &lt;code&gt;isFeatureEnabled(..,): Boolean&lt;/code&gt; method to integrate both language branches, convenient for internal plugin calls. When actually using this &lt;code&gt;EnableByFeatureExtension&lt;/code&gt;, we let the plugin&#39;s &lt;code&gt;BundleToolExtension&lt;/code&gt; inherit from it, thus isolating the plugin&#39;s main functionality from the general switch feature. Any other plugin can conveniently copy this class and add it to their own Extension.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;androidExtension&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onVariants&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isFeatureEnabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;variant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; BundleToolFeature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;BUILD_APKS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token label symbol&quot;&gt;@onVariants&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; featureGetSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isFeatureEnabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;variant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; BundleToolFeature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GET_SIZE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; buildApksTaskProvider &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;register&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BundleToolTask&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TransformApksFromBundleFor&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;variantName&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        enableGetSizeFeature &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; featureGetSize&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; BundleToolTask &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DefaultTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@get:Input&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; enableGetSizeFeature&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@TaskAction&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;enableGetSizeFeature&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we demonstrated examples of using this switch in two places:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If a Feature affects the entire Task, we can use the switch to skip its registration.&lt;/li&gt;
&lt;li&gt;If a Feature only affects part of a Task&#39;s functionality, we pass the switch value into the Task for internal judgment.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The above process is not entirely fixed; this idea can be adjusted in combination with other switch methods. I&#39;ve already used this solution in &lt;a href=&quot;https://github.com/2BAB/bundle-tool-gradle-plugin&quot;&gt;bundle-tool-gradle-plugin&lt;/a&gt; and &lt;a href=&quot;https://github.com/2BAB/ScratchPaper&quot;&gt;ScratchPaper&lt;/a&gt;, making plugin feature introduction more flexible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Of course, you may have also noticed that this solution is from the plugin development perspective. Unless all plugin developers use a similar solution for optimization, Solution 3 remains the only universal approach from the user&#39;s perspective. But the advantage of this solution is finer granularity control and more convenience — developers can customize as they wish.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;This article mainly discussed solutions for enabling plugin features based on different Variant channels. It may also be the first time there&#39;s been such detailed and complete multi-solution comparative discussion anywhere online. The thinking and solutions in this article can actually be applied to other Gradle scenarios as well — feel free to use your imagination. That&#39;s it for the last technical article of 2021. See you next year!&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Build Guide #11 BundleTool Gradle Plugin</title>
    <link href="https://2bab.me/en/blog/2021-12-19-bundle-tool-plugin/"/>
    <updated>2021-12-19T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2021-12-19-bundle-tool-plugin/</id>
    <content type="html">&lt;p&gt;App Bundle (.aab) as the officially promoted new delivery format for Android has been around for a while. This year&#39;s PlayStore policy mandating that &amp;quot;all new apps must use .aab&amp;quot; for submission has become a major driver for everyone to make the switch. Making your app compatible with and packaging into .aab format is not complicated - simply add the corresponding DSL configuration and replace the packaging command with &lt;code&gt;bundle${VariantName}&lt;/code&gt;. However, the .aab obtained after packaging cannot be directly installed on debugging devices using adb locally. You need to use &lt;a href=&quot;https://github.com/google/bundletool&quot;&gt;bundletool&lt;/a&gt; to convert it to .apks before calling its install command to install.&lt;/p&gt;
&lt;h2 id=&quot;common-scenarios&quot; tabindex=&quot;-1&quot;&gt;Common Scenarios&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.android.com/studio/command-line/bundletool#build_app_bundle&quot;&gt;BundleTool official documentation&lt;/a&gt; lists various uses of CLI commands. In more complex environments, you might have encountered these scenarios with BundleTool:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;During local testing, you need to manually run conversion and installation commands using BundleTool&#39;s CLI;&lt;/li&gt;
&lt;li&gt;In CI environments, you need to configure the BundleTool CLI tool, then write some Shell scripts to control the post-packaging conversion process;&lt;/li&gt;
&lt;li&gt;Real device labs, cloud device testing platforms need to integrate BundleTool into corresponding tests, making it convenient to generate apks packages for specific device models.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These scenarios often come with the following issues:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Some internal test package distribution channels don&#39;t support aab and apks. We need to do quick functional tests on these non-GP, non-real-device-lab platforms where universal apk is the best choice for such scenarios (but packaging a new apk from scratch is obviously a waste of resources);&lt;/li&gt;
&lt;li&gt;After uploading the aab package to GP&#39;s Console, you can clearly see the estimated download size for all supported devices, but this data metric is not convenient to collect and quantify on CI using Shell scripts (though it might be possible with some effort or Python);&lt;/li&gt;
&lt;li&gt;AGP actually integrates the BundleTool package, so having to configure BundleTool&#39;s CLI separately each time seems somewhat redundant.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This made me wonder: AGP has BundleTool as a dependency - doesn&#39;t it have these subsequent BundleTool conversion Tasks? Wouldn&#39;t using Gradle for these subsequent operations actually be more suitable?&lt;/p&gt;
&lt;h2 id=&quot;deep-dive-into-agp-%26-bundletool&quot; tabindex=&quot;-1&quot;&gt;Deep Dive into AGP &amp;amp; BundleTool&lt;/h2&gt;
&lt;p&gt;If we open the IDE&#39;s Gradle Task list and search for the &amp;quot;Bundle&amp;quot; keyword, we&#39;ll easily find tasks like &lt;code&gt;makeApkFromBundleForXxxx&lt;/code&gt; etc. Their implementation is the &lt;code&gt;com.android.build.gradle.internal.tasks.BundleToApkTask&lt;/code&gt; class.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20211219_bundle_tool_bundle_to_apk_task.png?imageslim&quot; alt=&quot;Task list and BundleToApkTask screenshot&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When checking the associated usage of this class in the source code, you&#39;ll find that besides registration, there&#39;s no trace of this class being used anywhere else. And the specific role of this task itself is actually just:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Depends on &lt;code&gt;PackageXxxBundle&lt;/code&gt;, waiting for it to produce an unsigned Bundle;&lt;/li&gt;
&lt;li&gt;Inputs the Bundle from the previous step, executes the &lt;code&gt;BuildApksCommand&lt;/code&gt; command from the BundleTool package to produce an .apks package.&lt;/li&gt;
&lt;li&gt;From its input parameters, you can see that the only &lt;strong&gt;configuration&lt;/strong&gt; it actually exposes to users is &lt;code&gt;enableLocalTesting&lt;/code&gt;, while everything else uses the &lt;strong&gt;default values&lt;/strong&gt; of BundleTool&#39;s &lt;code&gt;build-apks&lt;/code&gt; command;&lt;/li&gt;
&lt;li&gt;From its output result (see below), you&#39;ll discover it&#39;s actually just an &amp;quot;&lt;strong&gt;intermediate product&lt;/strong&gt;&amp;quot; (located in the &lt;code&gt;/intermediates&lt;/code&gt; folder), not a final product.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20211219_bundle_tool_tasks_1.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is a bit like &amp;quot;food that&#39;s tasteless but a pity to throw away&amp;quot;: there&#39;s an existing Task but its usage scope is very limited. Why not... wrap our own Gradle Plugin using the BundleTool library?&lt;/p&gt;
&lt;h2 id=&quot;how-to-wrap-bundle-tool-gradle-plugin%3F&quot; tabindex=&quot;-1&quot;&gt;How to Wrap bundle-tool-gradle-plugin?&lt;/h2&gt;
&lt;p&gt;After simple analysis of BundleTool&#39;s several commands, we found their dependency relationships as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20211219_bundle_tool_commands.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Among them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In terms of sequence, &lt;code&gt;build-apks&lt;/code&gt; is a required prerequisite task (solid line) for the other commands, and &lt;code&gt;get-device-spec&lt;/code&gt; is an optional prerequisite task (dashed line) for several commands.&lt;/li&gt;
&lt;li&gt;In terms of interaction, &lt;code&gt;build-apks&lt;/code&gt; and &lt;code&gt;get-size&lt;/code&gt; (italicized parts) are closely related to the build process and don&#39;t require test devices; other commands require test devices.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get-device-spec&lt;/code&gt; exporting json files is a one-time task, we can assume this part is already complete;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thus, our &lt;strong&gt;involved domain&lt;/strong&gt; is also clear: &lt;code&gt;build-apks&lt;/code&gt; and &lt;code&gt;get-size&lt;/code&gt; commands that are directly related to final products. &lt;code&gt;install-apks&lt;/code&gt; and &lt;code&gt;extract-apks&lt;/code&gt; can be completed locally using CLI based on the current device, and in CI or cloud testing platforms there are usually dedicated scripts to handle them in combination with BundleTool.&lt;/p&gt;
&lt;p&gt;Then we consider &lt;strong&gt;how to get and modify the final output Bundle&lt;/strong&gt;. The new Variant API actually already provides convenient methods to modify and get the final Bundle. The entire process can refer to the running screenshot below. You can see that compared to the previous intermediate product mode, Variant API products are all output to the &lt;code&gt;/outputs&lt;/code&gt; folder now. Since we don&#39;t need the intermediate aab, we just need to simply call &lt;code&gt;variants.get(SingleArtifact.BUNDLE)&lt;/code&gt;, pass the obtained .aab file to a custom Task, and then borrow from AGP&#39;s code to wrap various commands:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20211219_bundle_tool_tasks_2.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// A simple Task to verify the idea. The complete plugin implementation is more complex than this. Please refer to the repository link at the end&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; ConsumeBundleFileTask &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DefaultTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@get:InputFiles&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; finalBundleProperty&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RegularFileProperty&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@get:Internal&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; buildToolInfo&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Property&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BuildToolInfo&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@get:Nested&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;lateinit&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; signingConfigData&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SigningConfigDataProvider&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@get:OutputFile&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; apksFileProperty&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RegularFileProperty&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token annotation builtin&quot;&gt;@TaskAction&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;taskAction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; aapt2Path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; buildToolInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BuildToolInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PathId&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AAPT2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.get(SingleArtifact.BUNDLE)&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;[ConsumeBundleFileTask][input]:&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; finalBundleProperty&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;asFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;absolutePath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;[ConsumeBundleFileTask][output]:&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; apksFileProperty&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;asFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;absolutePath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; signingConfigData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; signingConfigData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!!&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; command &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BuildApksCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setBundlePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;finalBundleProperty&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;asFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setOutputFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;apksFileProperty&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;asFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAapt2Command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                Aapt2Command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createFromExecutablePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token function&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;aapt2Path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSigningConfiguration2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                keystoreFile &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; signingConfigData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storeFile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                keystorePassword &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; signingConfigData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storePassword&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                keyAlias &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; signingConfigData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;keyAlias&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                keyPassword &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; signingConfigData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;keyPassword&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setLocalTestingMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Friends unfamiliar with the &lt;strong&gt;new Variant API&lt;/strong&gt; can refer to my sharing this month at GDG community &amp;quot;Extend Android Build Process - Based on New Variant/Artifact APIs&amp;quot; (&lt;a href=&quot;https://www.bilibili.com/video/BV1WP4y1G71h?share_source=copy_web&quot;&gt;replay&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Finally, let&#39;s briefly look at BundleTool&#39;s BuildApksCommand.Builder. This Builder&#39;s setXxx-related &lt;strong&gt;APIs&lt;/strong&gt; have only had two minor changes in the past year, one of which was a new method addition that doesn&#39;t really affect original compatibility - it&#39;s &lt;strong&gt;relatively stable&lt;/strong&gt; compared to AGP.&lt;/p&gt;
&lt;p&gt;At this point, the theoretical construction cost and maintenance cost of the entire plugin are within acceptable range.&lt;/p&gt;
&lt;h2 id=&quot;using-the-plugin&quot; tabindex=&quot;-1&quot;&gt;Using the Plugin&lt;/h2&gt;
&lt;p&gt;The plugin development is no different from several others I&#39;ve written before. Let&#39;s look directly at plugin usage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;0x01. Add the plugin to classpath:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;buildscript &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    repositories &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;mavenCentral&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;classpath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.android.tools.build:gradle:7.0.4&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;classpath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;me.2bab:bundle-tool-plugin:1.1.0&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;0x02. Apply Plugin:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// For your application module&lt;/span&gt;&lt;br /&gt;plugins &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;me.2bab.bundletool&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;0x03. Advanced Configurations&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; me&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;xx2bab&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bundletool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;bundleTool &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// This is a very interesting configuration item - it can enable/disable&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// plugin features by different variant channels. For example, here we disable&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// the debug + GET_SIZE feature combination.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// You can adjust and enable needed features according to your project&#39;s actual buildtypes and flavors.&lt;/span&gt;&lt;br /&gt;    enableByVariant &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; feature &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;debug&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; feature &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; BundleToolFeature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GET_SIZE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Each configuration item corresponds to a `build-apks` command execution&lt;/span&gt;&lt;br /&gt;    buildApks &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;universal&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            buildMode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ApkBuildMode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UNIVERSAL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;pixel4a&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            deviceSpec&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./pixel4a.json&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Each configuration item will calculate the size of all apks output from `buildApks` above in sequence.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// With the current configuration, it will output 2 * 1 = 2 csv files&lt;/span&gt;&lt;br /&gt;    getSize &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;all&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            dimensions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                GetSizeDimension&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SDK&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                GetSizeDimension&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ABI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                GetSizeDimension&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SCREEN_DENSITY&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                GetSizeDimension&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LANGUAGE&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;0x04. Build your App and Enjoy!&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Make sure the Variant in the command is allowed in `enableByVariant`&lt;/span&gt;&lt;br /&gt;./gradlew TransformApksFromBundleForProductionRelease&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, you can find the output results in &lt;code&gt;/app/outputs/bundle/${variantName}/bundletool&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/20211219_bundle_tool_outputs2.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;I hope this small tool can help everyone when integrating the new Android Bundle. The plugin is open-sourced on my Github: &lt;a href=&quot;https://github.com/2BAB/bundle-tool-gradle-plugin&quot;&gt;bundle-tool-gradle-plugin&lt;/a&gt;. For the idea of enabling/disabling plugin features by Variant, please refer to the next Build Guide #12.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Build Guide #10 Android Development Tool Compatibility</title>
    <link href="https://2bab.me/en/blog/2021-08-07-android-dev-tool-composition-on-m1/"/>
    <updated>2021-08-07T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2021-08-07-android-dev-tool-composition-on-m1/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;&amp;quot;Build Guide&amp;quot; is a series of articles exploring Android build-related topics, covering Gradle, Android Gradle Plugin, Kotlin Script, and other tools, as well as their architectural applications. Discovering problems, solving problems, sharing new knowledge, and improving efficiency.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Due to my CPU burning out a few months ago, I was forced to switch to an M1 Mac Mini, so I rebuilt the entire development environment. Taking this opportunity, I want to organize the version matching strategies, compatibility, and performance on M1 chips for several basic tools. The discussions about version matching and compatibility are not limited to the current versions and platforms being used.&lt;/p&gt;
&lt;p&gt;The versions mentioned below are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zulu JDK: 11.0.11&lt;/li&gt;
&lt;li&gt;Kotlin: 1.5.21&lt;/li&gt;
&lt;li&gt;Gradle: 7.1.1&lt;/li&gt;
&lt;li&gt;Android Gradle Plugin (AGP): 7.0 &amp;amp; 7.1&lt;/li&gt;
&lt;li&gt;Android Studio: Arctic Fox 2020.3.1 &amp;amp; BumbleBee 2021.1.1 Canary 6&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;jdk&quot; tabindex=&quot;-1&quot;&gt;JDK&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Starting from AGP 7.0, JDK 11 is the minimum requirement&lt;/strong&gt;. JDK 11 has been an LTS (Long-term Support) version for nearly 3 years (Sep 2018), and has gone through 12 minor versions (11.0.12) of iteration, making it relatively mature now. For comparison, the previous LTS JDK 8 was released in 2014 and has accompanied us through 7 years and 300 minor version iterations. In fact, as Android developers, even if the project is primarily Java, the Language Level generally only targets 1.8 (some features from 9-12 are supported by D8, R8, and Android 11, 12 have incorporated them). Although Android officially says they won&#39;t abandon Java, support for Kotlin is indeed stronger in practice.&lt;/p&gt;
&lt;p&gt;From this perspective, JDK 11 brings us more of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Performance upgrades at the Kotlin Compiler, Gradle, and IDE levels;&lt;/li&gt;
&lt;li&gt;Continued security assurance when JDK 8 stops maintenance (in 3 years);&lt;/li&gt;
&lt;li&gt;Adapting to the new release mechanism - a minor version every six months, an LTS major version every three years, reducing legacy baggage and running faster;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And I believe the JDK upgrade strategy should be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conservative: If you don&#39;t care about new language features, you can decide the JDK version based on the annual AGP upgrade, since IDE generally bundles a JDK, Kotlin compiler, Gradle keeps up with the latest JDK and has good backward compatibility - so based on the barrel principle, just wait for AGP to upgrade first. Currently it looks like AGP may only follow relatively stable LTS versions;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggressive: Test after Gradle supports it;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On M1 Mac, since Oracle doesn&#39;t have an ARM64 version yet, the current mainstream approach is to install &lt;code&gt;Zulu JDK11&lt;/code&gt; maintained by &lt;a href=&quot;https://www.azul.com/downloads/?package=jdk&quot;&gt;Azul&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/20210805160844.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Note that the commonly used JDK management tool &lt;code&gt;SDKMAN!&lt;/code&gt; in my testing still runs under &lt;code&gt;Rosetta 2&lt;/code&gt; translation. This causes even if you install &lt;code&gt;Zulu JDK11&lt;/code&gt;, starting through &lt;code&gt;SDKMAN!&lt;/code&gt; scripts will still show Gradle &lt;code&gt;java&lt;/code&gt; processes running under Intel ABI. So currently it&#39;s recommended to download and install from the official website, and consider switching after supporting tools are ready.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/20210805162529.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;kotlin&quot; tabindex=&quot;-1&quot;&gt;Kotlin&lt;/h2&gt;
&lt;p&gt;Kotlin version matching has relatively few restrictions. Generally I consider three points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Are there particularly attractive new features, such as newly released stable versions of Coroutine, Flow, or better support for new versions of Kotlin Multiplatform Mobile;&lt;/li&gt;
&lt;li&gt;Whether to use the first version of a major iteration, such as observing the just-released &lt;code&gt;1.4.0&lt;/code&gt;, &lt;code&gt;1.5.0&lt;/code&gt; - this actually applies broadly to various Libraries;&lt;/li&gt;
&lt;li&gt;The Kotlin version currently bundled/tested by Gradle, e.g., the latest 7.1.1 stable still uses Kotlin &lt;code&gt;1.4.31&lt;/code&gt; (7.2 RC jumps to &lt;code&gt;1.5.21&lt;/code&gt;);&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regarding the last point, if the Kotlin version used is inconsistent with what Gradle bundles, you&#39;ll see this Warning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;w: Runtime JAR files in the classpath should have the same version. These files were found in the classpath:
...
/Users/2bab/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.4.31/84ce8e85f6e84270b2b501d44e9f0ba6ff64fa71/kotlin-stdlib-jdk7-1.4.31.jar (version 1.4)
/Users/2bab/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.5.21/2f537cad7e9eeb9da73738c8812e1e4cf9b62e4e/kotlin-stdlib-1.5.21.jar (version 1.5)
/Users/2bab/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.5.21/cc8bf3586fd2ebcf234058b9440bb406e62dfacb/kotlin-stdlib-common-1.5.21.jar (version 1.5)
w: Consider providing an explicit dependency on kotlin-reflect 1.5 to prevent strange errors
w: Some runtime JAR files in the classpath have an incompatible version. Consider removing them from the&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&#39;s look at a simple example of how the problem occurs:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;plugins &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.android.application&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// No version specified, using Gradle&#39;s bundled version,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Gradle 7.1.1 corresponds to Kotlin 1.4.31&#39;s various libraries and compilation tools&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;kotlin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;android&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// But here we use the latest 1.5.21 Kotlin, causing the above problem&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;org.jetbrains.kotlin:kotlin-stdlib:1.5.21&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The solution is also simple:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;plugins &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.android.application&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Manually specify the version&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;kotlin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;android&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; version &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;1.5.21&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The official Kotlin &lt;a href=&quot;https://kotlinlang.org/docs/gradle.html&quot;&gt;documentation&lt;/a&gt; directly demonstrates the writing with version number. &lt;strong&gt;However, this version is Kotlin tested based on Gradle, but Gradle itself hasn&#39;t &lt;a href=&quot;https://docs.gradle.org/7.1.1/userguide/compatibility.html&quot;&gt;tested&lt;/a&gt; and packaged it yet (see the image in the Gradle section). If problems occur, they may be difficult to solve&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;So my recommended upgrade strategy is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conservative: Wait until Gradle upgrades to upgrade. For example, from 1.5.0 to 1.5.21 was only two months apart, and Gradle followed up;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggressive: Upgrade when there are practical new features or the first patch version after a major iteration.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;gradle&quot; tabindex=&quot;-1&quot;&gt;Gradle&lt;/h2&gt;
&lt;p&gt;Previously I mentioned that Kotlin&#39;s official documentation explains various compatibility situations with Gradle. Conversely, Gradle also has a &lt;a href=&quot;https://docs.gradle.org/7.1.1/userguide/compatibility.html&quot;&gt;support document&lt;/a&gt; indicating support for various languages and platforms including Java, Kotlin, Android, etc.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/20210805214442.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Like the Java version support and Kotlin version support mentioned earlier, you can see everything at a glance here. Except that Kotlin support lags by a month or two, compatibility with the latest versions of other tools is not a problem. Gradle&#39;s own backward compatibility I think is quite good - I basically upgrade every version. But the upper-level AGP DSL, especially older versions, often has major changes (fortunately it&#39;s much better after 7.0).&lt;/p&gt;
&lt;p&gt;So my recommended upgrade strategy is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conservative: Upgrade according to AGP&#39;s &lt;a href=&quot;https://developer.android.com/studio/releases/gradle-plugin&quot;&gt;documentation&lt;/a&gt; at minimum version (see image below). For example, AGP 4.2.0+ corresponds to Gradle 6.7.1+;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggressive: Upgrade every patch version (x.y.1/2/3) or every version (Gradle doesn&#39;t have a -betaX version habit, usually just Nightly and RC).&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/20210806211434.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Additionally, Gradle 7.0 and later natively supports M1, and my personal experience has been quite good.&lt;/p&gt;
&lt;h2 id=&quot;android-gradle-plugin&quot; tabindex=&quot;-1&quot;&gt;Android Gradle Plugin&lt;/h2&gt;
&lt;p&gt;We&#39;ve basically covered AGP version matching restrictions earlier. Taking 7.0 as an example, let&#39;s look at the official Release Note compatibility description:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/20210806214019.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One additional point: Starting from AGP 7.0, its version will &lt;a href=&quot;https://android-developers.googleblog.com/2020/12/announcing-android-gradle-plugin.html&quot;&gt;sync with Gradle&#39;s major version&lt;/a&gt;, strictly following the Semantic Versioning system (previously it synced with AS&#39;s version). That is, AGP 7.x will adapt to Gradle 7.x versions. However, AGP&#39;s release time is still together with AS, and currently it looks like its alpha/beta numbers follow AS, so actually the three have formed some kind of tacit synchronization mechanism.&lt;/p&gt;
&lt;p&gt;So my recommended upgrade strategy is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conservative: Upgrade with AS stable releases (or appropriately skip the first major version iteration);&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggressive: Upgrade every version, or start upgrading from alpha/beta, such as for Gradle plugin adaptation.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;android-studio&quot; tabindex=&quot;-1&quot;&gt;Android Studio&lt;/h2&gt;
&lt;p&gt;AS basically has no matching restrictions. As long as you&#39;re using a previous stable version of AGP, AS can be backward compatible. My recommended upgrade strategy is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conservative: Appropriately skip the first major version iteration;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggressive: Upgrade every version, or start upgrading from alpha/beta, such as for IDE plugin adaptation or needs for new features like Compose, debugging tools, etc.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, since AS is based on secondary development of IDEA Community Edition, overall stability and speed of new feature support are not as good as IDEA Ultimate. For example, Gradle&#39;s nested Composite Build is currently not in AS&#39;s supported scope, see this &lt;a href=&quot;https://issuetracker.google.com/issues/189366120&quot;&gt;issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, starting from Arctic Fox 2020.3.1, AS natively supports M1, but for a smoother experience, I think BumbleBee 2021.1.1 Canary is better.&lt;/p&gt;
&lt;h2 id=&quot;idea&quot; tabindex=&quot;-1&quot;&gt;IDEA&lt;/h2&gt;
&lt;p&gt;IDEA&#39;s main matching restrictions come from Android Plugin (Android IDE plugin) version adaptation. Generally speaking, after a new stable version of AS is released, the next stable version of IDEA will come with the new plugin version, thus supporting Android development including AGP.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/20210807154410.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Occasionally there are longer waits. For example, this year AS&amp;amp;AGP 4.2 was released in April, but it wasn&#39;t until July when IDEA 2021.2 was &lt;a href=&quot;https://www.jetbrains.com/idea/whatsnew/#Other&quot;&gt;released&lt;/a&gt; that the Android Plugin was updated. The official explanation was that Google released AGP 4.2 source code late, missing the 2021.1 version.&lt;/p&gt;
&lt;p&gt;My recommended upgrade strategy is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conservative: Only upgrade stable versions;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggressive: Start upgrading from EAP or RC, such as for better Kotlin support, earlier AGP support, and M1 platform optimizations.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, 2021.2 is also the version that made me feel there&#39;s no longer any lag on M1.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;I personally use the M1 platform + adapt some &lt;a href=&quot;https://github.com/2BAB&quot;&gt;Gradle Plugins&lt;/a&gt;, so I often use beta or even alpha AGP (as a Runtime library), and developing with the latest IDEA Ultimate is quite smooth.&lt;/p&gt;
&lt;p&gt;For company projects, at this stage on x86 platforms I think you can use the following configuration, adjusting corresponding tool versions for ARM M1 based on the above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JDK 11 (will have to upgrade sooner or later since AGP upgraded)&lt;/li&gt;
&lt;li&gt;Gradle 7.1.1 (can upgrade after 7.2 supports 1.5.21)&lt;/li&gt;
&lt;li&gt;Kotlin 1.4.31&lt;/li&gt;
&lt;li&gt;AGP 4.2.2 (7.0 stabilized the new Variant API, soon 7.1 will also stabilize the new DSL, can wait and see if you don&#39;t need Compose)&lt;/li&gt;
&lt;li&gt;AS 4.2.2 (can wait and see if you don&#39;t need Compose)&lt;/li&gt;
&lt;li&gt;IDEA 2021.2&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Moving Forward: Google I/O 21 Android Gradle Plugin Update Summary</title>
    <link href="https://2bab.me/en/blog/2021-06-17-google-io-21-agp-recap/"/>
    <updated>2021-06-17T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2021-06-17-google-io-21-agp-recap/</id>
    <content type="html">&lt;p&gt;It&#39;s been nearly a month since Google I/O 2021. In the past few days, I&#39;ve been reviewing the Android Gradle Plugin (AGP) content, mainly focusing on the &amp;quot;What&#39;s new in Android Gradle plugin&amp;quot; session. However, since there was no Google I/O in 2020 and all offline events were suspended due to the pandemic, this 11-minute session contains an enormous amount of information - it&#39;s essentially a condensed highlight of the past two years&#39; updates (I watched it three times). Therefore, in this article, I&#39;ll include many additional reference materials, digging into talks/posts/repos that you might have missed in the past year or two. The overall structure still follows the session&#39;s agenda.&lt;/p&gt;
&lt;h2 id=&quot;performance-improvements&quot; tabindex=&quot;-1&quot;&gt;Performance Improvements&lt;/h2&gt;
&lt;h3 id=&quot;configuration-cache&quot; tabindex=&quot;-1&quot;&gt;Configuration Cache&lt;/h3&gt;
&lt;p&gt;Gradle&#39;s lifecycle is divided into three major parts: Initialization Phase, Configuration Phase, and Execution Phase. The task execution part can already be well cached and reused if handled properly - reusing existing caches is a crucial part of speeding up compilation. If this mechanism is applied to other phases, it should naturally bring some benefits as well. The next most time-consuming phase after execution is usually the configuration phase, and this year AGP brings us Gradle &lt;a href=&quot;https://docs.gradle.org/current/userguide/configuration_cache.html#config_cache:requirements&quot;&gt;Configuration Cache&lt;/a&gt; support, a new feature that has been incubating since &lt;a href=&quot;https://blog.gradle.org/introducing-configuration-caching&quot;&gt;Gradle 6.6&lt;/a&gt;. It enables the main output of the configuration phase - the Task Graph - to be reused. In the example project, this optimization can eliminate about 8 seconds of unnecessary waiting (if Gradle script configuration hasn&#39;t changed).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/20210617155730.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To experience this optimization, simply add &lt;code&gt;--configuration-cache&lt;/code&gt; when executing Gradle commands, such as &lt;code&gt;./gradlew --configuration-cache help&lt;/code&gt;. Since Configuration Cache is not yet fully stable, if you want to keep it enabled (including optimization during IDE Sync), you need to use the following properties:&lt;/p&gt;
&lt;pre class=&quot;language-properties&quot;&gt;&lt;code class=&quot;language-properties&quot;&gt;&lt;span class=&quot;token key attr-name&quot;&gt;org.gradle.unsafe.configuration-cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On first use, you&#39;ll see a prompt about calculating the Task Graph:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Calculating task graph as no configuration cache is available for tasks: :test-app:assembleDebug&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On success, it will prompt at the end of the Build:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Configuration cache entry stored.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then the Cache can be reused by the next build (if there are no build script modifications):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Reusing configuration cache.&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;51 actionable tasks: 2 executed, 49 up-to-date&lt;/p&gt;
&lt;p&gt;Configuration cache entry reused.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a plugin user, if you find that commonly used plugins are unsupported, you can first search whether the same issue has already appeared. For example, here&#39;s an issue that occurred with Kotlin 1.4.32 plugin and Gradle 7.0:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-agp-config-cache-kotlin-plugin.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In this YouTrack &lt;a href=&quot;https://youtrack.jetbrains.com/issue/KT-43605&quot;&gt;issue&lt;/a&gt;, we can see that upgrading the Kotlin plugin to version 1.5.0 or above solves the problem.&lt;/p&gt;
&lt;p&gt;In fact, the core plugins of AGP/Kotlin/Gradle (mainly the Tasks behind them) already support Configuration Cache in recent versions. You can learn more through these documents/issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gradle/gradle/issues/13490&quot;&gt;Help community Gradle plugins adopt the configuration cache #13490 - Gradle Github Issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/studio/releases/gradle-plugin#4.2-gradle-properties&quot;&gt;Gradle Properties Change - Android Gradle Plugin 4.2 Release Note&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/docs/gradle.html#gradle-configuration-cache-support&quot;&gt;Gradle Configuration Cache Support - Kotlin Doc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a plugin developer, you also need to be concerned with Configuration Cache adaptation work. The key point is: Task parameters and internal implementations need to avoid directly passing in/using Gradle&#39;s Context classes and some non-serializable classes. Taking my maintained &lt;a href=&quot;https://github.com/2BAB/Seal&quot;&gt;Seal&lt;/a&gt; plugin as an example, it&#39;s a small plugin that solves &lt;code&gt;AndroidManifest.xml&lt;/code&gt; conflicts. Running &lt;code&gt;/gradlew --configuration-cache :test-app:assembleDebug&lt;/code&gt; reveals two issues to fix:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-agp-config-cache-seal-plugin.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Through the Configuration Cache HTML Report output at the end of the build, we can view the detailed stack trace:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-agp-config-cache-error-html.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For this error, we simply need to change &lt;code&gt;project.logger&lt;/code&gt; to a &lt;code&gt;this.logger&lt;/code&gt; reference:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-agp-config-cache-git-change.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For more complex rules and use cases, refer to Gradle&#39;s documentation and AGP&#39;s journey to Configuration Cache compatibility (fixing over 400 issues):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/configuration_cache.html#config_cache:requirements&quot;&gt;Configuration Cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/androiddevelopers/configuration-caching-deep-dive-bcb304698070&quot;&gt;Configuration caching deep dive - Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, there&#39;s an official Gradle-maintained &lt;a href=&quot;https://github.com/gradle/android-cache-fix-gradle-plugin&quot;&gt;android-cache-fix-gradle-plugin&lt;/a&gt;. For some special AGP build cache and configuration cache issues, you can check here - it might be exactly what your project encountered.&lt;/p&gt;
&lt;h3 id=&quot;non-transitive-r-classes&quot; tabindex=&quot;-1&quot;&gt;Non-transitive R-classes&lt;/h3&gt;
&lt;p&gt;In fact, R file features like this have been developing for many years. You can refer to this &lt;a href=&quot;https://www.mobileit.cz/Blog/Pages/r-class.aspx&quot;&gt;article&lt;/a&gt; organized in chronological order. However, the latest &lt;code&gt;nonTransitiveAppRClass&lt;/code&gt; feature requires AGP 7.0 or above. Currently there&#39;s limited documentation, with partial mention in the Android Studio Arctic Fox release notes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Non-transitive R class refactoring: Using non-transitive R classes in Android Gradle Plugin can bring faster build speeds for apps with multiple modules. It prevents resource duplication by ensuring that each module only contains references to its own resources without pulling references from dependencies. You can use this feature through Refactor &amp;gt; Migrate to Non-transitive R Classes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Enable it as follows:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-agp-non-transitive-r-as-refactor2.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This operation helps you automatically add two lines of feature-enabling code to &lt;code&gt;gradle.properties&lt;/code&gt; and rebuilds the project:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-agp-non-transitive-r-properties.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;cacheable-lint-task&quot; tabindex=&quot;-1&quot;&gt;Cacheable Lint Task&lt;/h3&gt;
&lt;p&gt;Lint has always been a time-consuming task. After AGP 7.0 (originally planned for 3.5, see this &lt;a href=&quot;https://docs.gradle.org/current/userguide/caching_android_projects.html#lint&quot;&gt;document&lt;/a&gt;), it finally becomes a cacheable Task.&lt;/p&gt;
&lt;h3 id=&quot;others&quot; tabindex=&quot;-1&quot;&gt;Others&lt;/h3&gt;
&lt;p&gt;Additionally, AS + AGP have had some improvement points since 4.x:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gradle Kotlin DSL experience and performance improvements - you can see the &lt;a href=&quot;https://github.com/google/iosched&quot;&gt;Google I/O Android App&lt;/a&gt; project has been completely converted to &lt;code&gt;*.gradle.kts&lt;/code&gt; scripts;&lt;/li&gt;
&lt;li&gt;AAPT2 performance improvements;&lt;/li&gt;
&lt;li&gt;Performance improvements from JDK 11;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find this information in the AGP/AS Release Notes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-new-as-bumblebee.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;new-dsl&quot; tabindex=&quot;-1&quot;&gt;New DSL&lt;/h2&gt;
&lt;h3 id=&quot;dsl-doc-migration-to-android.com&quot; tabindex=&quot;-1&quot;&gt;DSL Doc Migration to &lt;a href=&quot;http://android.com/&quot;&gt;android.com&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The old AGP DSL &lt;a href=&quot;https://google.github.io/android-gradle-dsl/&quot;&gt;documentation&lt;/a&gt; hasn&#39;t been updated since 3.4. The new documentation has migrated to &lt;a href=&quot;https://developer.android.com/reference/tools/gradle-api&quot;&gt;android.com&lt;/a&gt;, making it more unified. You can still view by version:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Current Release: i.e., stable version 4.2;&lt;/li&gt;
&lt;li&gt;Preview Releases: i.e., beta 7.0 and alpha 7.1 test versions;&lt;/li&gt;
&lt;li&gt;Past Releases: all previous old versions, but due to transitions/switches in between, documentation for versions 3.5 -&amp;gt; 4.0 is actually missing;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-dsl-doc.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This change is also reflected in &lt;a href=&quot;https://android.googlesource.com/platform/manifest/+refs&quot;&gt;google source&lt;/a&gt; tags. For AGP source code, &lt;code&gt;gradle-x.y.z&lt;/code&gt; tags no longer exist after 3.4. Currently you can use &lt;code&gt;studio-x.y.z&lt;/code&gt; such as &lt;code&gt;studio-4.2.0&lt;/code&gt; to reverse-locate AGP versions.&lt;/p&gt;
&lt;h3 id=&quot;agp-upgrade-assistant-provided-by-android-studio&quot; tabindex=&quot;-1&quot;&gt;AGP Upgrade Assistant Provided by Android Studio&lt;/h3&gt;
&lt;p&gt;To help developers upgrade AGP conveniently and smoothly, AGP in cooperation with AS has launched the upgrade assistant feature. This new feature has iterated through several versions and is currently very useful for upgrading Gradle Groovy DSL scripts. When you see an upgrade prompt (usually when first opening a project):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-agp-upgrade-assistant.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Clicking &lt;code&gt;Upgrade&lt;/code&gt; will also have a preview function (screenshot from session slide):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-agp-upgrade-assistant-2.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;However, support for Gradle Kotlin DSL still needs to be completed. For example, migration of deprecated DSL like basic &lt;code&gt;compileSdkVersion&lt;/code&gt; is not yet supported:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-agp-upgrade-assistant-3.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Of course, complex object references also cannot be directly modified for you, such as &lt;code&gt;classpath(Deps.agp)&lt;/code&gt; - this is beyond the scope of what the tool can do. You can treat it as an auxiliary tool similar to &lt;code&gt;Java&lt;/code&gt; to &lt;code&gt;Kotlin&lt;/code&gt; conversion - first use it to quickly upgrade and organize basic DSL, then manually modify the small portions that have errors by referring to the DSL documentation.&lt;/p&gt;
&lt;h2 id=&quot;new-variant-api&quot; tabindex=&quot;-1&quot;&gt;New Variant API&lt;/h2&gt;
&lt;p&gt;Variant API is the most important update related to Android and plugin development in the past two years. For friends who haven&#39;t developed cooperative plugins for AGP before, you can understand &amp;quot;what is Variant&amp;quot; through the image below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-variant-api-definition-2.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The Variant API update can be summarized as: To make cooperative plugin developers depend on more stable APIs, the original &lt;code&gt;com.android.tools.build:gradle&lt;/code&gt; package has been split into &lt;code&gt;gradle&lt;/code&gt; and &lt;code&gt;gradle-api&lt;/code&gt; packages, achieving separation of interface and implementation. From a practical perspective, we can focus on two parts: Variant traversal entry changes and simplification of some custom Tasks.&lt;/p&gt;
&lt;h3 id=&quot;variant-traversal-entry-changes&quot; tabindex=&quot;-1&quot;&gt;Variant Traversal Entry Changes&lt;/h3&gt;
&lt;p&gt;Most AGP ecosystem cooperative plugins need to register Variant-aware Tasks, i.e., traverse Variants to register corresponding custom Tasks, such as the Seal plugin&#39;s &lt;code&gt;postUpdateDebugManifest&lt;/code&gt; and &lt;code&gt;postUpdateReleaseManifest&lt;/code&gt; mentioned above. You must have seen code like this (Groovy):&lt;/p&gt;
&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; android &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extensions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;android&lt;br /&gt;android&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;applicationVariants&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;all &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; variantName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;capitalize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;createTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;project&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; variantName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or the Kotlin version:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; androidExtension &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extensions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AppExtension&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!!&lt;/span&gt;&lt;br /&gt;androidExtension&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;applicationVariants&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; variantName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;capitalize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;createTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;project&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; variantName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For plugins applicable to libraries, you need &lt;code&gt;LibraryExtension&lt;/code&gt; and &lt;code&gt;libraryVariants&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This type of API has now changed to new API calls within &lt;code&gt;gradle-api&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; androidExtension &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extensions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getByType&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ApplicationAndroidComponentsExtension&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;androidExtension&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onVariants&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Variant obtained here is &lt;a href=&quot;https://developer.android.com/reference/tools/gradle-api/7.1/com/android/build/api/variant/ApplicationVariant&quot;&gt;com.android.build.api.variant.ApplicationVariant&lt;/a&gt;, and the Extension comes from &lt;a href=&quot;https://developer.android.com/reference/tools/gradle-api/4.2/com/android/build/api/extension/ApplicationAndroidComponentsExtension&quot;&gt;com.android.build.api.extension.ApplicationAndroidComponentsExtension&lt;/a&gt;. Another interface you might use is &lt;code&gt;beforeVariants(...)&lt;/code&gt;, used to control Variant building, such as globally modifying some Variant properties. From this Snippet we might not see the specific changes in Variant, but behind these changes are standardized Variant state transitions, public APIs, etc.&lt;/p&gt;
&lt;h3 id=&quot;simplification-of-some-custom-tasks&quot; tabindex=&quot;-1&quot;&gt;Simplification of Some Custom Tasks&lt;/h3&gt;
&lt;p&gt;This type of simplification refers to simplification of Task insertion points and Task parameter acquisition (injection). APIs providing such features are also called Artifact APIs. In the classic pattern: for insertion points, we generally manually find the Task&#39;s dependency relationships and use Gradle APIs to reorganize dependencies (you might even need to customize some new lifecycle anchor Tasks to assist); for Task parameters, various tricks are employed to find the data we need from existing Task parameters/intermediate products/private objects, then inject them into custom Tasks. Now Artifact APIs standardize a set of operations, enabling us to easily interact with existing data and intermediate products. From a practical perspective, we can divide into two patterns:&lt;/p&gt;
&lt;p&gt;Complex Transform/Append/Create operations: one-stop service for inserting Tasks at specific nodes and Task parameter injection, generally suitable for needing to define a specific insertion point;&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;androidComponents &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; gitVersionProvider &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;register&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;GitVersionTask&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gitVersionProvider&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        gitVersionOutputFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buildDir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;intermediates/gitVersionProvider/output&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        outputs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;upToDateWhen&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    onVariants &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; manifestProducer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;register&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ManifestProducerTask&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;ManifestProducer&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            gitInfoFile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gitVersionProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;GitVersionTask&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;gitVersionOutputFile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;artifacts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;manifestProducer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wiredWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ManifestProducerTask&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;outputManifest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toCreate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;SingleArtifact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MERGED_MANIFEST&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pure Get: actively obtaining intermediates, generally suitable for relatively independent Tasks without strict insertion position requirements (but there are implicit Task dependencies through Provider passing), without replacement operations needed:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;androidComponents &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    onVariants &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;        project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;register&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;DisplayApksTask&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;DisplayApks&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            apkFolder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;artifacts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;SingleArtifact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;APK&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            builtArtifactsLoader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;artifacts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBuiltArtifactsLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;more&quot; tabindex=&quot;-1&quot;&gt;More&lt;/h3&gt;
&lt;p&gt;From a practical perspective, the new Variant interface and Extension interface have fewer public APIs than before, but they&#39;re more standardized. Artifacts as a supplement to manually obtaining Task input/output currently has relatively few public APIs. I hope plugin developers will submit more issues to the AGP team when encountering reasonable APIs that should be public but aren&#39;t yet :).&lt;/p&gt;
&lt;p&gt;Additionally, due to space limitations, I cannot introduce all Variant API updates here, including the introduction of new &lt;code&gt;Provider&amp;lt;T&amp;gt;&lt;/code&gt; API (Lazy Configuration), Variant state transitions, more types of Artifacts API usage, how to learn from its design to solve those interfaces that haven&#39;t been encapsulated/exposed yet, etc. You can get more inspiration from these materials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=OTANozHzgPc&quot;&gt;From Gradle properties to AGP APIs - Android Dev Summit &#39;19&lt;/a&gt;: Explains the cornerstone of Variant API — &lt;code&gt;Provider&amp;lt;T&amp;gt;&lt;/code&gt; API and its multiple derived subclasses, Variant state transitions and multiple usage patterns of its API;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/androiddevelopers/new-apis-in-the-android-gradle-plugin-f5325742e614&quot;&gt;New APIs in the Android Gradle Plugin - Android Developers Blog&lt;/a&gt;: Introduces the origin of Variant API design ideas and use of new APIs;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/android/gradle-recipes/tree/agp-7.1&quot;&gt;android/gradle-recipes&lt;/a&gt;: Provides Groovy/Kotlin DSL sample code for common Variant API usage;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/studio/releases/gradle-plugin-roadmap&quot;&gt;Android Gradle Plugin DSL/API migration timeline&lt;/a&gt;: Milestones for New DSL and Variant API over the next three years;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/lazy_configuration.html&quot;&gt;Lazy Configuration&lt;/a&gt;: Task configuration lazy acquisition, &lt;code&gt;Provider&amp;lt;T&amp;gt;&lt;/code&gt; and its various subclasses, Task implicit dependencies, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;new-asm-api&quot; tabindex=&quot;-1&quot;&gt;New ASM API&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/io-2021-asm-api.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ASM API is a replacement for the previous Transform API, aimed at providing a lower-cost insertion point between Class -&amp;gt; Dex for modifying bytecode. It no longer has the flexibility of the previous Transform API - for example, it currently appears to be bound to the ASM bytecode tool and doesn&#39;t support Javassist or Aspect. However, at the same time, it has better performance, lower usage cost (referring to implementing the transform itself, because ASM is actually a lower-level API compared to Javassist and Aspect - more flexible but with higher learning cost), and easier adaptation to new Gradle features. It&#39;s currently just starting incubation. From the API Doc perspective, developers are not yet recommended to use it to build production environment plugins.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; ExamplePlugin &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Plugin&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Project&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;project&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Project&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; androidComponents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extensions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AndroidComponentsExtension&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        androidComponents&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onVariants&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; variant &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;br /&gt;            variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transformClassesWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ExampleClassVisitorFactory&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                                 InstrumentationScope&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ALL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;writeToStdout&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            variant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAsmFramesComputationMode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;FramesComputationMode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;COPY_FRAMES&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; ExampleParams &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; InstrumentationParameters &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token annotation builtin&quot;&gt;@get:Input&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; writeToStdout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Property&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Boolean&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; ExampleClassVisitorFactory &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        AsmClassVisitorFactory&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ExampleParams&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createClassVisitor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            classContext&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ClassContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            nextClassVisitor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ClassVisitor&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ClassVisitor &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parameters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;writeToStdout&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token function&quot;&gt;TraceClassVisitor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nextClassVisitor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PrintWriter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token function&quot;&gt;TraceClassVisitor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nextClassVisitor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PrintWriter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;trace_out&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isInstrumentable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;classData&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ClassData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Boolean &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; classData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;className&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.example&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The APIs used in the code above can be referenced in these documents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/tools/gradle-api/7.1/com/android/build/api/component/Component.html#transformClassesWith(java.lang.Class,%20com.android.build.api.instrumentation.InstrumentationScope,%20kotlin.Function1)&quot;&gt;Component#transformClassesWith(...)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/tools/gradle-api/7.1/com/android/build/api/instrumentation/InstrumentationParameters&quot;&gt;InstrumentationParameters&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For friends unfamiliar with classic Transform, you can check out several well-known Transform library encapsulations (coincidentally all from Chinese companies&#39; open-source projects):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bytedance/ByteX&quot;&gt;ByteX&lt;/a&gt; (active)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/didi/booster&quot;&gt;Booster&lt;/a&gt; (active, partially used)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/eleme/lancet&quot;&gt;Lancet&lt;/a&gt; (inactive)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;From a developer&#39;s perspective, the Android Tools team is paying more attention to Engineering Experience in AGP &amp;amp; AS. While solving many legacy issues, this session also revealed long-term plans for building the AGP ecosystem. I hope to see these things truly accepted by more Android developers next year, and I&#39;ll definitely write another summary and outlook for 2022.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Pitfalls of MavenCentral Publishing Integration</title>
    <link href="https://2bab.me/en/blog/2021-05-09-trap-of-maven-central-publish/"/>
    <updated>2021-05-09T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2021-05-09-trap-of-maven-central-publish/</id>
    <content type="html">&lt;p&gt;Due to &lt;a href=&quot;https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/&quot;&gt;JCenter shutting down&lt;/a&gt;, I recently migrated the publishing process of several active open-source projects to &lt;a href=&quot;https://search.maven.org/&quot;&gt;MavenCentral&lt;/a&gt;. The two reference articles below already explain the details of each step quite well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/kotlin/how-to-build-and-publish-a-kotlin-multiplatform-library-going-public-4a8k&quot;&gt;Publishing your Kotlin Multiplatform library to Maven Central&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://proandroiddev.com/publishing-android-libraries-to-mavencentral-in-2021-8ac9975c3e52&quot;&gt;Publishing Android libraries to MavenCentral in 2021&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, I still encountered some pitfalls during this process, as well as some baffling operations, which prompted me to write this article to share.&lt;/p&gt;
&lt;h2 id=&quot;don&#39;t-apply-for-a-single-artifact&quot; tabindex=&quot;-1&quot;&gt;Don&#39;t Apply for a Single Artifact&lt;/h2&gt;
&lt;p&gt;When applying for an OSSRH Ticket, what we&#39;re actually applying for is a &lt;strong&gt;Group ID&lt;/strong&gt;. The key parameter is the Group Id; the title doesn&#39;t even need to mention the specific Artifact.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/20210509204352.png?imageslim&quot; alt=&quot;group-id&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Generally, the Group ID is the reversed domain name. You&#39;ll be asked to verify domain ownership, Github repository ownership, JCenter Group ownership, etc. Just follow the corresponding reply instructions. Once approved, all future new package publishing won&#39;t require another application. For example: if I apply for the &lt;code&gt;me.2bab&lt;/code&gt; group, then all future &lt;code&gt;me.2bab.*&lt;/code&gt; publishing will be supported.&lt;/p&gt;
&lt;h2 id=&quot;implicit-configuration-of-signing-plugin&quot; tabindex=&quot;-1&quot;&gt;Implicit Configuration of Signing Plugin&lt;/h2&gt;
&lt;p&gt;To verify the legitimacy of uploads, we sign the packages to be uploaded using GPG, using Gradle&#39;s official &lt;a href=&quot;https://docs.gradle.org/current/userguide/signing_plugin.html&quot;&gt;The Signing Plugin&lt;/a&gt;. When I first integrated it, I followed the steps in the two tutorials above and always felt something was off: I didn&#39;t see myself passing the key information needed by the signing plugin into the plugin.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// The most basic DSL configuration for the plugin is just this one line&lt;/span&gt;&lt;br /&gt;signing &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;sign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;publishing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;publications&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After briefly browsing the documentation, you&#39;ll discover that it actually defines some Keys by convention, and the plugin configuration reads directly from the Project&#39;s Properties.&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// So you can see the reference tutorial writes it like this&lt;/span&gt;&lt;br /&gt;ext&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;signing.keyId&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;ext&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;signing.password&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;ext&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;signing.secretKeyRingFile&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this convention that I never knew was possible, referring to &lt;a href=&quot;https://docs.gradle.org/current/userguide/build_environment.html#sec:project_properties&quot;&gt;Build Environment&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Using the following setup, you can pass the secret key (in ascii-
// armored format) and the password using the
// ORG_GRADLE_PROJECT_signingKey and ORG_GRADLE_PROJECT_signingPassword
// environment variables, respectively:
signing {
    val signingKey: String? by project
    val signingPassword: String? by project
    useInMemoryPgpKeys(signingKey, signingPassword)
    sign(tasks[&amp;quot;stuffZip&amp;quot;])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I really dislike this overly &amp;quot;implicit&amp;quot; convention - you can&#39;t know what you&#39;ve actually written without carefully reading the documentation. Fortunately, there&#39;s also an explicit configuration method:&lt;/p&gt;
&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;signing &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; signingKeyId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; project &lt;span class=&quot;token comment&quot;&gt;// Where to put it is optional, not necessarily using Project Properties&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; signingKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; project&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; signingPassword&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; project&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;useInMemoryPgpKeys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;signingKeyId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; signingKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; signingPassword&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// This line is the key&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;sign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;stuffZip&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar approaches are used in some experimental configs of Android Gradle Plugin, but due to their widespread existence and usage, perhaps people are too tired to complain (though most switches can still be configured directly from the DSL). If you don&#39;t see the problem here, consider this scenario:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Obviously, DSL can provide &lt;strong&gt;constrained configuration&lt;/strong&gt;. With excellent DSL, you can understand what APIs are available and how to interact just through &lt;strong&gt;IDE completion&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;If all plugins use such implicit configuration, losing the advantages of DSL, it&#39;s no different from writing a JSON configuration - &lt;strong&gt;too loose, error-prone, hard to get started&lt;/strong&gt;. You might not know which configuration file corresponds to which module, whether this Key is correct, etc.;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next time I update the plugin, I plan to change to &lt;code&gt;useInMemoryPgpKeys(...)&lt;/code&gt;, otherwise after a year I&#39;ll forget this pitfall, or anyone taking over your project who doesn&#39;t understand the Signing plugin will be confused again.&lt;/p&gt;
&lt;h2 id=&quot;signing-plugin-key-path-specification&quot; tabindex=&quot;-1&quot;&gt;Signing Plugin Key Path Specification&lt;/h2&gt;
&lt;p&gt;If you use the &lt;code&gt;signing.secretKeyRingFile&lt;/code&gt; configuration, you need to consider different configurations for local and CI environments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local: &lt;code&gt;../local/secret.gpg&lt;/code&gt;, recommended to place in the project root directory or create a &lt;code&gt;local&lt;/code&gt; folder and add the entire folder to gitignore. The reason is that one machine might use more than one secret.gpg; keeping the key with the project makes it easier to find, and setup is also convenient for other collaborators;&lt;/li&gt;
&lt;li&gt;CI: &lt;code&gt;/secret.gpg&lt;/code&gt;, placed directly in the virtual environment root directory, convenient for coordinating with the RingFile generation script;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;batch-upload-%2B-control-panel-operations&quot; tabindex=&quot;-1&quot;&gt;Batch Upload + Control Panel Operations&lt;/h2&gt;
&lt;p&gt;I recently saw someone&#39;s MavenCentral publishing tutorial on Juejin mentioning not to upload multiple packages and then Close together. In fact, this is supported and recommended - packages with the same Group ID will be placed in one staging repo, and can then be closed &amp;amp; released together. If you&#39;ve referenced plugins that automatically handle the close &amp;amp; release process, aggregate upload (batch upload) can actually improve the success rate of subsequent operations (SonaType&#39;s API and webpage are not very stable). For example, this &lt;a href=&quot;https://github.com/2BAB/Polyfill&quot;&gt;project&lt;/a&gt; of mine has six modules and actually uses the Batch Upload strategy.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Build Guide #9 Debugging Gradle Scripts</title>
    <link href="https://2bab.me/en/blog/2021-02-14-android-build-script-debug-support/"/>
    <updated>2021-02-14T00:00:00Z</updated>
    <id>https://2bab.me/en/blog/2021-02-14-android-build-script-debug-support/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;&amp;quot;Build Guide&amp;quot; is a series of articles exploring Android build-related topics, covering Gradle, Android Gradle Plugin, Kotlin Script, and other tools, as well as their architectural applications. Starting from problem discovery and resolution, and landing on sharing new knowledge to improve productivity.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This article discusses the current debugging support for &lt;code&gt;*.gradle.kts&lt;/code&gt; scripts in IDEA / Android Studio.&lt;/p&gt;
&lt;h2 id=&quot;debugging-capabilities&quot; tabindex=&quot;-1&quot;&gt;Debugging Capabilities&lt;/h2&gt;
&lt;p&gt;Regarding the definition of debugging capabilities, let me give an example. We add various script snippets to a common &lt;code&gt;build.gradle.kts&lt;/code&gt;, including but not limited to property declarations, file reading, Gradle API operations, AGP API operations, etc., then set multiple breakpoints. When compiling, we remote attach to check &lt;strong&gt;whether it can suspend at the correct location, whether it can get context information, and whether it can perform Expression Evaluate operations&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Specific test points:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Context information inside the &lt;code&gt;android { default { ... } }&lt;/code&gt; closure;&lt;/li&gt;
&lt;li&gt;Context information inside the &lt;code&gt;dependencies { ... }&lt;/code&gt; closure;&lt;/li&gt;
&lt;li&gt;A small custom script snippet in &lt;code&gt;build.gradle.kts&lt;/code&gt;, containing property definitions, Gradle API calls, AGP API calls - context information at these three points;&lt;/li&gt;
&lt;li&gt;A small custom script snippet in &lt;code&gt;prebuilt.gradle.kts&lt;/code&gt; inside &lt;code&gt;/buildSrc&lt;/code&gt;, also containing the same three points;&lt;/li&gt;
&lt;li&gt;A custom plugin inside &lt;code&gt;/buildSrc&lt;/code&gt; - context information when running the plugin&#39;s apply method (as a reference for complete debugging capability).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I conducted two experiments. The first was about half a year ago using Android Studio 4.0 + IDEA 2020.1, and the second was recently using Android Studio 4.2.0-beta04 + IDEA 2021.1.EAP. The test results below all use the second IDE version plus:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Android Gradle Plugin 4.1.1&lt;/li&gt;
&lt;li&gt;Kotlin 1.4.30&lt;/li&gt;
&lt;li&gt;Gradle 6.8.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here we focus on Kotlin DSL; the Groovy DSL situation can be analyzed similarly.&lt;/p&gt;
&lt;h2 id=&quot;multi-module-android-project&quot; tabindex=&quot;-1&quot;&gt;Multi-module Android Project&lt;/h2&gt;
&lt;p&gt;For the first test, we used a multi-module (and multi-Application) project, with the following results:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:center&quot;&gt;#&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Test Item&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;IDEA &amp;amp; Android Studio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;code&gt;android { default { ... } }&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;code&gt;dependencies { ... }&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;code&gt;build.gradle.kts&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;3.1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;3.2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;3.3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;code&gt;prebuilt.gradle.kts&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;4.1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;4.2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;4.3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;plugin&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/Screenshot%202021-02-15%20at%2010.30.01%20AM.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Quick summary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The results from both IDEs are completely consistent (not surprising even though AS is the community edition);&lt;/li&gt;
&lt;li&gt;Breakpoints at 3.1 and 4.1 can be correctly recognized but cannot get context information;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When debugging breakpoints in &lt;code&gt;build.gradle.kts&lt;/code&gt;, you must manually specify which script the current source corresponds to, as shown in the dropdown menu in the image above; the basis for judgment is the context information provided by the &lt;code&gt;this&lt;/code&gt; object in the Variables section of the current debug panel. For example, here &lt;code&gt;this&lt;/code&gt; points to DefaultConfig, and we only set breakpoints on the defaultConfig of the app module across multiple scripts, so we select &lt;code&gt;app&lt;/code&gt;&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In this test, Gradle script debugging support is still not comprehensive enough.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;single-module-kotlin-project&quot; tabindex=&quot;-1&quot;&gt;Single-module Kotlin Project&lt;/h2&gt;
&lt;p&gt;Next, let&#39;s test a simple Kotlin project:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:center&quot;&gt;#&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Test Item&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;IDEA &amp;amp; Android Studio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;code&gt;java { ... }&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;code&gt;dependencies { ... }&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;code&gt;build.gradle.kts&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;3.1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;3.2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;3.3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;code&gt;prebuilt.gradle.kts&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;4.1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;4.2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;4.3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;plugin&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Y&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The results are no different from the previous multi-module Android project.&lt;/p&gt;
&lt;h2 id=&quot;common-debugging-issues&quot; tabindex=&quot;-1&quot;&gt;Common Debugging Issues&lt;/h2&gt;
&lt;p&gt;Here are some common issues for reference:&lt;/p&gt;
&lt;h3 id=&quot;breakpoint-line-jumping&quot; tabindex=&quot;-1&quot;&gt;Breakpoint Line Jumping&lt;/h3&gt;
&lt;p&gt;When dealing with some simple requirements, such as modifying generated APK names, we often write and debug scripts directly in &lt;code&gt;build.gradle.kts&lt;/code&gt;. &lt;strong&gt;During debugging, although breakpoints appear to be set in AS or IDEA, various situations occur during execution where the source code cannot be matched, causing line jumping. In this case, you need to manually specify the corresponding script using the method mentioned in the summary above.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;idea-support-for-android-plugin&quot; tabindex=&quot;-1&quot;&gt;IDEA Support for Android Plugin&lt;/h3&gt;
&lt;p&gt;Note that IDEA generally supports new features faster, but support for Android Gradle Plugin lags behind Android Studio. For example, after the current 4.1.1 version of Android Studio was released, IDEA announced support for 4.1 in 2020.3.2 (but actual testing shows it doesn&#39;t support 4.1.1; the &lt;a href=&quot;https://youtrack.jetbrains.com/issue/IDEA-252775&quot;&gt;issue&lt;/a&gt; says 2021.1.EAP actually supports it).&lt;/p&gt;
&lt;h3 id=&quot;using-plugin-wrapping&quot; tabindex=&quot;-1&quot;&gt;Using Plugin Wrapping&lt;/h3&gt;
&lt;p&gt;In the early days based on Groovy DSL + &lt;code&gt;build.gradle&lt;/code&gt; scripts (around AGP 2.x era), debugging support was even worse. But there was a workaround for the third test point above: wrap the custom script block in &lt;code&gt;build.gradle&lt;/code&gt; into a Plugin. However, this is currently not viable in AS 4.2.0-beta04 or IDEA 2021.1.EAP testing. On the Kotlin DSL side, although the API documentation looks supportive, the plugin cannot actually be created, causing script compilation failure, making debugging moot. Here&#39;s a reference &lt;a href=&quot;https://github.com/gradle/gradle/issues/13667&quot;&gt;issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/Screenshot%202021-02-15%20at%204.54.37%20PM.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;run-button-displayed-in-build.gradle(.kts)-in-ide&quot; tabindex=&quot;-1&quot;&gt;Run Button Displayed in build.gradle(.kts) in IDE&lt;/h3&gt;
&lt;p&gt;Additionally, some colleagues may have seen a &amp;quot;run button&amp;quot; and &amp;quot;menu&amp;quot; like this in &lt;code&gt;build.gradle&lt;/code&gt; + IDEA 2020.x environment, similar to running a unit test or a Java main method:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://2bab-images.lastmayday.com/blog/Screenshot%202020-07-03%20at%203.12.07%20PM.png?imageslim&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;However, its principle should be consistent with manually executing Gradle Sync / Build, and it hasn&#39;t changed our test results.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Gradle script (specifically &lt;code&gt;*.gradle.kts&lt;/code&gt;, though &lt;code&gt;*.gradle&lt;/code&gt; should be similar) debugging support is currently weak. Complex logic should use precompiled plugins/standalone plugins in &lt;code&gt;buildSrc&lt;/code&gt;, which have complete debugging capabilities.&lt;/p&gt;
</content>
  </entry>
</feed>