# Tiago Danin, complete site content > Every page of tiagodanin.com in one file, for ingestion in a single request. - Slim index: https://tiagodanin.com/llms.txt - Documents: 394 # Flutter Widgetbook: How to Document your Design System the Right Way! > How Widgetbook solves the documentation problem in Flutter projects with a custom design system. On Direção Fácil, it became a visual catalog, a contract with Figma, and precise context for Claude Code to generate screens using the right widgets. - HTML version: https://tiagodanin.com/post/flutter-widgetbook-how-to-document-your-design-system-the-right-way/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2026-05 - Language: English - Tags: Flutter, Design System, Widgetbook, Mobile, UI/UX, Article - Originally published at: https://www.linkedin.com/pulse/flutter-widgetbook-como-documentar-seu-design-system-do-tiago-danin-ocgnf/ ![Flutter Widgetbook: How to Document your Design System the Right Way!](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/cover.png) When I started developing Direção Fácil, it was clear from early on that the app would have its own design system. Every button, every card, every progress bar with a specific look, all designed pixel-perfect in Figma. To implement the components faster, I used an LLM with Figma as a reference. It was a straightforward process: I designed it, fed it to the model, tweaked it, moved on to the next. When I was done, I had 25 components ready: `DFButton` in 5 variants, `DFModule` in 4 states, `DFQuizBar`, `DFQuestion`, and another dozen more. ![Design of Direção Fácil components in Figma before becoming code](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/figma-direcao-facil.png) But as the components got finished, I noticed a gap: how would I know a component was correct if I could never see it in isolation, outside the context of the whole app? I wanted to be able to open `DFModule` with all its states side by side, see `DFButton` in every variant at once, and compare it to Figma before using it on screens. I needed a visual catalog, the kind Material Design has for Flutter's native widgets, but for the components of my own design system. The first option that came to mind was Storybook, but it doesn't support Flutter. While looking for an alternative, I discovered Widgetbook. ## What is Widgetbook Widgetbook is, basically, the Storybook of Flutter. You create "use cases" for each component: functions that render the widget in specific states. Widgetbook builds a separate interface from your main app, where you navigate through all the components, change parameters in real time, and validate visually. In practice, it works as an alternative entry point inside the same Flutter project: the real app stays untouched, and Widgetbook is just a different way to run the same code, now in catalog mode. ## Installation In `pubspec.yaml`, you need four dependencies: ```yaml dependencies: widgetbook: ^3.0.0 dev_dependencies: widgetbook_annotation: ^3.0.0 widgetbook_generator: ^3.0.0 build_runner: ^2.4.0 ``` The `widgetbook` package is the runtime with the visual interface, `widgetbook_annotation` brings the `@UseCase` and `@App` annotations, and `widgetbook_generator` is the most interesting part: it runs via `build_runner`, automatically discovers all use cases in the project, and generates the directory file that builds the catalog's sidebar menu. ## Creating the first use case For each component, you create a `*_use_case.dart` file alongside the widget, following a simple pattern: a function annotated with `@UseCase` that returns a `Widget`. In Direção Fácil, I started with `DFButton`, which has 5 variants. The use cases file looked like this: ```dart @widgetbook.UseCase(name: 'Primary Button', type: DFButton) Widget buildPrimaryButton(BuildContext context) { return DFButton.primary(label: 'Primary Button', onPressed: () {}); } @widgetbook.UseCase(name: 'Text Button', type: DFButton) Widget buildTextButton(BuildContext context) { return DFButton.text(label: 'Text Button', onPressed: () {}); } ... ``` Each `@UseCase` takes a `name`, which appears in the catalog's sidebar menu, and a `type`, which is the widget being documented. You can create as many use cases as you want for the same component. ![Widgetbook catalog showing the DFButton variants](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/widgetbook-button-view.png) ## Configuring the entry point With the use cases created, you need a separate entry point for Widgetbook: a class annotated with `@App()` where you configure the addons. `build_runner` automatically generates the directory file from your use cases, with no need to register anything manually. The [official documentation](https://docs.widgetbook.io) covers this step in detail. Addons are one of Widgetbook's strongest points. It works similarly to a plugin system: each addon adds a control in the side panel that changes how the component is displayed, without altering the code. With `ViewportAddon` you simulate specific screen sizes, from an iPhone 13 to a Galaxy Note, without switching devices. `TextScaleAddon` increases the font scale to test accessibility. `MaterialThemeAddon` switches between app themes in real time. And `AlignmentAddon` repositions the component on screen to test different layout contexts, and so on... Once configured, running the catalog is simple: ```bash # On the simulator flutter run -t lib/main_widgetbook.dart # In the browser, to share with the design team flutter run -d chrome -t lib/main_widgetbook.dart ``` The example below shows `DFProfileBar` in the catalog: ![DFProfileBar in Widgetbook with 5 lives and knobs in the sidebar](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/widgetbook-view-profile_bar_dfprofilebar_full-lives_5_lives.png) The same component in the real app, for comparison: ![DFProfileBar in the Direção Fácil app in production](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/Screenshot-result-app-mobile-df_profile-bar.png) ## Knobs: interactive parameters What makes Widgetbook most useful day-to-day are knobs. Instead of creating a use case for every possible combination of parameters, you expose the parameters as controls in the sidebar. Design and dev can test variations without editing code. In another project that uses the same pattern, the main button's use case looked like this: ```dart @UseCase(name: "default", type: AppButton) Widget buildButtonUseCase(BuildContext context) { final variant = context.knobs.object.dropdown( label: "variant", options: AppButtonVariant.values, initialOption: AppButtonVariant.tonal, labelBuilder: (value) => value.name, ); final label = context.knobs.string( label: "label", initialValue: "Continue", ); final enabled = context.knobs.boolean( label: "enabled", initialValue: true, ); return AppButton( label: label, variant: variant, enabled: enabled, onPressed: () {}, ); } ``` With this, you open the catalog, navigate to the component, and have a side panel to switch the variant, edit the text, and enable or disable it in real time. Widgetbook offers knobs for `string`, `boolean`, `int`, `double`, `color`, `duration`, and dropdown. ![DFButton knobs in the Widgetbook side panel](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/widgetbook-button-knobs.png) ## The game-changer: Widgetbook as LLM context The combination that accelerated development the most was this: design system documented in Widgetbook, with the component list exported as a text file, passed as context to Claude Code when writing the screens. The flow on Direção Fácil went like this: as I implemented the components with LLM help, I created the Widgetbook use cases alongside each one. When a component was ready, I ran the catalog to validate it visually against Figma. Then, when implementing a screen, I passed two contexts to Claude Code: the file with the component documentation and the architecture instructions. The central instruction was direct: don't create new components, only use the ones listed in this documentation. With that, the LLM would create entire screens with the right widgets, the correct constructors, and the exact parameters. Without this catalog, the model would invent names and signatures of widgets that don't exist, and the screen wouldn't run on the first try. The use cases documentation became the contract between the design system and the generated code. ## Conclusion Widgetbook solves a real problem in Flutter projects with a custom design system: the lack of a visual place to consult all available components. If you're starting a Flutter project that will have custom components, it's worth setting up Widgetbook from the very first widget. The cost of keeping the catalog up to date is practically zero when you create the use case alongside the component. And the benefit of having a catalog that runs in the simulator or browser, that can be shared with design for validation, and that serves as precise context for any collaborator, pays off long before the project grows. What I used here is just the basics. Widgetbook goes well beyond that: it has support for [golden tests](https://docs.widgetbook.io/glossary/golden-tests), which are automated visual regression tests where you take a snapshot of the component and any future change is compared against that reference. If something changes by accident, the test fails. And there's [Widgetbook Cloud](https://docs.widgetbook.io/cloud/reviews), which integrates visual reviews directly into the pull request flow: on every PR, the platform compares visual changes between builds and publishes the result as a status on the repository, allowing design and dev to approve or block merging based on what actually changed in the interface. For teams working with a design system, these two features change the game. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Flutter Widgetbook: Como Documentar seu Design System do jeito certo! > Como o Widgetbook resolve o problema de documentar componentes em projetos Flutter com design system próprio. No Direção Fácil, virou catálogo visual, contrato com o Figma e contexto preciso para o Claude Code gerar telas usando os widgets certos. - HTML version: https://tiagodanin.com/br/post/flutter-widgetbook-how-to-document-your-design-system-the-right-way/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2026-05 - Language: Portuguese - Tags: Flutter, Design System, Widgetbook, Mobile, UI/UX, Article - Originally published at: https://www.linkedin.com/pulse/flutter-widgetbook-como-documentar-seu-design-system-do-tiago-danin-ocgnf/ ![Flutter Widgetbook: Como Documentar seu Design System do jeito certo!](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/cover.png) Quando comecei a desenvolver o Direção Fácil, ficou claro desde cedo que o app teria um design system próprio. Cada botão, cada card, cada barra de progresso com visual específico, tudo desenhado pixel-perfect no Figma. Para implementar os componentes mais rápido, usei LLM com o Figma como referência. Foi um processo direto: desenhei, passei para o modelo, ajustei, parti para o próximo. Quando terminei, tinha 25 componentes prontos: `DFButton` em 5 variantes, `DFModule` em 4 estados, `DFQuizBar`, `DFQuestion` e mais uma dúzia de outros. ![Design dos componentes do Direção Fácil no Figma antes de virar código](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/figma-direcao-facil.png) Mas conforme os componentes iam ficando prontos, fui percebendo um gap: como eu sabia que o componente estava correto se nunca conseguia vê-lo isolado, fora do contexto do app inteiro? Eu queria poder abrir o `DFModule` com todos os seus estados lado a lado, ver o `DFButton` em todas as variantes de uma vez, e comparar com o Figma antes de sair usando nas telas. Precisava de um catálogo visual, do tipo que o Material Design tem para os widgets nativos do Flutter, mas para os componentes do meu próprio design system. A primeira opção que veio à cabeça foi o Storybook, mas ele não tem suporte para Flutter. Foi procurando uma alternativa que descobri o Widgetbook. ## O que é o Widgetbook O Widgetbook é, basicamente, o Storybook do Flutter. Você cria "use cases" para cada componente: funções que renderizam o widget em estados específicos. O Widgetbook monta uma interface separada do seu app principal, onde você navega por todos os componentes, troca parâmetros em tempo real e valida visualmente. Na prática, ele funciona como um entry point alternativo dentro do mesmo projeto Flutter: o app real continua inalterado, e o Widgetbook é só uma forma diferente de rodar o mesmo código, agora em modo catálogo. ## Instalação No `pubspec.yaml`, você precisa de quatro dependências: ```yaml dependencies: widgetbook: ^3.0.0 dev_dependencies: widgetbook_annotation: ^3.0.0 widgetbook_generator: ^3.0.0 build_runner: ^2.4.0 ``` O `widgetbook` é o runtime com a interface visual, o `widgetbook_annotation` traz as anotações `@UseCase` e `@App`, e o `widgetbook_generator` é o que faz a parte mais interessante: roda via `build_runner`, descobre automaticamente todos os use cases do projeto e gera o arquivo de diretórios que monta o menu lateral do catálogo. ## Criando o primeiro use case Para cada componente, você cria um arquivo `*_use_case.dart` ao lado do widget, seguindo um padrão simples: uma função anotada com `@UseCase` que retorna um `Widget`. No Direção Fácil, comecei pelo `DFButton`, que tem 5 variantes. O arquivo de use cases ficou assim: ```dart @widgetbook.UseCase(name: 'Primary Button', type: DFButton) Widget buildPrimaryButton(BuildContext context) { return DFButton.primary(label: 'Botão Primary', onPressed: () {}); } @widgetbook.UseCase(name: 'Text Button', type: DFButton) Widget buildTextButton(BuildContext context) { return DFButton.text(label: 'Botão Text', onPressed: () {}); } ... ``` Cada `@UseCase` recebe um `name`, que aparece no menu lateral do catálogo, e um `type`, que é o widget sendo documentado. Você pode criar quantos use cases quiser para o mesmo componente. ![Catálogo do Widgetbook mostrando as variantes do DFButton](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/widgetbook-button-view.png) ## Configurando o entry point Com os use cases criados, você precisa de um entry point separado para o Widgetbook: uma classe anotada com `@App()` onde você configura os addons. O `build_runner` gera automaticamente o arquivo de diretórios a partir dos seus use cases, sem precisar registrar nada manualmente. A [documentação oficial](https://docs.widgetbook.io) cobre esse passo em detalhes. Os addons são um dos pontos mais fortes do Widgetbook. Funciona parecido com um sistema de plugins: cada addon adiciona um controle no painel lateral que muda como o componente é exibido, sem alterar o código. Com o `ViewportAddon` você simula tamanhos de tela específicos, do iPhone 13 a um Galaxy Note, sem precisar trocar de dispositivo. O `TextScaleAddon` aumenta a escala de fonte para testar acessibilidade. O `MaterialThemeAddon` alterna entre temas do app em tempo real. E o `AlignmentAddon` reposiciona o componente na tela para testar diferentes contextos de layout, e por aí vai... Depois de configurado, rodar o catálogo é simples: ```bash # No simulador flutter run -t lib/main_widgetbook.dart # No navegador, para compartilhar com o time de design flutter run -d chrome -t lib/main_widgetbook.dart ``` O exemplo abaixo mostra o `DFProfileBar` no catálogo: ![DFProfileBar no Widgetbook com 5 vidas e knobs na sidebar](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/widgetbook-view-profile_bar_dfprofilebar_full-lives_5_lives.png) O mesmo componente no app real, para comparar: ![DFProfileBar no app Direção Fácil em produção](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/Screenshot-result-app-mobile-df_profile-bar.png) ## Knobs: parâmetros interativos O que torna o Widgetbook mais útil no dia a dia são os knobs. Em vez de criar um use case para cada combinação possível de parâmetros, você expõe os parâmetros como controles na sidebar. Design e dev conseguem testar variações sem editar código. Em um outro projeto que usa o mesmo padrão, o use case do botão principal ficou assim: ```dart @UseCase(name: "default", type: AppButton) Widget buildButtonUseCase(BuildContext context) { final variant = context.knobs.object.dropdown( label: "variant", options: AppButtonVariant.values, initialOption: AppButtonVariant.tonal, labelBuilder: (value) => value.name, ); final label = context.knobs.string( label: "label", initialValue: "Continuar", ); final enabled = context.knobs.boolean( label: "enabled", initialValue: true, ); return AppButton( label: label, variant: variant, enabled: enabled, onPressed: () {}, ); } ``` Com isso, você abre o catálogo, navega até o componente e tem um painel lateral para trocar a variante, editar o texto e habilitar ou desabilitar em tempo real. O Widgetbook oferece knobs para `string`, `boolean`, `int`, `double`, `color`, `duration` e dropdown. ![Knobs do DFButton no painel lateral do Widgetbook](/images/posts/flutter-widgetbook-como-documentar-seu-design-system-do-jeito-certo/widgetbook-button-knobs.png) ## O pulo do jogo: Widgetbook como contexto para LLM A combinação que mais acelerou o desenvolvimento foi essa: design system documentado no Widgetbook, com a lista de componentes exportada como arquivo de texto, passada como contexto para o Claude Code na hora de escrever as telas. O fluxo no Direção Fácil ficou assim: conforme implementava os componentes com ajuda do LLM, ia criando os use cases do Widgetbook ao lado de cada um. Quando um componente ficava pronto, rodava o catálogo para validar visualmente em comparação com o Figma. Depois, quando ia implementar uma tela, passava dois contextos para o Claude Code: o arquivo com a documentação dos componentes e as instruções de arquitetura. A instrução central era direta: não crie novos componentes, use somente os listados nesta documentação. Com isso, o LLM criava telas inteiras com os widgets certos, os construtores corretos e os parâmetros exatos. Sem esse catálogo, o modelo ficaria inventando nomes e assinaturas de widgets que não existem, e a tela não rodaria na primeira tentativa. A documentação dos use cases virou o contrato entre o design system e a geração de código. ## Conclusão O Widgetbook resolve um problema real em projetos Flutter com design system próprio: a falta de um lugar visual para consultar todos os componentes disponíveis. Se você está começando um projeto Flutter que vai ter componentes customizados, vale configurar o Widgetbook desde o primeiro widget. O custo de manter o catálogo atualizado é praticamente zero quando você cria o use case junto com o componente. E o benefício de ter um catálogo que roda no simulador ou no navegador, que pode ser compartilhado com design para validação e que serve como contexto preciso para qualquer colaborador, compensa muito antes do projeto crescer. O que usei aqui é só a base. O Widgetbook vai bem além disso: tem suporte a [golden tests](https://docs.widgetbook.io/glossary/golden-tests), que são testes de regressão visual automatizados, onde você tira um snapshot do componente e qualquer mudança futura é comparada contra essa referência. Se algo mudar sem querer, o teste falha. E tem o [Widgetbook Cloud](https://docs.widgetbook.io/cloud/reviews), que integra revisões visuais diretamente no fluxo de pull request: a cada PR, a plataforma compara as mudanças visuais entre builds e publica o resultado como um status no repositório, permitindo que design e dev aprovem ou bloqueiem a mesclagem com base no que realmente mudou na interface. Para times que trabalham com design system, essas duas funcionalidades mudam o nível do jogo. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Why Your API Key in Mobile Apps Needs to Be Restricted (Even When It Looks Like It Doesn't) > I pulled two working Google Maps API keys out of mobile apps in under a minute, both with no restrictions at all. Both reports were closed as Informative on HackerOne. Here is the extraction with apktool, the endpoint validation, and the three layers that actually fix it. - HTML version: https://tiagodanin.com/post/why-your-api-key-in-mobile-apps-needs-to-be-restricted/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2026-05 - Language: English - Tags: Security, Mobile, Android, Article - Originally published at: https://www.linkedin.com/pulse/por-que-sua-chave-de-api-em-apps-mobile-precisa-ser-restrita-danin-ioxye/ ![Why your API key in mobile apps needs to be restricted](/images/posts/por-que-sua-chave-de-api-em-apps-mobile-precisa-ser-restrita/cover.png) I was looking at a few mobile apps that use Google Maps and, in under a minute, I had two API keys in hand. Working keys, with no restrictions at all, pulled out with tools any security researcher already has installed. I reported both cases. Both were closed as **Informative** on HackerOne. And that response is exactly why I decided to write this article. ## Why this pattern is so common Most developers reason the same way: "if the key has to live inside the app, it is already exposed anyway, so there is nothing to do about it". That is a half truth, and the true half is convincing. An APK is basically a ZIP file. Java code compiles to bytecode that `apktool` and `jadx` reverse in seconds. Strings sit in plain text in `res/values/strings.xml` or in `AndroidManifest.xml`. No obfuscation solves that for good: anything the app needs to read at runtime, an attacker can read too. The false half is treating "exposed" as a synonym for "abusable". There is a whole spectrum between a completely open key and a key that is embedded in the app but only answers when the call comes from a context the provider can validate. That spectrum is what the standard answer ignores. ## Extracting the key The start is straightforward. With the APK in hand: ```bash java -jar apktool_2.12.0.jar d app.apk ``` That gives me the `AndroidManifest.xml`, the string files, the smali code and the resources. Google Cloud keys have a well known public prefix, so the grep is trivial: ```bash grep -r "AIzaSy" . ``` In one of the apps, the key was in the manifest: ```xml ``` In the other one, in the strings file: ```xml AIzaSy[REDACTED] ``` So far, nothing abnormal. This is how the Google Maps SDK expects to receive the key, and neither case is an implementation mistake. ## Validating the key Finding an embedded key is not a vulnerability. The problem starts when you prove it works outside the legitimate app. For that I use [gmapsapiscanner](https://github.com/ozguralp/gmapsapiscanner), an open source project that fires requests against every known Google Maps endpoint: Geocoding, Places (Find Place, Autocomplete, Details, Nearby Search, Text Search), Static Maps, Street View, Elevation, Timezone, Directions and Distance Matrix. Testing all of them matters because key restriction on Google Cloud is configurable **per API**. It is very common to find an app that locked down the main service and left everything else open, which keeps the key abusable while looking protected. A single call already answers the question: ```bash curl "https://maps.googleapis.com/maps/api/geocode/json?latlng=12,34&key=AIzaSy[REDACTED]" ``` On a properly configured key, the response looks like this: ```json { "error_message": "This IP, site or mobile application is not authorized to use this API key. Request received from IP address xxxxxxxx, with empty referer", "results": [], "status": "REQUEST_DENIED" } ``` On both apps, what came back was the full JSON, with results, no blocking whatsoever. From my personal machine, with no relationship at all to the production app. ## The risk scenario is real This is not hypothetical. There are bots running around the clock that download APKs in bulk from the Play Store, decompile them, grep for the known prefixes (`AIzaSy`, `sk_live` and so on) and validate every key they find against the open endpoints. A valid key becomes raw material. It gets used to run someone else's operation on top of the account that published the app, and the discovery usually arrives through the monthly invoice, not through a security alert. ## The fix is three layers The Google Cloud Console already ships everything you need. **1. Application restriction.** Restricts the key by package name (`com.example.app`) combined with the SHA-1 fingerprint of the signing certificate. Google validates that the call comes from that exact combination. Whoever extracts the key would have to sign an APK with the company's certificate to use it. On iOS the equivalent is the bundle ID. **2. API restriction.** Limits which Maps services that key can reach. If the app only uses Static Maps and Places, only those two stay enabled. Any attempt at Geocoding or Directions comes back as `REQUEST_DENIED` right away. It shrinks the exploitable surface dramatically. **3. Usage cap.** Sets a monthly spending ceiling. This is the last resort: it protects the wallet, but it does not prevent abuse. The key keeps being consumed until it hits the limit, and when it does, the legitimate app stops working too until the next cycle. The three work together. Application restriction blocks the origin, API restriction narrows the scope, usage cap contains the damage when the other two fail. Relying on the third one alone is exactly the scenario I found. ## How the reports ended Both were closed as Informative, with the standard justification: map keys have to be embedded in the front end to render, and the usage cap exists to limit spending. I did not argue. Bug bounty has canned responses to cut down on noise, and each program decides which risk level it accepts. For a bank with a large budget, an exposed map key may genuinely not be a priority. What bothers me is not the triage, it is that the full trade-off never gets documented anywhere, leaving the developer on the other side convinced that "there is nothing to do about it". ## The rule that applies to any project Every key embedded in the app needs to be restricted. No exceptions. And if a key cannot be restricted by signature or by scope, it probably should not be in the app at all. In that case the right solution is to move the call to a backend proxy that integrates with the third party and exposes only what is needed. ## Responsible disclosure Some technical details were modified or omitted following HackerOne's responsible disclosure guidelines and the bug bounty program rules of the companies involved. The keys are redacted and no identifier of the applications was exposed. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Por que sua chave de API em Apps Mobile precisa ser restrita (mesmo que pareça que não) > Extraí duas chaves do Google Maps de apps mobile em menos de um minuto, ambas sem restrição nenhuma. Os dois reports fecharam como Informative no HackerOne. O passo a passo da extração com apktool, a validação dos endpoints e as três camadas que resolvem o problema de verdade. - HTML version: https://tiagodanin.com/br/post/why-your-api-key-in-mobile-apps-needs-to-be-restricted/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2026-05 - Language: Portuguese - Tags: Security, Mobile, Android, Article - Originally published at: https://www.linkedin.com/pulse/por-que-sua-chave-de-api-em-apps-mobile-precisa-ser-restrita-danin-ioxye/ ![Por que sua chave de API em Apps Mobile precisa ser restrita](/images/posts/por-que-sua-chave-de-api-em-apps-mobile-precisa-ser-restrita/cover.png) Estava olhando alguns aplicativos mobile que usam Google Maps e, em menos de um minuto, tinha duas chaves de API na mão. Funcionais, sem restrição nenhuma, extraídas com ferramentas que qualquer pesquisador de segurança tem instaladas. Reportei os dois casos. Os dois foram fechados como **Informative** no HackerOne. E é justamente por causa dessa resposta que resolvi escrever esse artigo. ## Por que esse padrão é tão comum A intuição da maioria dos devs é essa: "se a chave precisa estar dentro do app, ela já está exposta de qualquer jeito, então não tem o que fazer". É uma meia-verdade, e a parte verdadeira é bem convincente. APK é basicamente um arquivo ZIP. Código Java compila para bytecode que `apktool` e `jadx` revertem em segundos. String fica em texto plano no `res/values/strings.xml` ou no `AndroidManifest.xml`. Não existe ofuscação que resolva isso de forma definitiva, qualquer coisa que o app precise ler em runtime, o atacante também consegue ler. A parte falsa é tratar "exposta" como sinônimo de "abusável". Existe um espectro inteiro entre uma chave completamente livre e uma chave que está embutida no app mas só responde quando a chamada vem de um contexto que o provedor consegue validar. É esse espectro que a resposta padrão ignora. ## Extraindo a chave O começo é direto. Com o APK em mãos: ```bash java -jar apktool_2.12.0.jar d app.apk ``` Isso me dá o `AndroidManifest.xml`, os arquivos de strings, o smali do código e os recursos. As chaves do Google Cloud têm um prefixo público bem conhecido, então o grep é trivial: ```bash grep -r "AIzaSy" . ``` Em um dos apps, a chave estava no manifesto: ```xml ``` No outro, no arquivo de strings: ```xml AIzaSy[REDACTED] ``` Até aqui, nada de anormal. É assim que o SDK do Google Maps espera receber a chave, e nenhum dos dois casos é um erro de implementação. ## Validando a chave Encontrar chave embutida não é vulnerabilidade. O problema começa quando você prova que ela funciona fora do app legítimo. Para isso uso o [gmapsapiscanner](https://github.com/ozguralp/gmapsapiscanner), um projeto open source que dispara requisições contra todos os endpoints conhecidos do Google Maps: Geocoding, Places (Find Place, Autocomplete, Details, Nearby Search, Text Search), Static Maps, Street View, Elevation, Timezone, Directions e Distance Matrix. Testar todos importa porque restrição de chave no Google Cloud é configurável **por API**. É muito comum encontrar app que bloqueou o serviço principal e deixou todo o resto aberto, o que mantém a chave abusável mesmo parecendo protegida. Uma chamada simples já responde a pergunta: ```bash curl "https://maps.googleapis.com/maps/api/geocode/json?latlng=12,34&key=AIzaSy[REDACTED]" ``` Em uma chave bem configurada, a resposta é essa: ```json { "error_message": "This IP, site or mobile application is not authorized to use this API key. Request received from IP address xxxxxxxx, with empty referer", "results": [], "status": "REQUEST_DENIED" } ``` Nos dois apps, o que voltou foi o JSON completo, com resultados, sem bloqueio nenhum. Da minha máquina pessoal, sem relação alguma com o app de produção. ## O cenário de risco é real Isso não é hipotético. Existem bots rodando 24 horas por dia que baixam APKs em massa da Play Store, descompilam, dão grep nos prefixos conhecidos (`AIzaSy`, `sk_live` e por aí vai) e validam cada chave encontrada contra os endpoints abertos. Chave válida vira insumo. Ela é usada para rodar operação de terceiro em cima da conta de quem publicou o app, e a descoberta costuma vir pela fatura no fim do mês, não por um alerta de segurança. ## A solução são três camadas O Google Cloud Console já entrega tudo o que é necessário. **1. Application restriction.** Restringe a chave por package name (`com.exemplo.app`) combinado com o fingerprint SHA-1 do certificado de assinatura. O Google valida se a chamada vem daquela combinação exata. Quem extrai a chave precisaria assinar um APK com o certificado da empresa para conseguir usá-la. No iOS o equivalente é o bundle ID. **2. API restriction.** Limita quais serviços do Maps aquela chave pode acessar. Se o app usa só Static Maps e Places, só esses dois ficam habilitados. Qualquer tentativa de Geocoding ou Directions volta como `REQUEST_DENIED` na hora. Reduz drasticamente a superfície explorável. **3. Usage cap.** Define um teto de gasto mensal. É o último recurso: protege o bolso, mas não impede o abuso. A chave continua sendo consumida até bater o limite, e quando bate, o app legítimo também para de funcionar até o próximo ciclo. As três trabalham juntas. Application restriction barra a origem, API restriction reduz o escopo, usage cap segura o prejuízo quando as outras duas falham. Só a terceira, sozinha, é exatamente o cenário que encontrei. ## Como os reports terminaram Os dois fecharam como Informative, com a justificativa padrão: chave de mapa precisa estar embutida no front-end para renderizar, e o usage cap existe para limitar gasto. Não discuti. Bug bounty tem resposta padronizada para reduzir ruído, e cada programa decide qual nível de risco aceita. Para um banco com orçamento grande, chave de mapa exposta pode realmente não ser prioridade. O que me incomoda não é o triage, é o trade-off inteiro nunca ser documentado em lugar nenhum, e o dev do outro lado achar que "não tem o que fazer". ## A regra que vale para qualquer projeto Toda chave embutida no app precisa estar restrita. Sem exceção. E se uma chave não pode ser restrita nem por assinatura nem por escopo, ela provavelmente não deveria estar no app. Nesse caso a solução certa é mover a chamada para um proxy no backend, que integra com o terceiro e expõe só o necessário. ## Divulgação responsável Alguns dados técnicos foram modificados ou omitidos seguindo as diretrizes de divulgação responsável do HackerOne e as regras dos programas de bug bounty das empresas envolvidas. As chaves estão redacted e nenhum identificador das aplicações foi exposto. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Creating an Agent in ChatGPT to Write Technical Stories > Automating the creation of technical stories by consolidating dispersed information from conversations, messages, and feedback. A solution to transform chaotic inputs into clear and standardized technical artifacts, freeing up cognitive capacity for trade-off analysis and higher-value technical decisions. - HTML version: https://tiagodanin.com/post/creating-a-chatgpt-agent-to-write-technical-stories/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2026-01 - Language: English - Tags: AI, Article - Originally published at: https://www.linkedin.com/pulse/criando-um-agente-chatgpt-para-escrever-stories-t%C3%A9cnicas-tiago-danin-jvaqf/?trackingId=u%2FtjVtwzG%2B9Dlb%2BQjZEHXg%3D%3D ![Creating an Agent in ChatGPT to Write Technical Stories](/images/posts/criando-um-agente-chatgpt-para-escrever-stories-tecnicas/cover.jpg) In my day-to-day work, my role is not limited to implementing code — I participate in technical decisions, impact assessments, risk analysis, flow definition, and alignment between Product and Engineering, and naturally I end up involved in story creation. This information comes from multiple sources: asynchronous conversations, loose messages in threads, screenshots of unexpected behavior, comments during calls, or decisions made informally. At some point, all of this needs to be consolidated into a clear, traceable, and shareable artifact with the team. ## The friction isn't writing, it's repeating the process Writing a story was never the problem — the friction lies in repeating the process every time I need to add a new item to the backlog. It's not difficult, but it's recurring. And when something is recurring, I immediately think about how I can automate parts of the process. It was from this that I started using ChatGPT as an operational support tool in my daily work. I would throw in loose texts, pieces of conversations, raw ideas, and ask it to help organize them within a story template I was already used to using. It wasn't just about "rewriting better": it helped me align the text to a known standard and raise questions that completed the story before it became part of the backlog. Over time, ChatGPT became a mirror, quickly showing where the idea was still incomplete. The problem is that this still required repeating the same request every time. I needed to explain the context, reinforce the format, remember what could or could not go into the story. It worked, but it wasn't reusable. That's when the question that changed everything arose: how to transform these ChatGPT conversations into something consistent, reusable, and not dependent on me making the same request every time? At that moment I remembered that I already used some agents from the Explore GPTs tab. I went to understand how to create one. There was nothing very sophisticated about it — it was more about structuring the prompt well, which I had already been doing in previous conversations. I went back to past interactions, asked ChatGPT itself to summarize what it had learned from me in that story creation flow, reviewed it, adjusted it, cut excesses, and compiled everything into a single place. From that, I created a fixed prompt with clear rules and an immutable structure. The idea was simple: whenever I threw in any input — loose text, feedback, conversation, or raw idea — the agent returned a ready-made technical story in the standard I already used daily; when this started working consistently, it became clear that I hadn't just created a better prompt, but a work agent. ## Creating the Agent in GPTs With this clear, I went directly to the GPTs editor at https://chatgpt.com/gpts/editor, and started configuring my new agent. The editor itself is relatively simple. It allows you to define name, description, instructions, examples, model, and permissions. However, it quickly becomes clear that the agent's behavior is determined almost exclusively by the base prompt. In my case, I already had a prompt that worked well in loose conversations. The work here was to transform that into something fixed, explicit, and without room for interpretation. I wanted it to behave the same way every time. I started by making the agent's role explicit: > you are an assistant specialized in… This way I don't treat the prompt as a request, but as a behavioral specification. An assistant specialized in standardizing technical stories for Product and Engineering teams. This anchors the domain and eliminates generic or didactic responses. Then, I defined rigid clear rules about what it can and cannot do: > Write stories in Brazilian Portuguese; Do not explain what you are doing; Do not use emojis; etc A central point is defining a fixed output structure (provide a Markdown model of an organized story), with title, expectation, context, acceptance criteria, scenarios, observations, and refinement questions — not as an optional example, but as a contract to follow. I also made clear what type of input it should expect: loose text, conversation, feedback, raw idea. The more specific it is, the less room the agent will have to improvise. During tests, any behavior outside expectations (excessive explanations, unnecessary creativity, or format variation) was corrected directly in the prompt by adding new rules to the "can and cannot do" list. The focus was on eliminating ambiguity until the output became predictable. With this, the agent started working predictably: the input can be chaotic, but the output is always a standardized technical story. When it reached that point, it became clear that I no longer depended on manual adjustments — any text became a story in the expected standard. After that, I published the agent in GPTs Explore. The Story Writer is available here and you can test it exactly the way I use it daily: https://chatgpt.com/g/g-696a498b7d0c8191b3c00ad1b40e6afa-story-writer ## Conclusion and Next Steps The main gain wasn't "creating a GPT", but removing a recurring cognitive cost from daily work. The responsibility to think, prioritize, and decide remains human. The agent acts only on the mechanical part of the process: organizing, standardizing, and structuring information. The Story Writer doesn't think for me or decide what should be done. It only solves the repetitive part of the process — the part that doesn't need to be re-evaluated every time. This frees up time and attention for what really matters: discussing impact, evaluating trade-offs, and making better technical decisions. This frees up time and attention for higher-value activities, such as trade-off analysis, impact assessment, and technical decision-making. The next step is to apply the same logic to other points in the routine where there is repetition, a well-defined standard, and unnecessary wear. Not treating these agents as definitive solutions, but as evolutionary tools that make sense as long as they continue solving real problems. Just like code fragments, these agents only justify their existence while delivering practical value in daily use. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Criando um Agente no ChatGPT para escrever stories técnicas > Automatizando a criação de stories técnicas consolidando informações dispersas em conversas, mensagens e feedbacks. Uma solução para transformar inputs caóticos em artefatos técnicos claros e padronizados, liberando capacidade cognitiva para análises de trade-offs e decisões técnicas de maior valor. - HTML version: https://tiagodanin.com/br/post/creating-a-chatgpt-agent-to-write-technical-stories/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2026-01 - Language: Portuguese - Tags: AI, Article - Originally published at: https://www.linkedin.com/pulse/criando-um-agente-chatgpt-para-escrever-stories-t%C3%A9cnicas-tiago-danin-jvaqf/?trackingId=u%2FtjVtwzG%2B9Dlb%2BQjZEHXg%3D%3D ![Criando um Agente no ChatGPT para escrever stories técnicas](/images/posts/criando-um-agente-chatgpt-para-escrever-stories-tecnicas/cover.jpg) No meu dia a dia, minha atuação não se limita à implementação de código, participo de decisão técnica, avaliação de impacto, análise de risco, definição de fluxos e alinhamento entre Produto e Engenharia e, naturalmente, acabo envolvido na criação de story. Essas informações surgem de múltiplas fontes: conversas assíncronas, mensagens soltas em threads, prints de comportamento inesperado, comentários durante calls ou decisões tomadas informalmente. Em algum ponto, tudo isso precisa ser consolidado em um artefato claro, rastreável e compartilhável com o time. ## O atrito não é escrever, é repetir o processo Escrever uma story nunca foi o problema, o atrito está em repetir o processo toda vez que preciso adicionar um novo item no backlog. Não é difícil, mas é recorrente. E quando algo é recorrente, eu penso logo em como posso automatizar algumas partes do processo. Foi a partir disso que comecei a usar o ChatGPT como uma ferramenta de apoio operacional no dia a dia. Jogava textos soltos, pedaços de conversa, ideias ainda cruas, e solicitava para ele ajudar a organizar aquilo dentro de um modelo de story que eu já estava acostumado a usar. Não era só sobre "reescrever melhor": ele me ajudava a alinhar o texto em um padrão conhecido e a levantar perguntas que completavam a story antes de virar parte do backlog. Com o tempo, o ChatGPT virou um espelho, mostrando rapidamente onde a ideia ainda estava incompleta. O problema é que isso ainda exigia repetir o mesmo pedido toda vez. Eu precisava explicar o contexto, reforçar o formato, lembrar o que podia ou não entrar na story. Funcionava, mas não era reutilizável. Foi aí que surgiu a pergunta que mudou tudo: como transformar essas conversas no ChatGPT em algo consistente, reutilizável e que não dependesse de eu solicitar a mesma coisa toda vez? Nesse momento lembrei que já usava alguns agentes da aba Explore GPTs. Fui atrás de entender como criar um. Não tinha nada muito sofisticado nisso, era mais sobre estruturar bem o prompt, que eu já vinha fazendo nas conversas anteriores. Voltei nas interações passadas, solicitei para o próprio ChatGPT resumir o que ele tinha aprendido comigo naquele fluxo de criação de story, revisei, ajustei, cortei excessos e compilei tudo em um único lugar. A partir disso, criei um prompt fixo, com regras claras e uma estrutura imutável. A ideia era simples: sempre que eu jogasse qualquer entrada, texto solto, feedback, conversa ou ideia, o agente devolvia uma story técnica pronta, no padrão que eu já usava no dia a dia; quando isso começou a funcionar consistentemente, ficou claro que eu não tinha criado só um prompt melhor, mas um agente de trabalho. ## Criando o Agente no GPTs Com isso claro, eu fui direto no editor do GPTs, em https://chatgpt.com/gpts/editor, e comecei a configurar meu novo agente. O editor em si é relativamente simples. Permite definir nome, descrição, instruções, exemplos, modelo e permissões. No entanto, fica claro rapidamente que o comportamento do agente é determinado quase exclusivamente pelo prompt base. No meu caso, eu já tinha um prompt que funcionava bem nas conversas soltas. O trabalho aqui foi transformar aquilo em algo fixo, explícito e sem margem para interpretação. Eu queria que ele se comportasse sempre do mesmo jeito. Comecei deixando explícito o papel do agente: > você é um assistente especializado em… Desse modo eu não trato o prompt como um pedido, mas como uma especificação de comportamento. Um assistente especializado em padronização de stories técnicas para times de Produto e Engenharia. Isso ancora o domínio e elimina respostas genéricas ou didáticas. Depois, defini regras rígidas claras do que ele pode e não fazer: > Escreva as stories em Português Brasil; Não explique o que você está fazendo; Não use emojis; e etc Um ponto central é definir estrutura fixa de saída (envie um modelo em Markdown de uma story organizada), com título, expectativa, contexto, critérios de aceite, cenários, observações e dúvidas de refinamento, não como exemplo opcional, mas como contrato a seguir. Também deixei claro que tipo de entrada ele deveria esperar: texto solto, conversa, feedback, ideia crua. Quanto mais específico é, menos margem o agente vai ter para improvisar. Durante os testes, qualquer comportamento fora do esperado (explicações excessivas, criatividade desnecessária ou variação de formato) era corrigido diretamente no prompt adicionado novas regras no "pode e não fazer". O foco era eliminar ambiguidade até que o output se tornasse previsível. Com isso, o agente passou a funcionar de forma previsível: a entrada pode ser caótica, mas a saída é sempre uma story técnica padronizada. Quando chegou nesse ponto, ficou claro que eu não dependia mais de ajustes manuais, qualquer texto virava uma story no padrão esperado. Depois disso, publiquei o agente no Explore dos GPTs. O Story Writer está disponível aqui e você pode testar exatamente do jeito que uso no dia a dia: https://chatgpt.com/g/g-696a498b7d0c8191b3c00ad1b40e6afa-story-writer ## Conclusão e próximos passos O principal ganho não foi "criar um GPTs", mas remover um custo cognitivo recorrente do dia a dia. A responsabilidade de pensar, priorizar e decidir, continua sendo humana. O agente atua apenas na parte mecânica do processo: organizar, padronizar e estruturar informação. O Story Writer não pensa por mim e nem decide o que deve ser feito. Ele só resolve a parte repetitiva do processo, aquela que não precisa ser reavaliada toda vez. Isso libera tempo e atenção para o que realmente importa: discutir impacto, avaliar trade-offs e tomar decisões técnicas melhores. Isso libera tempo e atenção para atividades de maior valor, como análise de trade-offs, avaliação de impacto e tomada de decisão técnica. O próximo passo é aplicar a mesma lógica a outros pontos da rotina onde existe repetição, padrão bem definido e desgaste desnecessário. Não tratando esses agentes como soluções definitivas, mas como ferramentas evolutivas, que fazem sentido enquanto continuam resolvendo problemas reais. Assim como fragmento de código, esses agentes só justificam sua existência enquanto entregarem valor prático no uso diário. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Creating an AI Agent in Claude Code to Control my Smartphone > Automating Android app documentation and testing with artificial intelligence. I was facing a specific challenge: I needed to automatically document various flows of an Android application. That's when I started exploring ways to carry out this process, and one of the tools I decided to test was Claude Code. - HTML version: https://tiagodanin.com/post/creating-an-ai-agent-in-claude-code-to-control-my-smartphone/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-09 - Language: English - Tags: Android, AI, Mobile, UI/UX, Testing, Tools, Article - Originally published at: https://blog.idopterlabs.com.br/criando-um-agente-de-ia-no-claude-code-para-controlar-meu-smartphone-5b19672564c0 ![Creating an AI Agent in Claude Code to Control my Smartphone](/images/posts/criando-um-agente-de-ia-no-claude-code-para-controlar-meu-smartphone/cover.png) ## Automating Android App Documentation and Testing with Artificial Intelligence ## The Initial Challenge I was facing a specific challenge: I needed to automatically document various flows of an Android application. That's when I started exploring ways to carry out this process, and one of the tools I decided to test was Claude Code. Initially, Claude Code didn't seem like a good choice for creating an autonomous agent — I had to keep "teaching" it how to use ADB commands to simulate user actions like clicks, typing, and other interactions, so the AI could control my smartphone. I thought about looking for an MCP (Model Context Protocol) that already implemented this functionality, but I couldn't find a suitable one, so I decided to build my own: [Android-Debug-Bridge-MCP](https://github.com/TiagoDanin/Android-Debug-Bridge-MCP). ## The Power of ADB UI Automator One of the most valuable ADB tools for what we're building is the UI Automator, which returns a detailed XML of the current interface. This makes the use of the `adb shell tap x y` command much more precise for clicking on specific elements on the screen — for the LLM, XML or Markdown format is more effective than an image. What I needed to organize was: - What types of elements to detect in the interface - How to show the content and position of each element - How to allow Claude Code to execute the correct action at the exact position ![Diagram of the agent controlling the smartphone via ADB](/images/posts/criando-um-agente-de-ia-no-claude-code-para-controlar-meu-smartphone/agent-diagram.png) ## Evolving the Agent After integrating Claude Code with my MCP, I rewrote the agent and gradually improved it until reaching an efficient flow. My AI agent follows a simple but effective pattern: 1. **Visualize** the current screen content 2. **Execute** a specific action 3. **Repeat** the process 4. **Generate** a complete test report ## Real Example: Testing the Stock Pulse App I'll show how the agent automatically tested the process of adding a stock (NVDA) within an app I helped develop called Stock Pulse: ### Command Used > @agent-app-tester Open br.com.idopterlabs.Tickerapp, add an Nvidia stock to the portfolio, click Save, and I expect the stock's current data to be displayed on the screen. The agent then executed the entire flow automatically. It created a test folder called `tickerapp_nvda_test`, opened the Stock Pulse app, and began its work. First, it captured the initial screen showing the empty portfolio screen. Then, it navigated to add a new stock, clicked the "+" button, and captured the stock selection screen. Next, the agent selected NVIDIA (NVDA) from the list, verified that the stock details were displayed correctly, and generated a complete report with all screenshots and documented results. ## Final Result The agent automatically generated: - Organized screenshots - Complete markdown report - Documentation of each step - Success/failure status for each stage All this in less than 2 minutes, without manual intervention! ## Use Cases and Possibilities The combination of **MCP** with **Claude Code** opens up various possibilities: **Test Automation** becomes incredibly powerful with this approach. You can run automated regression tests for critical flows without any manual intervention, automatically validate interface elements and layouts, and simulate realistic user interactions for usability testing. **Intelligent Documentation** is another game-changer. The system can automatically capture organized screenshots for app documentation, create detailed user journey documentation within applications, generate technical manuals with step-by-step guides and visual evidence, and even perform periodic monitoring to check application states. The possibilities are practically endless — any need involving automated control of Android devices can benefit from this solution. ## Quick Setup Getting started is simple: you'll need ADB installed and configured on your system, an Android device connected or an emulator running, and Claude Code properly configured. To install the MCP, add it to Claude Code with the command `claude mcp add --scope project android-debug-bridge-mcp -- npx android-debug-bridge-mcp`. Then, configure a custom agent with specific prompts for your test cases. You can find my configuration at: [app-tester.md](https://github.com/TiagoDanin/Android-Debug-Bridge-MCP/issues/4) Once everything is configured, you can start running tests using simple and direct commands to start the automation. ## Conclusion Claude Code, when combined with the right tools and well-structured instructions, demonstrates impressive potential for automation. This experiment represents just the beginning of a broader exploration — there is plenty of room for future improvements. It's important to recognize that for more complex scenarios, the expertise of a professional QA is still necessary. AI automation complements, but does not completely replace, specialized testing knowledge. Despite the limitations, I must say it was extremely gratifying to develop this solution. It not only met my initial goal of documenting application flows, but also opened doors to new automation possibilities that previously seemed impractical. The project will continue to evolve, and I hope to bring new features in the future. [Creating an AI Agent in Claude Code to Control my Smartphone](https://www.youtube.com/watch?v=zp38frEGcPA) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Criando um Agente de IA no Claude Code para Controlar meu Smartphone > Automatizando documentação e testes de apps Android com inteligência artificial. Eu estava enfrentando um desafio específico: precisava documentar automaticamente diversos fluxos de uma aplicação Android. Foi quando comecei a explorar maneiras de realizar esse processo, e uma das ferramentas que decidi testar foi o Claude Code. - HTML version: https://tiagodanin.com/br/post/creating-an-ai-agent-in-claude-code-to-control-my-smartphone/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-09 - Language: Portuguese - Tags: Android, AI, Mobile, UI/UX, Testing, Tools, Article - Originally published at: https://blog.idopterlabs.com.br/criando-um-agente-de-ia-no-claude-code-para-controlar-meu-smartphone-5b19672564c0 ![Criando um Agente de IA no Claude Code para Controlar meu Smartphone](/images/posts/criando-um-agente-de-ia-no-claude-code-para-controlar-meu-smartphone/cover.png) ## Automatizando documentação e testes de apps Android com inteligência artificial ## O Desafio Inicial Eu estava enfrentando um desafio específico: precisava documentar automaticamente diversos fluxos de uma aplicação Android. Foi quando comecei a explorar maneiras de realizar esse processo, e uma das ferramentas que decidi testar foi o Claude Code. Inicialmente, o Claude Code não parecia uma boa escolha para criar um agente autônomo, eu tinha que ficar "ensinado" como usar comandos do ADB para simular ações do usuário como cliques, digitação e outras interações, para que a IA pudesse controlar meu smartphone. Pensei em procurar um MCP (Model Context Protocol) que já implementasse essa funcionalidade, mas não consegui encontrar nenhum adequado, então decidi construir o meu próprio: [Android-Debug-Bridge-MCP](https://github.com/TiagoDanin/Android-Debug-Bridge-MCP). ## O Poder do ADB UI Automator Uma das ferramentas mais valiosas do ADB para o que estamos construindo é o UI Automator, que retorna um XML detalhado da interface atual. Isso torna o uso do comando `adb shell tap x y` muito mais preciso para clicar em elementos específicos da tela, para a LLM o formato XML ou Markdown é mais eficaz que uma imagem. O que eu precisava organizar era: - Quais tipos de elementos detectar na interface - Como mostrar o conteúdo e posição de cada elemento - Como permitir que o Claude Code executasse a ação correta na posição exata ![Diagrama do agente controlando o smartphone via ADB](/images/posts/criando-um-agente-de-ia-no-claude-code-para-controlar-meu-smartphone/agent-diagram.png) ## Evoluindo o Agente Após integrar o Claude Code com meu MCP, reescrevi o agente e o aprimorei gradualmente até chegar a um fluxo eficiente. Meu agente de IA segue um padrão simples, mas eficaz: 1. **Visualizar** o conteúdo atual da tela 2. **Executar** uma ação específica 3. **Repetir** o processo 4. **Gerar** um relatório de teste completo ## Exemplo Real: Testando o App Stock Pulse Vou mostrar como o agente testou automaticamente o processo de adicionar uma ação (NVDA) dentro de um aplicativo que ajudei a desenvolver chamado Stock Pulse: ### Comando Utilizado > @agent-app-tester Open br.com.idopterlabs.Tickerapp, add an Nvidia stock to the portfolio, click Save, and I expect the stock's current data to be displayed on the screen. O agente então executou todo o fluxo automaticamente. Ele criou uma pasta de teste chamada `tickerapp_nvda_test`, abriu o app Stock Pulse e começou seu trabalho. Primeiro, capturou a tela inicial mostrando a tela de portfólio vazia. Depois, navegou para adicionar uma nova ação clicou no botão "+" e capturou a tela de seleção de ações. Em seguida, o agente selecionou a NVIDIA (NVDA) da lista, verificou se os detalhes da ação eram exibidos corretamente e gerou um relatório completo com todas as capturas de tela e resultados documentados. ## Resultado Final O agente gerou automaticamente: - Screenshots organizadas - Relatório completo em markdown - Documentação de cada etapa - Status de sucesso/falha para cada estágio Tudo isso em menos de 2 minutos, sem intervenção manual! ## Casos de Uso e Possibilidades A combinação de **MCP** com o **Claude Code** abre várias possibilidades: **Automação de Testes** se torna incrivelmente poderosa com essa abordagem. Você pode executar testes de regressão automatizados para fluxos críticos sem qualquer intervenção manual, validar elementos e layouts de interface automaticamente e simular interações realistas de usuários para testes de usabilidade. **Documentação Inteligente** é outro divisor de águas. O sistema pode capturar automaticamente screenshots organizadas para documentação de aplicativos, criar documentação detalhada de jornadas de usuário dentro de aplicações, gerar manuais técnicos com guias passo a passo e evidências visuais, e até realizar monitoramento periódico para verificar estados de aplicação. As possibilidades são praticamente infinitas -- qualquer necessidade envolvendo controle automatizado de dispositivos Android pode se beneficiar desta solução. ## Configuração Rápida Começar é simples, você precisará do ADB instalado e configurado em seu sistema, um dispositivo Android conectado ou emulador em execução, e o Claude Code adequadamente configurado. Para instalar o MCP, adicione-o ao Claude Code com o comando `claude mcp add --scope project android-debug-bridge-mcp -- npx android-debug-bridge-mcp`. Em seguida, configure um agente personalizado com prompts específicos para seus casos de teste. Você pode encontrar minha configuração em: [app-tester.md](https://github.com/TiagoDanin/Android-Debug-Bridge-MCP/issues/4) Uma vez que tudo esteja configurado, você pode começar a executar testes usando comandos simples e diretos para iniciar a automação. ## Conclusão O Claude Code, quando combinado com as ferramentas certas e instruções bem estruturadas, demonstra um potencial impressionante para automação. Este experimento representa apenas o início de uma exploração mais ampla -- há muito espaço para melhorias futuras. É importante reconhecer que para cenários mais complexos, a expertise de um QA profissional ainda é necessária. A automação com IA complementa, mas não substitui completamente, o conhecimento especializado em testes. Apesar das limitações, confesso que foi extremamente gratificante desenvolver esta solução. Ela não apenas atendeu ao meu objetivo inicial de documentar fluxos de aplicação, mas também abriu portas para novas possibilidades de automação que antes pareciam impraticáveis. O projeto continuará evoluindo, e espero trazer novos recursos no futuro. [Criando um Agente de IA no Claude Code para Controlar meu Smartphone](https://www.youtube.com/watch?v=zp38frEGcPA) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Creating an AI Agent in Claude Code to Control my Smartphone > Automating Android app documentation and testing with an AI agent in Claude Code, wired to an MCP server that drives a real device over ADB. - HTML version: https://tiagodanin.com/post/mcp-claude-code-control-my-smartphone/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-08 - Language: English - Tags: Android, AI, Mobile, Testing, Tools, UI/UX, Article - Originally published at: https://dev.to/tiagodanin/creating-an-ai-agent-in-claude-code-to-control-my-smartphone-1e3e ## Automating Android App Documentation and Testing with Artificial Intelligence ## The Initial Challenge I was facing a specific challenge: I needed to automatically document various flows of an Android application. That's when I started exploring ways to carry out this process, and one of the tools I decided to test was Claude Code. Initially, Claude Code didn't seem like a good choice for creating an autonomous agent — I had to keep "teaching" it how to use ADB commands to simulate user actions like clicks, typing, and other interactions, so the AI could control my smartphone. I thought about looking for an MCP (Model Context Protocol) that already implemented this functionality, but I couldn't find a suitable one, so I decided to build my own: [Android-Debug-Bridge-MCP](https://github.com/TiagoDanin/Android-Debug-Bridge-MCP). ## The Power of ADB UI Automator One of the most valuable ADB tools for what we're building is the UI Automator, which returns a detailed XML of the current interface. This makes the use of the `adb shell tap x y` command much more precise for clicking on specific elements on the screen — for the LLM, XML or Markdown format is more effective than an image. What I needed to organize was: - What types of elements to detect in the interface - How to show the content and position of each element - How to allow Claude Code to execute the correct action at the exact position ## Evolving the Agent After integrating Claude Code with my MCP, I rewrote the agent and gradually improved it until reaching an efficient flow. My AI agent follows a simple but effective pattern: 1. **Visualize** the current screen content 2. **Execute** a specific action 3. **Repeat** the process 4. **Generate** a complete test report ## Real Example: Testing the Stock Pulse App I'll show how the agent automatically tested the process of adding a stock (NVDA) within an app I helped develop called Stock Pulse: ### Command Used > @agent-app-tester Open br.com.idopterlabs.Tickerapp, add an Nvidia stock to the portfolio, click Save, and I expect the stock's current data to be displayed on the screen. The agent then executed the entire flow automatically. It created a test folder called `tickerapp_nvda_test`, opened the Stock Pulse app, and began its work. First, it captured the initial screen showing the empty portfolio screen. Then, it navigated to add a new stock, clicked the "+" button, and captured the stock selection screen. Next, the agent selected NVIDIA (NVDA) from the list, verified that the stock details were displayed correctly, and generated a complete report with all screenshots and documented results. ## Final Result The agent automatically generated: - Organized screenshots - Complete markdown report - Documentation of each step - Success/failure status for each stage All this in less than 2 minutes, without manual intervention! ## Use Cases and Possibilities The combination of **MCP** with **Claude Code** opens up various possibilities: **Test Automation** becomes incredibly powerful with this approach. You can run automated regression tests for critical flows without any manual intervention, automatically validate interface elements and layouts, and simulate realistic user interactions for usability testing. **Intelligent Documentation** is another game-changer. The system can automatically capture organized screenshots for app documentation, create detailed user journey documentation within applications, generate technical manuals with step-by-step guides and visual evidence, and even perform periodic monitoring to check application states. The possibilities are practically endless — any need involving automated control of Android devices can benefit from this solution. ## Quick Setup Getting started is simple: you'll need ADB installed and configured on your system, an Android device connected or an emulator running, and Claude Code properly configured. To install the MCP, add it to Claude Code with the command `claude mcp add --scope project android-debug-bridge-mcp -- npx android-debug-bridge-mcp`. Then, configure a custom agent with specific prompts for your test cases. You can find my configuration at: [app-tester.md](https://github.com/TiagoDanin/Android-Debug-Bridge-MCP/issues/4) Once everything is configured, you can start running tests using simple and direct commands to start the automation. ## Conclusion Claude Code, when combined with the right tools and well-structured instructions, demonstrates impressive potential for automation. This experiment represents just the beginning of a broader exploration — there is plenty of room for future improvements. It's important to recognize that for more complex scenarios, the expertise of a professional QA is still necessary. AI automation complements, but does not completely replace, specialized testing knowledge. Despite the limitations, I must say it was extremely gratifying to develop this solution. It not only met my initial goal of documenting application flows, but also opened doors to new automation possibilities that previously seemed impractical. The project will continue to evolve, and I hope to bring new features in the future. > Special thanks to my friend Iago Cavalcante for reviewing the translation and for introducing me to Claude Code. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Criando um agente de IA no Claude Code para controlar meu smartphone > Automatizando documentação e testes de apps Android com um agente de IA no Claude Code, ligado a um servidor MCP que controla um aparelho real via ADB. - HTML version: https://tiagodanin.com/br/post/mcp-claude-code-control-my-smartphone/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-08 - Language: Portuguese - Tags: Android, AI, Mobile, Testing, Tools, UI/UX, Article - Originally published at: https://dev.to/tiagodanin/creating-an-ai-agent-in-claude-code-to-control-my-smartphone-1e3e ## Automatizando documentação e testes de apps Android com inteligência artificial ## O Desafio Inicial Eu estava enfrentando um desafio específico: precisava documentar automaticamente diversos fluxos de uma aplicação Android. Foi quando comecei a explorar maneiras de realizar esse processo, e uma das ferramentas que decidi testar foi o Claude Code. Inicialmente, o Claude Code não parecia uma boa escolha para criar um agente autônomo, eu tinha que ficar "ensinado" como usar comandos do ADB para simular ações do usuário como cliques, digitação e outras interações, para que a IA pudesse controlar meu smartphone. Pensei em procurar um MCP (Model Context Protocol) que já implementasse essa funcionalidade, mas não consegui encontrar nenhum adequado, então decidi construir o meu próprio: [Android-Debug-Bridge-MCP](https://github.com/TiagoDanin/Android-Debug-Bridge-MCP). ## O Poder do ADB UI Automator Uma das ferramentas mais valiosas do ADB para o que estamos construindo é o UI Automator, que retorna um XML detalhado da interface atual. Isso torna o uso do comando `adb shell tap x y` muito mais preciso para clicar em elementos específicos da tela, para a LLM o formato XML ou Markdown é mais eficaz que uma imagem. O que eu precisava organizar era: - Quais tipos de elementos detectar na interface - Como mostrar o conteúdo e posição de cada elemento - Como permitir que o Claude Code executasse a ação correta na posição exata ## Evoluindo o Agente Após integrar o Claude Code com meu MCP, reescrevi o agente e o aprimorei gradualmente até chegar a um fluxo eficiente. Meu agente de IA segue um padrão simples, mas eficaz: 1. **Visualizar** o conteúdo atual da tela 2. **Executar** uma ação específica 3. **Repetir** o processo 4. **Gerar** um relatório de teste completo ## Exemplo Real: Testando o App Stock Pulse Vou mostrar como o agente testou automaticamente o processo de adicionar uma ação (NVDA) dentro de um aplicativo que ajudei a desenvolver chamado Stock Pulse: ### Comando Utilizado > @agent-app-tester Open br.com.idopterlabs.Tickerapp, add an Nvidia stock to the portfolio, click Save, and I expect the stock's current data to be displayed on the screen. O agente então executou todo o fluxo automaticamente. Ele criou uma pasta de teste chamada `tickerapp_nvda_test`, abriu o app Stock Pulse e começou seu trabalho. Primeiro, capturou a tela inicial mostrando a tela de portfólio vazia. Depois, navegou para adicionar uma nova ação clicou no botão "+" e capturou a tela de seleção de ações. Em seguida, o agente selecionou a NVIDIA (NVDA) da lista, verificou se os detalhes da ação eram exibidos corretamente e gerou um relatório completo com todas as capturas de tela e resultados documentados. ## Resultado Final O agente gerou automaticamente: - Screenshots organizadas - Relatório completo em markdown - Documentação de cada etapa - Status de sucesso/falha para cada estágio Tudo isso em menos de 2 minutos, sem intervenção manual! ## Casos de Uso e Possibilidades A combinação de **MCP** com o **Claude Code** abre várias possibilidades: **Automação de Testes** se torna incrivelmente poderosa com essa abordagem. Você pode executar testes de regressão automatizados para fluxos críticos sem qualquer intervenção manual, validar elementos e layouts de interface automaticamente e simular interações realistas de usuários para testes de usabilidade. **Documentação Inteligente** é outro divisor de águas. O sistema pode capturar automaticamente screenshots organizadas para documentação de aplicativos, criar documentação detalhada de jornadas de usuário dentro de aplicações, gerar manuais técnicos com guias passo a passo e evidências visuais, e até realizar monitoramento periódico para verificar estados de aplicação. As possibilidades são praticamente infinitas -- qualquer necessidade envolvendo controle automatizado de dispositivos Android pode se beneficiar desta solução. ## Configuração Rápida Começar é simples, você precisará do ADB instalado e configurado em seu sistema, um dispositivo Android conectado ou emulador em execução, e o Claude Code adequadamente configurado. Para instalar o MCP, adicione-o ao Claude Code com o comando `claude mcp add --scope project android-debug-bridge-mcp -- npx android-debug-bridge-mcp`. Em seguida, configure um agente personalizado com prompts específicos para seus casos de teste. Você pode encontrar minha configuração em: [app-tester.md](https://github.com/TiagoDanin/Android-Debug-Bridge-MCP/issues/4) Uma vez que tudo esteja configurado, você pode começar a executar testes usando comandos simples e diretos para iniciar a automação. ## Conclusão O Claude Code, quando combinado com as ferramentas certas e instruções bem estruturadas, demonstra um potencial impressionante para automação. Este experimento representa apenas o início de uma exploração mais ampla -- há muito espaço para melhorias futuras. É importante reconhecer que para cenários mais complexos, a expertise de um QA profissional ainda é necessária. A automação com IA complementa, mas não substitui completamente, o conhecimento especializado em testes. Apesar das limitações, confesso que foi extremamente gratificante desenvolver esta solução. Ela não apenas atendeu ao meu objetivo inicial de documentar fluxos de aplicação, mas também abriu portas para novas possibilidades de automação que antes pareciam impraticáveis. O projeto continuará evoluindo, e espero trazer novos recursos no futuro. > Agradecimentos ao meu amigo Iago Cavalcante pela revisão da tradução e por me apresentar ao Claude Code. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Liquid Glass in Flutter: Do We Really Need It? > A critical reflection on the Liquid Glass effect in Flutter and its real necessity in mobile applications. Exploring when this feature adds value to the user experience and when it can be just unnecessary complexity to the project. - HTML version: https://tiagodanin.com/post/liquid-glass-flutter-do-we-really-need-it/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-06 - Language: English - Tags: Flutter, UI/UX, Article - Originally published at: https://www.linkedin.com/pulse/liquid-glass-flutter-ser%C3%A1-que-realmente-precisamos-disso-tiago-danin-zjptf/ ![Liquid Glass in Flutter - do we really need it?](/images/posts/liquid-glass-flutter-sera-que-realmente-precisamos-disso/cover.jpg) You know that moment when Apple releases something new and suddenly everyone is talking about it? That's what happened with Liquid Glass. If you followed WWDC 2025 or have already looked at iOS 26, you saw that new effect all over the operating system — wet glass, with blur. But do we really need this in Flutter? It didn't take long for the community to start comparing: "In React Native, you just update Xcode and that's it, Liquid Glass in the app!" And it's true — since React Native uses native components, if Apple changes the look, your app already follows along. It seems like magic, but it comes at a price. Many apps might break out of nowhere. For example, the iOS navigation bar changed from fixed to floating, and that impacts many apps, which will start properly testing these new approaches more frequently once it leaves beta, as more devs and end users will have access. Source: developer.apple.com/documentation/technologyoverviews/adopting-liquid-glass#Navigation In Flutter, the approach is different — the framework draws everything from scratch on the screen as if it were a canvas, using the Skia engine or Impeller in more recent Flutter versions. This ensures your app's appearance will be consistent across all platforms, but it also means visual novelties from the operating system are not automatically inherited. Here at Idopter Labs, we've been through situations where the design looked different between Android and iOS, but with Flutter, these inconsistencies are rare — which is one of the main reasons we chose this stack. ## Shaders: Flutter's Superpower The Flutter team is known for being cautious before implementing new features — each feature needs to work on all supported platforms. So, before adopting something like Liquid Glass, it's necessary to evaluate whether it makes sense for the ecosystem as a whole. After all, Google, Meta, Facebook, and others have their own design systems, often going against what Apple proposes. Even without native support, Flutter offers a powerful alternative: Shaders — small programs that run on the GPU and allow you to create advanced visual effects such as blur, distortion, glass, water, and much more. With GLSL shader support, it's possible to simulate something close to Apple's Liquid Glass in a customized way using Impeller. I created a shader to imitate Liquid Glass and shared the code in this repository. https://github.com/TiagoDanin/Flutter-Liquid-Glass-Example The shader, implemented in the shaders/glass.frag file, uses SDF (Signed Distance Functions) techniques to draw rounded shapes like a modal, applies refraction with different indices for each channel — creating the chromatic aberration effect (a type of glass lens distortion) — and blends everything with the screen background. The secret lies in manipulating textures and simulating how light behaves when passing through glass. The Flutter integration is done in main.dart, using FragmentProgram.fromAsset to load the shader. The result is a card with rounded edges, reflections, and an incredible look — all running performantly thanks to the power of shaders. If you want to try it, just check the repository and observe how the shader is loaded and applied to the widget. The code is open and can be adapted for different needs. ## Native vs. Drawn: Which Is Better? Using native components has its advantages, such as perfect integration with the operating system. However, this also brings risks: a single update to Android or iOS can make the app look strange, lose an effect, or even break. A classic example is elevation, present in Android buttons but absent in iOS native components. In Flutter, since everything is drawn, control is total — ensuring consistency and performance on all platforms. This way, elevation in Flutter exists on both Android and iOS. My opinion? If the goal is to deliver a "premium Apple experience", go with SwiftUI. But if the focus is compatibility, consistency, and fewer headaches with updates, Flutter is the right choice. ## Liquid Glass: Trend or Controversy? Liquid Glass is visually impressive, but it's still a novelty. We don't know if apps beyond Apple's own will adopt it, and discussions about accessibility and readability have already emerged — since translucent backgrounds can make reading difficult, especially for people with low vision. Furthermore, the effect goes against the flat and minimalist trend that dominates the current design of most apps. And outside of a certain nostalgia for Windows Vista (who remembers Aero Glass?), there's nothing like it in other operating systems. --- What do you think about Liquid Glass? Do you think it's here to stay or is it just another passing trend? Leave your opinion in the comments! --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Liquid Glass no Flutter: será que realmente precisamos disso? > Uma reflexão crítica sobre o efeito Liquid Glass no Flutter e sua real necessidade em aplicações móveis. Explorando quando esse recurso agrega valor à experiência do usuário e quando pode ser apenas complexidade desnecessária ao projeto. - HTML version: https://tiagodanin.com/br/post/liquid-glass-flutter-do-we-really-need-it/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-06 - Language: Portuguese - Tags: Flutter, UI/UX, Article - Originally published at: https://www.linkedin.com/pulse/liquid-glass-flutter-ser%C3%A1-que-realmente-precisamos-disso-tiago-danin-zjptf/ ![Liquid Glass no Flutter - será que realmente precisamos disso?](/images/posts/liquid-glass-flutter-sera-que-realmente-precisamos-disso/cover.jpg) Sabe aquele momento em que a Apple lança uma novidade e, de repente, todo mundo só fala disso? Assim foi com o Liquid Glass. Se você acompanhou a WWDC 2025 ou já deu uma olhada no iOS 26, viu aquele efeito novo por todo o sistema operacional de vidro molhado, com blur. Mas será que a gente realmente precisa disso no Flutter? Não demorou para a comunidade começar a comparar: "No React Native, é só atualizar o Xcode e pronto, Liquid Glass no app!". E é verdade, como o React Native usa componentes nativos, se a Apple muda o visual, seu app já acompanha. Parece mágica, mas tem um preço. Muitos apps podem quebrar do nada, por exemplo, a navigation bar do iOS mudou de fixa para flutuante, e isso impactar em diversos apps, que vão começar a realmente testarem essas novas abordagens com mais frequência quando sair do beta, já que mais devs e usuários finais vão ter acesso. Souce: developer.apple.com/documentation/technologyoverviews/adopting-liquid-glass#Navigation No Flutter, a abordagem é diferente, o framework desenha tudo do zero na tela como se fosse um papel, usando o motor Skia ou Impeller nas versões mais recentes do Flutter. Isso garante que o visual do seu app será consistente em todas as plataformas, mas também significa que novidades visuais do sistema operacional não são herdadas automaticamente. Aqui na Idopter Labs, já passamos por situações em que o design ficava diferente entre Android e iOS, mas com Flutter, essas inconsistências são raras, o que é um dos grandes motivos de termos escolhido essa stack. ## Shaders: o superpoder do Flutter O time do Flutter é conhecido por ser cauteloso antes de implementar novidades, cada recurso precisa funcionar em todas as plataformas suportadas. Por isso, antes de adotar algo como o Liquid Glass, é preciso avaliar se faz sentido para o ecossistema como um todo, afinal Google, Meta, Facebook e outros têm seus próprios design systems, muitas vezes na contramão do que a Apple propõe. Mesmo sem suporte nativo, o Flutter oferece uma alternativa poderosa: Shaders, que são pequenos programas que rodam na GPU e permitem criar efeitos visuais avançados, como blur, distorção, vidro, água e muito mais. Com o suporte a shaders GLSL, é possível simular algo próximo ao Liquid Glass da Apple de forma customizada no Impeller. Eu mesmo criei um shader para imitar o Liquid Glass, e compartilhei o código neste repositório. https://github.com/TiagoDanin/Flutter-Liquid-Glass-Example O shader, implementado no arquivo shaders/glass.frag, utiliza técnicas de SDF (Signed Distance Functions) para desenhar formas arredondadas como um modal, aplica refração com diferentes índices para cada canal, criando assim o efeito de chromatic aberration (Um tipo de distorção de lentes de vidro) e mistura tudo com o fundo da tela. O segredo está em manipular as texturas e simular como a luz se comporta ao atravessar um vidro. A integração com o Flutter é feita no main.dart, usando o FragmentProgram.fromAsset para carregar o shader. O resultado é um cartão com bordas arredondadas, reflexos e um visual incrível, tudo rodando de forma performática graças ao poder dos shaders. Se quiser experimentar, basta conferir o repositório e observar como o shader é carregado e aplicado ao widget. O código é aberto e pode ser adaptado para diferentes necessidades. ## Nativo vs. Desenhado: qual o melhor? Usar componentes nativos tem suas vantagens, como integração perfeita com o sistema operacional. No entanto, isso também traz riscos: basta uma atualização no Android ou iOS para o app ficar estranho, perder um efeito ou até quebrar. Um exemplo clássico é o elevation, presente nos botões do Android, mas ausente nos componentes nativos do iOS. No Flutter, como tudo é desenhado, o controle é total, garantindo consistência e performance em todas as plataformas, desse modo o elevation no Flutter existe tanto no Android quanto no iOS. Minha opinião? Se o objetivo é entregar uma "experiência premium Apple", vá de SwiftUI. Mas se o foco é compatibilidade, consistência e menos dor de cabeça com atualizações, Flutter é a escolha certa. ## Liquid Glass: tendência ou polêmica? O Liquid Glass é visualmente impressionante, mas ainda é uma novidade. Não sabemos se outros apps além da Apple vão adotar, e já surgiram discussões sobre acessibilidade e legibilidade, já que fundos translúcidos podem dificultar a leitura, especialmente para quem tem baixa visão. Além disso, o efeito vai na contramão da tendência flat e minimalista que domina o design atual da maioria dos apps, e fora uma certa nostalgia do Windows Vista (quem lembra do Aero Glass?), não há nada parecido em outros sistemas operacionais. --- E você, o que acha do Liquid Glass? Acha que veio pra ficar ou é só mais uma moda passageira? Deixe sua opinião nos comentários! --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Creating an AI Agent in Cursor to Interact with GitLab and Shortcut > In the world of agile development, we talk a lot about productivity, automation, and less repetitive work. But in practice, how many times have you wasted time just to open a merge request? Finding the issue context, writing the description, adding links, titles. - HTML version: https://tiagodanin.com/post/creating-an-ai-agent-in-cursor-to-interact-with-gitlab-and-shortcut/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-05 - Language: English - Tags: AI, GitHub, DevOps, Tools, Article - Originally published at: https://blog.idopterlabs.com.br/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut-4243a7a29bf8 ![Creating an AI Agent in Cursor to Interact with GitLab and Shortcut](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/cover.png) ## Cursor, MCP, and AI Agents: The Combo That Changed My Way of Working Cursor goes far beyond a code editor with "AI chat". It understands the project context, reads and edits files, accesses documentation, runs terminal commands, and allows you to create agents that automate entire tasks. The secret lies in integrations with MCP servers — the Model Context Protocol. It's a "plugins" system that connects Cursor and LLMs to external tools. In the presented case, the GitLab MCP (for repositories) and Shortcut (for stories) are used. With MCP, LLMs can fetch data, run commands, create MRs, and query issues. ![Screenshot of the agent in Cursor](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/cursor-agent.png) The configuration is simple: go to Cursor settings > MCP and add a new server. In the JSON file, put the startup commands: ```json { "mcpServers": { "gitlab": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-gitlab"], "env": { "GITLAB_PERSONAL_ACCESS_TOKEN": "seu_token" } }, "shortcut": { "command": "npx", "args": ["-y", "@shortcut/mcp"], "env": { "SHORTCUT_API_TOKEN": "seu_token" } } } } ``` After restarting Cursor, all available MCPs appear. It's fully adaptable — if you switch to GitHub, just swap the MCP. ![GitLab and Shortcut integration via MCP](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/gitlab-shortcut.png) ## How I Created My Custom Agent (and Why I Can't Live Without It) An AI agent in Cursor is an ally that understands your project, follows personalized instructions, and carries out tasks from start to finish. It runs commands, accesses files, interacts with other tools, and makes decisions based on context. To create an MR-opening agent, go to the chat, click the agents menu, and select "Add custom mode". Enable: - All **Search** features, except **Rules** - **Run** - **MCP**, only **GitLab** and **Shortcut** - **Auto run** The prompt used follows this structure: ```xml Você é um assistente de programação e deve ajudar a criar uma descrição para Merge Request (MR) no Gitlab. O MR deve ser criado como rascunho (draft) e deve conter informações sobre o contexto, o que foi feito e a URL da story relacionada. Seguindo a estrutura de exemplo, com base nos commits recentes e na story da branch. ## Contexto Ao finalizar a primeira parte do onboarding, criação de conta na franco (internal account). Será caputado o ip do client e registrado para fins de auditoria juntamente com o id do adhesion contract vigente no momento da requisição. ## O que foi feito? - Criado plug para capturar o client ip - Criado tabela adhesion_contracts - Criado modulo para consultar e criar o registro contrado de adesão pelo o usuário - Adicionado no fluxo de criação de conta interna o registro da adesão aceitada pelo usuário - Atualizado o fallback_controller ## URL da Story - https://app.shortcut.com/idopterlabs-project-x/story/52460/ Leia o Diff com a Main Branch e resuma as mudanças em até 5 tópicos objetivos. Obtenha o nome da branch e url do repositório com o comando no terminal: git log -5 --oneline && git remote -v Busque a story relacionada usando: shortcut.get-story Monte o titulo do MR em markdown, em Português do Brasil, com o formato: "[sc-xxxx] Nome da story" (Se preciso modifique levemente o título) Monte a descrição do MR em markdown, em Português do Brasil, com os blocos: Contexto, O que foi feito, URL da Story. Se foi solicitado criar um MR e a URL for do Gitlab, crie o MR como draft usando gitlab.create_merge_request, com título: Draft: [sc-xxxx] Nome da story, e a descrição gerada. Mande o resultado no chat. ``` The agent works in the following steps: - Fetches the branch name and repository URL - Queries the story in Shortcut to get the real context - Generates the MR description already in the standard template - Opens the MR in GitLab as a draft A crucial detail: merge requests are always initiated as drafts for review before requesting code review from the team. ![Agent configuration in Cursor](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/screenshot-04.png) ## Real Example: Using the Agent in Practice After finishing a feature and committing, just type in the Cursor chat: "Create my MR". The agent: - Gets the branch and repository - Fetches the story in Shortcut - Assembles the MR description based on commits - Opens the MR in GitLab as a draft In less than 1 minute, everything is ready for review. No need to open tabs or copy and paste. ![Agent opening MR in GitLab](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/screenshot-05.png) ## What Really Makes a Difference - **Less repetitive work:** No need to manually open MRs, search for links, or copy templates - **Standardization:** Every MR follows the same standard, making review and understanding the history easier - **Complete context:** The agent ensures the story, repository, and template are correct, reducing errors - **Adaptable:** Works with GitLab and Shortcut, but can be used with GitHub, Jira, or other tools ![Result of the MR generated by the agent](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/screenshot-06.png) ![Details of the generated MR](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/screenshot-07.png) ## Conclusion Automating MR opening with an AI agent in Cursor improved the development flow. The time savings, process standardization, and elimination of repetitive tasks represent a significant advancement. This is just the beginning — Cursor can be used to run other agents, such as test automation and Flutter screen generation. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Criando um AI Agent no Cursor para interagir com o Gitlab e Shortcut > No mundo do desenvolvimento ágil, falamos muito em produtividade, automação e menos trabalho repetitivo. Mas, na prática, quantas vezes você já perdeu tempo só para abrir um merge request? Buscar o contexto da issue, escrever a descrição, colocar link, título. - HTML version: https://tiagodanin.com/br/post/creating-an-ai-agent-in-cursor-to-interact-with-gitlab-and-shortcut/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-05 - Language: Portuguese - Tags: AI, GitHub, DevOps, Tools, Article - Originally published at: https://blog.idopterlabs.com.br/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut-4243a7a29bf8 ![Criando um AI Agent no Cursor para interagir com o Gitlab e Shortcut](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/cover.png) ## Cursor, MCP e Agentes de IA: o combo que mudou meu jeito de trabalhar O Cursor vai muito além de um editor de código com "chat com IA". Ele entende o contexto do projeto, lê e edita arquivos, acessa documentações, executa comandos no terminal e permite criar agentes que automatizam tarefas inteiras. O segredo está nas integrações com servidores MCP, o Model Context Protocol. É um sistema de "plugins" que conecta o Cursor e as LLMs a ferramentas externas. No caso apresentado, utiliza-se o MCP do GitLab (para repositórios) e do Shortcut (para stories). Com o MCP, as LLMs conseguem buscar dados, executar comandos, criar MRs e consultar issues. ![Screenshot do agente no Cursor](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/cursor-agent.png) A configuração é simples: vá nas configurações do Cursor > MCP e adicione um novo servidor. No arquivo JSON, coloque os comandos de inicialização: ```json { "mcpServers": { "gitlab": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-gitlab"], "env": { "GITLAB_PERSONAL_ACCESS_TOKEN": "seu_token" } }, "shortcut": { "command": "npx", "args": ["-y", "@shortcut/mcp"], "env": { "SHORTCUT_API_TOKEN": "seu_token" } } } } ``` Após reiniciar o Cursor, todos os MCPs disponíveis aparecem. É totalmente adaptável -- se mudar para GitHub, basta trocar o MCP. ![Integração GitLab e Shortcut via MCP](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/gitlab-shortcut.png) ## Como criei meu agente customizado (e por que não vivo mais sem) Um agente de IA no Cursor é um aliado que entende seu projeto, segue instruções personalizadas e realiza tarefas do início ao fim. Ele executa comandos, acessa arquivos, interage com outras ferramentas e toma decisões baseado no contexto. Para criar um agente de abrir MR, vá até o chat, clique no menu de agentes e selecione "Add custom mode". Habilite: - Todos os recursos de **Search**, menos a de **Rules** - **Run** - **MCP**, somente do **Gitlab** e **Shortcut** - **Auto run** O prompt utilizado segue a seguinte estrutura: ```xml Você é um assistente de programação e deve ajudar a criar uma descrição para Merge Request (MR) no Gitlab. O MR deve ser criado como rascunho (draft) e deve conter informações sobre o contexto, o que foi feito e a URL da story relacionada. Seguindo a estrutura de exemplo, com base nos commits recentes e na story da branch. ## Contexto Ao finalizar a primeira parte do onboarding, criação de conta na franco (internal account). Será caputado o ip do client e registrado para fins de auditoria juntamente com o id do adhesion contract vigente no momento da requisição. ## O que foi feito? - Criado plug para capturar o client ip - Criado tabela adhesion_contracts - Criado modulo para consultar e criar o registro contrado de adesão pelo o usuário - Adicionado no fluxo de criação de conta interna o registro da adesão aceitada pelo usuário - Atualizado o fallback_controller ## URL da Story - https://app.shortcut.com/idopterlabs-project-x/story/52460/ Leia o Diff com a Main Branch e resuma as mudanças em até 5 tópicos objetivos. Obtenha o nome da branch e url do repositório com o comando no terminal: git log -5 --oneline && git remote -v Busque a story relacionada usando: shortcut.get-story Monte o titulo do MR em markdown, em Português do Brasil, com o formato: "[sc-xxxx] Nome da story" (Se preciso modifique levemente o título) Monte a descrição do MR em markdown, em Português do Brasil, com os blocos: Contexto, O que foi feito, URL da Story. Se foi solicitado criar um MR e a URL for do Gitlab, crie o MR como draft usando gitlab.create_merge_request, com título: Draft: [sc-xxxx] Nome da story, e a descrição gerada. Mande o resultado no chat. ``` O agente funciona nas seguintes etapas: - Busca o nome da branch e a URL do repositório - Consulta a story no Shortcut para saber o contexto real - Gera a descrição do MR já no template padrão - Abre o MR no Gitlab como draft Um detalhe crucial: os merge requests sempre são iniciados como draft para revisão antes de solicitar code review ao time. ![Configuração do agente no Cursor](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/screenshot-04.png) ## Exemplo real: usando o agente na prática Após terminar uma feature e dar commit, basta digitar no chat do Cursor: "Crie o meu MR". O agente: - Pega a branch e o repositório - Busca a story no Shortcut - Monta a descrição do MR com base nos commits - Abre o MR no Gitlab como draft Em menos de 1 minuto, está tudo pronto para revisão. Não é necessário abrir abas ou copiar e colar. ![Agente abrindo MR no Gitlab](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/screenshot-05.png) ## O que faz diferença de verdade - **Menos trabalho repetitivo:** Não é necessário abrir MR manualmente, buscar link ou copiar template - **Padronização:** Todo MR segue o mesmo padrão, facilitando revisão e entendimento do histórico - **Contexto completo:** O agente garante que a story, repositório e template estão corretos, reduzindo erros - **Adaptável:** Funciona com Gitlab e Shortcut, mas pode ser utilizado com Github, Jira ou outras ferramentas ![Resultado do MR gerado pelo agente](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/screenshot-06.png) ![Detalhes do MR gerado](/images/posts/criando-um-ai-agent-no-cursor-para-interagir-com-o-gitlab-e-shortcut/screenshot-07.png) ## Conclusão Automatizar a abertura de MRs com agente de IA no Cursor melhorou o fluxo de desenvolvimento. O ganho de tempo, padronização do processo e eliminação de tarefas repetitivas representam um avanço significativo. Este é apenas o começo -- o Cursor pode ser utilizado para executar outros agentes, como automação de testes e geração de telas em Flutter. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # The Big Security Problem in SaaS Created with Vibe Coding > SaaS products created in the AI rush are failing at the basics: security. In this video, I show you one of the biggest problems I've been finding in various products made with Vibe Coding-style tools: completely exposed databases, with no minimum security policy. In practice, you'll see how this happens. The problem is serious, easy to exploit, and affects many indie hacker projects who don't even know they're vulnerable. If you're creating a digital product, especially with low-code/no-code tools, this content is for you. - HTML version: https://tiagodanin.com/post/saas-created-with-vibe-coding-security-problem/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-05 - Language: English - Tags: AI, GitHub, Security, Tools, Video - Originally published at: https://www.youtube.com/watch?v=4hD4UGtLWqU SaaS products created in the AI rush are failing at the basics: **security**. In this video, I show one of the biggest problems I've been finding in various products made with Vibe Coding-style tools: completely exposed databases, with no minimum security policy. In practice, you'll see how this happens, why the problem is so serious, and what to do to protect your product. If you're creating a digital product with low-code/no-code tools or AI, this content is essential. [The Big Security Problem in SaaS Created with Vibe Coding](https://www.youtube.com/watch?v=4hD4UGtLWqU) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # O grande problema de segurança nos SaaS criados com Vibe Coding > SaaS criados no embalo da IA estão falhando no básico: segurança. Nesse vídeo, eu te mostro um dos maiores problemas que venho encontrando em vários produtos feitos com ferramentas no estilo Vibe Coding: bancos de dados completamente expostos, sem nenhuma política mínima de segurança. 😱 Na prática, você vai ver como isso acontece. O problema é grave, fácil de explorar e afeta muitos projetos de indie hackers que nem sabem que estão vulneráveis. 🔐 Se você está criando um produto digital, principalmente com ferramentas low-code/no-code, esse conteúdo é pra você. - HTML version: https://tiagodanin.com/br/post/saas-created-with-vibe-coding-security-problem/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-05 - Language: Portuguese - Tags: AI, GitHub, Security, Tools, Video - Originally published at: https://www.youtube.com/watch?v=4hD4UGtLWqU SaaS criados no embalo da IA estão falhando no básico: **segurança**. Nesse vídeo, mostro um dos maiores problemas que venho encontrando em vários produtos feitos com ferramentas no estilo Vibe Coding: bancos de dados completamente expostos, sem nenhuma política mínima de segurança. Na prática, você vai ver como isso acontece, por que o problema é tão grave e o que fazer para proteger o seu produto. Se você está criando um produto digital com ferramentas low-code/no-code ou IA, esse conteúdo é essencial. [O grande problema de segurança nos SaaS criados com Vibe Coding](https://www.youtube.com/watch?v=4hD4UGtLWqU) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Enhancing User Experience with Keyboards on iOS using Keyboard Actions > You'll never struggle with the iOS Keyboard in Flutter again! See how to solve a common problem in mobile apps for iOS that is… - HTML version: https://tiagodanin.com/post/enhancing-user-experience-with-keyboards-on-ios-using-keyboard-actions/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-03 - Language: English - Tags: Flutter, iOS, Mobile, UI/UX, Article - Originally published at: https://medium.com/idopterlabs/aprimorando-a-experiencia-do-usuario-com-teclados-no-ios-usando-keyboard-actions-5f077dd5a1d2 ![Enhancing keyboard experience on iOS with Keyboard Actions](/images/posts/aprimorando-a-experiencia-do-usuario-com-teclados-no-ios-usando-keyboard-actions/cover.png) ## Summary In this article, we'll explore how we solved a common problem in mobile apps for iOS: the absence of the "Done" and "Next" buttons on the iOS numeric keyboard, and we'll also discuss how we implemented an elegant solution using the **keyboard_actions** package. ## The iOS Keyboard Problem Mobile app developers frequently face a frustrating challenge when working with keyboards on iOS, especially with the numeric keyboard, which is a **keyboard without action buttons** that help with navigation flow between form fields. This problem becomes particularly annoying when: 1. Users need to fill out forms with multiple numeric fields; 2. Navigation between fields becomes difficult without a clear way to finish input; 3. The user experience is impaired, especially in data-heavy apps; 4. Difficulty closing the keyboard after typing. We encountered this problem while developing a financial application where users needed to enter values in several numeric fields. The absence of an intuitive way to complete input and navigate between fields was causing friction in the user experience. ## The Solution: Keyboard Actions ![Keyboard Actions - custom action bar on the iOS keyboard](/images/posts/aprimorando-a-experiencia-do-usuario-com-teclados-no-ios-usando-keyboard-actions/keyboard-actions.png) We found a robust solution using the **keyboard_actions** package. This library allowed us to create a custom action bar that floats above the keyboard, offering navigation buttons and an explicit "Done" button. First we need to add the dependency to our project. In the **pubspec.yaml** file: ```dart dependencies: flutter: sdk: flutter keyboard_actions: ^4.2.0 # Versão no momento da criação deste post ``` Before creating our encapsulated solution, let's understand how the **keyboard_actions** package works directly. Here's what a basic implementation for a simple form would look like: ```dart // ... // class _FormularioBasicoScreenState extends State { final FocusNode _valorFocusNode = FocusNode(); final FocusNode _quantidadeFocusNode = FocusNode(); final FocusNode _observacaoFocusNode = FocusNode(); final TextEditingController _valorController = TextEditingController(); final TextEditingController _quantidadeController = TextEditingController(); final TextEditingController _observacaoController = TextEditingController(); KeyboardActionsConfig _buildConfig(BuildContext context) { return KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.ALL, keyboardBarColor: Colors.grey[200], nextFocus: true, actions: [ KeyboardActionsItem( focusNode: _valorFocusNode, displayArrows: true, displayDoneButton: false, toolbarButtons: [ (node) { return GestureDetector( onTap: () => _quantidadeFocusNode.requestFocus(), child: Text("NEXT"), ); }, ], ), KeyboardActionsItem( focusNode: _quantidadeFocusNode, displayArrows: true, displayDoneButton: false, toolbarButtons: [ (node) { return GestureDetector( onTap: () => _observacaoFocusNode.requestFocus(), child: Text("NEXT"), ); }, ], ), KeyboardActionsItem( focusNode: _observacaoFocusNode, displayArrows: true, displayDoneButton: false, toolbarButtons: [ (node) { return GestureDetector( onTap: () => _submitForm(), child: Text("DONE"), ); }, ], ), ], ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Formulário Básico")), body: KeyboardActions( config: _buildConfig(context), child: Padding( padding: EdgeInsets.all(16.0), child: Column( children: [ TextFormField( focusNode: _valorFocusNode, controller: _valorController, decoration: InputDecoration(labelText: "Valor"), keyboardType: TextInputType.numberWithOptions(decimal: true), ), // ... // ], ), ), ), ); } } ``` As we can see, this basic approach works, but presents several problems: 1. **Repetitive code**: We need to manually define each button ("Next" and "Done") in the toolbar for each field. 2. **Hard to maintain**: If we add or remove fields, we need to manually redo all connections between focus nodes. 3. **Lack of reuse**: The navigation logic between fields needs to be reimplemented in every form in the app. 4. **Visual consistency**: It's difficult to ensure that button appearance is consistent across all forms. 5. **Growing complexity**: Complexity increases exponentially with the number of fields in the form. ## Why Encapsulate? Facing these challenges, we decided to encapsulate the **keyboard_actions** logic in a class, which provided us with several benefits: 1. **Code reuse**: We can use the same solution across all forms in the app. 2. **Simplified maintenance**: Updates to the keyboard interface are made in a single place. 3. **Visual consistency**: We ensure that all forms have the same appearance and behavior. 4. **Ease of use**: We reduce the amount of boilerplate code needed to implement the keyboard toolbar. 5. **Adaptability**: Our solution automatically adapts to the number of fields in the form. Next, we developed a reusable controller to manage keyboard actions consistently throughout the app. This controller became a crucial component that we now use in all our forms. ## Implementing the Keyboard Actions Controller We created a utility class called **KeyboardActionsController** that encapsulates all the logic needed to configure the custom action bar. This class generates configurations for the **KeyboardActions** widget based on a list of focus nodes and a callback for when the user finishes input. ```dart class KeyboardActionsController { static List _buildKeyboardItems( BuildContext context, List focusNodesSteps, VoidCallback? doneCallback, ) { return List.generate( focusNodesSteps.length, (index) { void previousStep() { focusNodesSteps[index - 1].requestFocus(); } void nextStep() { focusNodesSteps[index + 1].requestFocus(); } void doneStep() { focusNodesSteps[index].unfocus(); doneCallback?.call(); } return KeyboardActionsItem( focusNode: focusNodesSteps[index], displayDoneButton: false, displayArrows: false, toolbarButtons: [ (node) { return IconButton( icon: const Icon(Icons.close), tooltip: "Close", iconSize: IconTheme.of(context).size, color: IconTheme.of(context).color, onPressed: node.unfocus, ); }, (_) { return IconButton( icon: const Icon(Icons.keyboard_arrow_up), tooltip: "Previous", iconSize: IconTheme.of(context).size, color: IconTheme.of(context).color, disabledColor: Theme.of(context).disabledColor, onPressed: index > 0 ? previousStep : null, ); }, (_) { return IconButton( icon: const Icon(Icons.keyboard_arrow_down), tooltip: "Next", iconSize: IconTheme.of(context).size, color: IconTheme.of(context).color, disabledColor: Theme.of(context).disabledColor, onPressed: index < focusNodesSteps.length - 1 ? nextStep : null, ); }, (_) => const Spacer(), (node) { return Padding( padding: const EdgeInsets.all(4.0), child: TextButton( onPressed: index < focusNodesSteps.length - 1 ? nextStep : doneStep, child: Text( index < focusNodesSteps.length - 1 ? "NEXT" : "DONE", style: TextStyle( color: IconTheme.of(context).color, ), ), ), ); }, ], ); }, ); } static KeyboardActionsConfig buildConfigKeyboardActions({ required BuildContext context, required List focusNodesSteps, VoidCallback? doneCallback, }) { return KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.ALL, keyboardBarColor: Colors.grey[200], nextFocus: true, actions: _buildKeyboardItems(context, focusNodesSteps, doneCallback), ); } } ``` ## Implementation in a Real Case In our financial app, we implemented this solution in a form for creating stock price alerts. Here's how we integrated the **KeyboardActionsController** in the real flow: ```dart // Controllers for text fields final TextEditingController valorController = TextEditingController(); final TextEditingController quantidadeController = TextEditingController(); final TextEditingController observacaoController = TextEditingController(); // FocusNodes for each text field final List focusNodes = List.generate(3, (_) => FocusNode()); void submitForm() { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Formulário enviado com sucesso!"), duration: Duration(seconds: 2), ), ); } @override Widget build(BuildContext context) { return Scaffold( body: KeyboardActions( config: KeyboardActionsController.buildConfigKeyboardActions( context: context, focusNodesSteps: focusNodes, doneCallback: submitForm, ), child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 24), TextFormField( focusNode: focusNodes[0], controller: valorController, decoration: const InputDecoration( hintText: "Digite o valor", ), keyboardType: const TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.next, ), const SizedBox(height: 16), TextFormField( focusNode: focusNodes[1], controller: quantidadeController, decoration: const InputDecoration( hintText: "Digite a quantidade", ), keyboardType: const TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.next, ), const SizedBox(height: 16), TextFormField( focusNode: focusNodes[2], controller: observacaoController, decoration: const InputDecoration( hintText: "Digite uma observação", ), textInputAction: TextInputAction.done, ), const SizedBox(height: 24), Center( child: ElevatedButton( onPressed: submitForm, child: const Text("ENVIAR FORMULÁRIO"), ), ), ], ), ), ), ); } ``` Full implementation at [github.com/TiagoDanin/keyboard_actions_example](https://github.com/TiagoDanin/keyboard_actions_example/blob/main/lib/custom_keyboad_actions_example.dart). ## Results Achieved ![Demo of keyboard behavior on iOS with Keyboard Actions](/images/posts/aprimorando-a-experiencia-do-usuario-com-teclados-no-ios-usando-keyboard-actions/demo.gif) After implementing this solution, we observed significant improvements in user experience: 1. Intuitive navigation between fields accelerated the data entry process; 2. Android and iOS users now have a uniform experience, regardless of native keyboard differences; 3. Allows closing the keyboard after typing. The key to success is the correct configuration of **FocusNodes** and the customization of toolbar buttons. In our controller, the navigation buttons are intelligently enabled or disabled based on the current focus position. ## Conclusion User experience in mobile apps is often determined by seemingly small details, such as ease of form filling. Our **KeyboardActionsController** solved a specific iOS problem that was impairing the user experience in our Flutter mobile apps. This solution demonstrates how we can overcome native platform limitations while maintaining a consistent, high-quality user experience across all devices. The code we shared is flexible and adaptable for different projects and scenarios, making it a valuable tool in any team's Flutter development toolkit. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Aprimorando a Experiência do Usuário com Teclados no iOS usando Keyboard Actions > Você nunca mais vai lutar contra o Teclado do iOS no Flutter! Veja como resolver um problema comum em aplicativos mobile para iOS que é… - HTML version: https://tiagodanin.com/br/post/enhancing-user-experience-with-keyboards-on-ios-using-keyboard-actions/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-03 - Language: Portuguese - Tags: Flutter, iOS, Mobile, UI/UX, Article - Originally published at: https://medium.com/idopterlabs/aprimorando-a-experiencia-do-usuario-com-teclados-no-ios-usando-keyboard-actions-5f077dd5a1d2 ![Aprimorando teclados no iOS com Keyboard Actions](/images/posts/aprimorando-a-experiencia-do-usuario-com-teclados-no-ios-usando-keyboard-actions/cover.png) ## Resumo Neste artigo, vamos explorar como resolvemos um problema comum em aplicativos mobile para iOS: a ausência do botão "Done" (Concluído) e "Next" (Próximo) no teclado numérico do iOS, e também falaremos sobre como implementamos uma solução elegante utilizando o pacote **keyboard_actions**. ## O Problema do Teclado iOS Desenvolvedores de aplicativos mobile frequentemente enfrentam um desafio frustrante ao trabalhar com teclados no iOS, especialmente com teclado numérico, que é um **teclado sem botões de ação** que ajudam no fluxo de navegação entre os campos de um formulário. Este problema torna-se particularmente irritante quando: 1. Os usuários precisam preencher formulários com múltiplos campos numéricos; 2. A navegação entre campos se torna difícil sem uma maneira clara de finalizar a entrada; 3. A experiência do usuário é prejudicada, especialmente em aplicativos com muita entrada de dados; 4. Dificuldade para fechar o teclado após a digitação. Nos deparamos com esse problema durante o desenvolvimento de um aplicativo financeiro onde os usuários precisavam inserir valores em vários campos numéricos. A ausência de uma forma intuitiva de concluir a entrada e navegar entre campos estava causando atritos na experiência do usuário. ## A Solução: Keyboard Actions ![Keyboard Actions - barra de ações personalizada no teclado iOS](/images/posts/aprimorando-a-experiencia-do-usuario-com-teclados-no-ios-usando-keyboard-actions/keyboard-actions.png) Encontramos uma solução robusta utilizando o pacote **keyboard_actions**. Esta biblioteca nos permitiu criar uma barra de ações personalizada que flutua sobre o teclado, oferecendo botões de navegação e um botão "Done" (Concluir) explícito. Primeiro precisamos adicionar a dependência ao nosso projeto. No arquivo **pubspec.yaml**: ```dart dependencies: flutter: sdk: flutter keyboard_actions: ^4.2.0 # Versão no momento da criação deste post ``` Antes de criar nossa solução encapsulada, vamos entender como o pacote **keyboard_actions** funciona diretamente. Veja como seria a implementação básica para um formulário simples: ```dart // ... // class _FormularioBasicoScreenState extends State { final FocusNode _valorFocusNode = FocusNode(); final FocusNode _quantidadeFocusNode = FocusNode(); final FocusNode _observacaoFocusNode = FocusNode(); final TextEditingController _valorController = TextEditingController(); final TextEditingController _quantidadeController = TextEditingController(); final TextEditingController _observacaoController = TextEditingController(); KeyboardActionsConfig _buildConfig(BuildContext context) { return KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.ALL, keyboardBarColor: Colors.grey[200], nextFocus: true, actions: [ KeyboardActionsItem( focusNode: _valorFocusNode, displayArrows: true, displayDoneButton: false, toolbarButtons: [ (node) { return GestureDetector( onTap: () => _quantidadeFocusNode.requestFocus(), child: Text("NEXT"), ); }, ], ), KeyboardActionsItem( focusNode: _quantidadeFocusNode, displayArrows: true, displayDoneButton: false, toolbarButtons: [ (node) { return GestureDetector( onTap: () => _observacaoFocusNode.requestFocus(), child: Text("NEXT"), ); }, ], ), KeyboardActionsItem( focusNode: _observacaoFocusNode, displayArrows: true, displayDoneButton: false, toolbarButtons: [ (node) { return GestureDetector( onTap: () => _submitForm(), child: Text("DONE"), ); }, ], ), ], ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Formulário Básico")), body: KeyboardActions( config: _buildConfig(context), child: Padding( padding: EdgeInsets.all(16.0), child: Column( children: [ TextFormField( focusNode: _valorFocusNode, controller: _valorController, decoration: InputDecoration(labelText: "Valor"), keyboardType: TextInputType.numberWithOptions(decimal: true), ), // ... // ], ), ), ), ); } } ``` Como podemos ver, essa abordagem básica funciona, mas apresenta vários problemas: 1. **Código repetitivo**: Precisamos definir manualmente cada botão ("Next" e "Done") da barra de ferramentas para cada campo. 2. **Difícil manutenção**: Se adicionarmos ou removermos campos, precisamos refazer manualmente todas as conexões entre focus nodes. 3. **Falta de reutilização**: A lógica de navegação entre campos precisa ser reimplementada em cada formulário do aplicativo. 4. **Consistência visual**: É difícil garantir que a aparência dos botões seja consistente em todos os formulários. 5. **Complexidade crescente**: A complexidade aumenta exponencialmente com o número de campos no formulário. ## Por que Encapsular? Diante desses desafios, decidimos encapsular a lógica do **keyboard_actions** em uma classe, isso nos proporcionou diversos benefícios: 1. **Reutilização de código**: Podemos usar a mesma solução em todos os formulários do aplicativo. 2. **Manutenção simplificada**: Atualizações na interface do teclado são feitas em um único lugar. 3. **Consistência visual**: Garantimos que todos os formulários tenham a mesma aparência e comportamento. 4. **Facilidade de uso**: Reduzimos a quantidade de código boilerplate necessário para implementar a barra de ferramentas do teclado. 5. **Adaptabilidade**: Nossa solução se adapta automaticamente ao número de campos do formulário. Em seguida, desenvolvemos um controlador reutilizável para gerenciar as ações do teclado consistentemente em todo o aplicativo. Esse controlador se tornou um componente crucial que agora usamos em todos os nossos formulários. ## Implementado o Keyboard Actions Controller Criamos uma classe utilitária chamada **KeyboardActionsController** que encapsula toda a lógica necessária para configurar a barra de ações personalizada. Esta classe gera configurações para o widget **KeyboardActions** com base em uma lista de nós de foco e um callback para quando o usuário finaliza a entrada. ```dart class KeyboardActionsController { static List _buildKeyboardItems( BuildContext context, List focusNodesSteps, VoidCallback? doneCallback, ) { return List.generate( focusNodesSteps.length, (index) { void previousStep() { focusNodesSteps[index - 1].requestFocus(); } void nextStep() { focusNodesSteps[index + 1].requestFocus(); } void doneStep() { focusNodesSteps[index].unfocus(); doneCallback?.call(); } return KeyboardActionsItem( focusNode: focusNodesSteps[index], displayDoneButton: false, displayArrows: false, toolbarButtons: [ (node) { return IconButton( icon: const Icon(Icons.close), tooltip: "Close", iconSize: IconTheme.of(context).size, color: IconTheme.of(context).color, onPressed: node.unfocus, ); }, (_) { return IconButton( icon: const Icon(Icons.keyboard_arrow_up), tooltip: "Previous", iconSize: IconTheme.of(context).size, color: IconTheme.of(context).color, disabledColor: Theme.of(context).disabledColor, onPressed: index > 0 ? previousStep : null, ); }, (_) { return IconButton( icon: const Icon(Icons.keyboard_arrow_down), tooltip: "Next", iconSize: IconTheme.of(context).size, color: IconTheme.of(context).color, disabledColor: Theme.of(context).disabledColor, onPressed: index < focusNodesSteps.length - 1 ? nextStep : null, ); }, (_) => const Spacer(), (node) { return Padding( padding: const EdgeInsets.all(4.0), child: TextButton( onPressed: index < focusNodesSteps.length - 1 ? nextStep : doneStep, child: Text( index < focusNodesSteps.length - 1 ? "NEXT" : "DONE", style: TextStyle( color: IconTheme.of(context).color, ), ), ), ); }, ], ); }, ); } static KeyboardActionsConfig buildConfigKeyboardActions({ required BuildContext context, required List focusNodesSteps, VoidCallback? doneCallback, }) { return KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.ALL, keyboardBarColor: Colors.grey[200], nextFocus: true, actions: _buildKeyboardItems(context, focusNodesSteps, doneCallback), ); } } ``` ## Implementação em um Caso Real Em nosso aplicativo financeiro, implementamos esta solução em um formulário de criação de alertas de preço para ações. Veja como integramos o **KeyboardActionsController** no fluxo real: ```dart // Controladores para os campos de texto final TextEditingController valorController = TextEditingController(); final TextEditingController quantidadeController = TextEditingController(); final TextEditingController observacaoController = TextEditingController(); // FocusNodes para cada campo de texto final List focusNodes = List.generate(3, (_) => FocusNode()); void submitForm() { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Formulário enviado com sucesso!"), duration: Duration(seconds: 2), ), ); } @override Widget build(BuildContext context) { return Scaffold( body: KeyboardActions( config: KeyboardActionsController.buildConfigKeyboardActions( context: context, focusNodesSteps: focusNodes, doneCallback: submitForm, ), child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 24), TextFormField( focusNode: focusNodes[0], controller: valorController, decoration: const InputDecoration( hintText: "Digite o valor", ), keyboardType: const TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.next, ), const SizedBox(height: 16), TextFormField( focusNode: focusNodes[1], controller: quantidadeController, decoration: const InputDecoration( hintText: "Digite a quantidade", ), keyboardType: const TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.next, ), const SizedBox(height: 16), TextFormField( focusNode: focusNodes[2], controller: observacaoController, decoration: const InputDecoration( hintText: "Digite uma observação", ), textInputAction: TextInputAction.done, ), const SizedBox(height: 24), Center( child: ElevatedButton( onPressed: submitForm, child: const Text("ENVIAR FORMULÁRIO"), ), ), ], ), ), ), ); } ``` Implementação completa em [github.com/TiagoDanin/keyboard_actions_example](https://github.com/TiagoDanin/keyboard_actions_example/blob/main/lib/custom_keyboad_actions_example.dart). ## Resultados Obtidos ![Demo do comportamento do teclado no iOS com Keyboard Actions](/images/posts/aprimorando-a-experiencia-do-usuario-com-teclados-no-ios-usando-keyboard-actions/demo.gif) Após implementar esta solução, observamos melhorias significativas na experiência do usuário: 1. A navegação intuitiva entre campos acelerou o processo de entrada de dados; 2. Usuários de Android e iOS agora têm uma experiência uniforme, independentemente das diferenças nativas dos teclados; 3. Permite fechar o teclado após a digitação. A chave para o sucesso é a configuração correta dos **FocusNodes** e a personalização dos botões da barra de ferramentas. Em nosso controlador, os botões de navegação são habilitados ou desabilitados de forma inteligente com base na posição atual do foco. ## Conclusão A experiência do usuário em aplicativos móveis muitas vezes é determinada por detalhes aparentemente pequenos, como a facilidade de preenchimento de formulários. Nosso **KeyboardActionsController** resolveu um problema específico do iOS que estava prejudicando a experiência dos usuários de nossos aplicativos mobile em Flutter. Esta solução demonstra como podemos superar as limitações das plataformas nativas enquanto mantemos uma experiência de usuário consistente e de alta qualidade em todos os dispositivos. O código que compartilhamos é flexível e adaptável para diferentes projetos e cenários, o que o torna uma ferramenta valiosa no kit de desenvolvimento Flutter de qualquer equipe. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Creating an Image Editor with Flutter - CAM Covers (From Idea to Reality) > How I created an image editing app with Flutter. See the challenges, solutions, and monetization of CAM Covers, an album cover editor. - HTML version: https://tiagodanin.com/post/creating-an-image-editor-with-flutter-cam-covers-from-idea-to-reality/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-03 - Language: English - Tags: Flutter, iOS, Mobile, Article - Originally published at: https://www.linkedin.com/pulse/criando-um-editor-de-imagem-com-flutter-cam-covers-da-tiago-danin-xxarf ![CAM Covers - image editor with Flutter](/images/posts/criando-um-editor-de-imagem-com-flutter-cam-covers-da-ideia-a-realidade/cover.jpg) In today's digital world, trends emerge and spread rapidly through social media. Some time ago, I noticed a common trend on Instagram, where people were transforming their photos into music album covers, as if they were artists launching their own albums — the aesthetic of music players like Spotify, combined with personal photos, went viral on social networks. One day I tried to create one of these montages manually and discovered that the process was surprisingly complex. It required using Instagram stickers, manually adjusting elements, and performing a series of edits that demanded time and skill. That's when I had an idea: why not create an app that automates the entire process? Thus CAM Covers was born — an app for transforming ordinary photos into professional-looking album covers, inspired by real music players. ## The Project Vision My vision for CAM Covers was clear: create a tool that would allow anyone, regardless of their design skills, to transform their photos into professional-looking album covers in just a few taps. The app's flow would be simple and intuitive: 1. Select a music player template that matches your photo 2. Customize elements like title, artist name, and other details 3. Publish directly to Instagram or other social networks It seemed simple in theory, but the implementation brought interesting challenges that I needed to overcome — and that's exactly what I'll share with you in this article. ## Challenge 1: Creating an Editable Canvas The first major challenge was creating an editable canvas where users could preview their creations in real time while making adjustments. Initially, I tested various approaches, including existing image editing libraries, but none offered the flexibility I needed. That's when I had an idea — use the phone's screen itself as the canvas. The problem is that each device has a different screen, so the solution came through Flutter's AspectRatio widget, which allowed me to maintain a consistent ratio (9:16) regardless of the device's screen size: ```dart AspectRatio( aspectRatio: 9 / 16, child: buildTemplate(context, viewModel), ), ``` Inside this container I could build a real-time preview of the template being edited. When the user finished their edits, I captured the view using RenderRepaintBoundary, which allowed me to convert the UI into an image — one of the things I love most about Flutter is the amount of control we have over the UI; we can capture the entire UI and convert it into an image, it's incredible. ```dart final RenderRepaintBoundary boundary = globalRepaintBoundaryKey.currentContext!.findRenderObject() as RenderRepaintBoundary; final ui.Image image = await boundary.toImage(pixelRatio: 3.0); final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); final Uint8List pngBytes = byteData!.buffer.asUint8List(); final String directory = (await getExternalStorageDirectory())!.path; final File imgFile = File("$directory/cam-covers.png"); await imgFile.writeAsBytes(pngBytes); await GallerySaver.saveImage( imgFile.path, albumName: "CAM Covers", ); ``` This approach was a true revelation! Instead of creating a complex image editing system, I leveraged Flutter's own rendering system to create the editing experience and capture the final result as an image ready for sharing. ## Challenge 2: Implementing a Monetization Model As any independent developer knows, monetizing an app can be a significant challenge. I decided to implement a freemium model with a paywall to unlock premium features, such as ad-free viewing. After researching various solutions, I found Superwall, which made implementing the paywall surprisingly simple. The basic configuration was straightforward: ```dart Superwall.configure(ConfigBase.superwallApiKey); ``` One of the main advantages of Superwall is the ability to edit campaigns without releasing app updates. This allowed me to: - Run A/B tests to optimize conversion - Adjust prices and offers in real time - Customize the paywall experience using a visual editor I integrated event analytics to trigger the paywall at strategic moments in the user journey: ```dart Superwall.shared.registerEvent( eventName, params: parameters, feature: () { callbackContinue(); }, ); ``` This approach allowed me to offer immediate value to free users while presenting premium features at strategic moments in the user experience. One of the advantages of Superwall is that it's very easy to set up and use, in addition to having a visual interface for editing campaigns with ready-made campaigns, at a very affordable price. ## Challenge 3: Creating Diverse Templates with the Help of AI The third challenge arose when I realized I needed a large variety of templates to offer attractive options to users. Creating each template manually would be extremely time-consuming — the first two templates took me approximately 2 hours each. Instead of spending hours creating templates, I decided to experiment with an innovative approach: use AI to accelerate the process. I used Claude 3.5 through Cursor to analyze the two templates I had already created and generate four more based on the same design principles. The process was surprisingly efficient. I trained the chat to understand the structure and style of the existing templates and then asked it to create variations following the same aesthetic, but with different elements. The result was impressive: I managed to save approximately 8 hours of work that I would have spent creating the additional templates manually. ## Final Result After overcoming these challenges, CAM Covers finally came to life. The app now allows anyone to: - Transform their photos into professional-looking album covers - Choose between different music player styles - Customize titles, artist names, and decorative elements - Instantly share their creations on social networks What started as a solution for a personal need turned into a complete app. I learned a lot from this project, because when we think of an image editor, we immediately think of a complex and time-consuming solution to build, especially natively — but with Flutter it was possible to create an app with an incredible experience at a very low cost. If you want to try CAM Covers, it's available for download on [Google Play](https://play.google.com/store/apps/details?id=com.tiagodanin.camcovers.cam_covers). Transform your memories into music-inspired masterpieces and share your creations with the world! I hope you enjoyed the article and that it helps you understand a little more about how to create products with Flutter. Until next time! --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Criando um editor de imagem com Flutter - CAM Covers (Da Ideia à Realidade) > Como criei um app de edição de imagens com Flutter. Veja os desafios, soluções e a monetização do CAM Covers, um editor de capas de álbuns. - HTML version: https://tiagodanin.com/br/post/creating-an-image-editor-with-flutter-cam-covers-from-idea-to-reality/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2025-03 - Language: Portuguese - Tags: Flutter, iOS, Mobile, Article - Originally published at: https://www.linkedin.com/pulse/criando-um-editor-de-imagem-com-flutter-cam-covers-da-tiago-danin-xxarf ![CAM Covers - editor de imagens com Flutter](/images/posts/criando-um-editor-de-imagem-com-flutter-cam-covers-da-ideia-a-realidade/cover.jpg) No mundo digital de hoje, tendências surgem e se espalham rapidamente pelas redes sociais. Há algum tempo atrás, notei uma treading comum no Instagram, onde as pessoas transformando suas fotos em capas de álbuns de música, como se fossem artistas lançando seus próprios álbuns, na estética de players de música como Spotify, combinada com fotos pessoais, que viralizavam nas redes sociais. Um certo dia eu tentei criar uma dessas montagens manualmente e descobri que o processo era surpreendentemente complexo. Era necessário usar stickers do Instagram, ajustar elementos manualmente e realizar uma série de edições que demandavam tempo e habilidade. Foi então que tive uma ideia: por que não criar um aplicativo que automatizasse todo esse processo? Assim nasceu o CAM Covers, um aplicativo para transformar fotos comuns em capas de álbuns com aparência profissional, inspiradas em players de música reais. ## A Visão do Projeto Minha visão para o CAM Covers era clara: criar uma ferramenta que permitisse a qualquer pessoa, independentemente de suas habilidades de design, transformar suas fotos em capas de álbuns com aparência profissional em apenas alguns toques. O fluxo do aplicativo seria simples e intuitivo: 1. Selecionar um modelo de player de música que combine com sua foto 2. Personalizar os elementos como título, nome do artista e outros detalhes 3. Publicar diretamente no Instagram ou outras redes sociais Parecia simples na teoria, mas a implementação trouxe desafios interessantes que precisei superar e é justamente isso que vou compartilhar com vocês neste artigo. ## Desafio 1: Criando um Canvas Editável O primeiro grande desafio foi criar um canvas editável onde os usuários pudessem visualizar suas criações em tempo real enquanto faziam ajustes. Inicialmente, testei várias abordagens, incluindo bibliotecas de edição de imagem existentes, mas nenhuma oferecia a flexibilidade que eu precisava. Foi então que tive uma ideia, usar a própria tela do celular como canvas. O problema é que cada dispositivo tem uma tela diferente, então a solução veio através do widget AspectRatio do Flutter, que me permitiu manter uma proporção consistente (9:16) independentemente do tamanho da tela do dispositivo: ```dart AspectRatio( aspectRatio: 9 / 16, child: buildTemplate(context, viewModel), ), ``` Dentro deste container eu podia construir uma visualização em tempo real do template sendo editado. Quando o usuário terminava suas edições, capturava a visualização usando RenderRepaintBoundary, que me permitia converter a UI em uma imagem, uma das partes que mais gosto no Flutter é a quantidade de controle que temos sobre a UI, podemos capturar a UI inteira e convertê-la em uma imagem, é incrível. ```dart final RenderRepaintBoundary boundary = globalRepaintBoundaryKey.currentContext!.findRenderObject() as RenderRepaintBoundary; final ui.Image image = await boundary.toImage(pixelRatio: 3.0); final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); final Uint8List pngBytes = byteData!.buffer.asUint8List(); final String directory = (await getExternalStorageDirectory())!.path; final File imgFile = File("$directory/cam-covers.png"); await imgFile.writeAsBytes(pngBytes); await GallerySaver.saveImage( imgFile.path, albumName: "CAM Covers", ); ``` Esta abordagem foi uma verdadeira revelação! Ao invés de criar um sistema complexo de edição de imagens, aproveitei o próprio sistema de renderização do Flutter para criar a experiência de edição e capturar o resultado final como uma imagem pronta para compartilhamento. ## Desafio 2: Implementando um Modelo de Monetização Como qualquer desenvolvedor independente sabe, monetizar um aplicativo pode ser um desafio significativo. Decidi implementar um modelo freemium com um paywall para desbloquear recursos premium, como não visualizar anúncios. Após pesquisar várias soluções, encontrei o Superwall, que tornou a implementação do paywall surpreendentemente simples. A configuração básica foi direta: ```dart Superwall.configure(ConfigBase.superwallApiKey); ``` Uma das principais vantagens do Superwall é a capacidade de editar campanhas sem lançar atualizações do aplicativo. Isso me permitiu: - Realizar testes A/B para otimizar a conversão - Ajustar preços e ofertas em tempo real - Personalizar a experiência do paywall usando um editor visual Integrei análises de eventos para acionar o paywall em momentos estratégicos da jornada do usuário: ```dart Superwall.shared.registerEvent( eventName, params: parameters, feature: () { callbackContinue(); }, ); ``` Esta abordagem permitiu que eu oferecesse valor imediato aos usuários gratuitos enquanto apresentava recursos premium em momentos estratégicos da experiência do usuário. Uma das vantagens do Superwall é que ele é muito fácil de configurar e usar, além de ter uma interface visual para editar as campanhas com campanhas já prontas para uso, é o preço da ferramenta que é bem baixo. ## Desafio 3: Criando Templates Diversos com a Ajuda da IA O terceiro desafio surgiu quando percebi que precisava de uma grande variedade de templates para oferecer opções atraentes aos usuários. Criar cada template manualmente seria extremamente demorado - os dois primeiros templates me tomaram aproximadamente 2 horas cada. Em vez de gastar horas criando templates, decidi experimentar uma abordagem inovadora: usar IA para acelerar o processo. Utilizei o Claude 3.5 através do Cursor para analisar os dois templates que já havia criado e gerar outros quatro baseados nos mesmos princípios de design. O processo foi surpreendentemente eficiente. Treinei o chat para compreender a estrutura e o estilo dos templates existentes e, em seguida, solicitei que criasse variações seguindo a mesma estética, mas com elementos diferentes. O resultado foi impressionante: consegui economizar aproximadamente 8 horas de trabalho que teria gasto criando os templates adicionais manualmente. ## Resultado Final Após superar esses desafios, o CAM Covers finalmente ganhou vida. O aplicativo agora permite que qualquer pessoa: - Transforme suas fotos em capas de álbuns com aparência profissional - Escolha entre diferentes estilos de player de música - Personalize títulos, nomes de artistas e elementos decorativos - Compartilhe instantaneamente suas criações nas redes sociais O que começou como uma solução para uma necessidade pessoal se transformou em um aplicativo completo, aprendi bastante com este projeto, pois quando pensamos em editor de imagem, pensamos logo em uma solução complexa e demorada de ser feita e trabalhando com Nativo, mas com Flutter acabou sendo possível criar um aplicativo com uma experiência incrível e com um custo muito baixo. Se você quiser experimentar o CAM Covers, ele está disponível para download na [Google Play](https://play.google.com/store/apps/details?id=com.tiagodanin.camcovers.cam_covers). Transforme suas memórias em obras-primas inspiradas na música e compartilhe suas criações com o mundo! Espero que você tenha gostado do artigo e que ele te ajude a entender um pouco mais sobre como criar produtos com Flutter. Até a próxima! --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Managing Certificates and Profiles in the App Store with Fastlane Match > How to set up Fastlane Match from scratch to centralize and sync App Store certificates and provisioning profiles across a team of several developers. - HTML version: https://tiagodanin.com/post/managing-certificates-and-profiles-in-the-app-store-with-fastlane-match/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2024-03 - Language: English - Tags: Mobile, iOS, DevOps, Tools, Video - Originally published at: https://www.youtube.com/watch?v=uKe63jYwE1w Managing App Store certificates and provisioning profiles can be a nightmare — especially in teams with multiple developers. **Fastlane Match** solves this problem elegantly, centralizing and synchronizing all certificates automatically. In this video, you'll learn how to configure Match from scratch and definitively simplify this process. [Managing Certificates and Profiles in the App Store with Fastlane Match](https://www.youtube.com/watch?v=uKe63jYwE1w) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Gerenciando Certificados e Perfis Na App Store com Fastlane Match > Como configurar o Fastlane Match do zero para centralizar e sincronizar certificados e perfis de provisionamento da App Store num time com vários devs. - HTML version: https://tiagodanin.com/br/post/managing-certificates-and-profiles-in-the-app-store-with-fastlane-match/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2024-03 - Language: Portuguese - Tags: Mobile, iOS, DevOps, Tools, Video - Originally published at: https://www.youtube.com/watch?v=uKe63jYwE1w Gerenciar certificados e perfis de provisionamento da App Store pode ser um pesadelo — especialmente em times com múltiplos desenvolvedores. O **Fastlane Match** resolve esse problema de forma elegante, centralizando e sincronizando todos os certificados automaticamente. Neste vídeo, você vai aprender como configurar o Match do zero e simplificar definitivamente esse processo. [Gerenciando Certificados e Perfis Na App Store com Fastlane Match](https://www.youtube.com/watch?v=uKe63jYwE1w) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Android Splash Screen in Jetpack Compose: Building an Impactful Intro for Your App > In the world of mobile apps, first impressions are crucial. The Splash Screen is the first screen the user sees when opening your app. In this video, we'll create an amazing Splash Screen for our app using Jetpack Compose. - HTML version: https://tiagodanin.com/post/android-splash-screen-in-jetpack-compose-building-an-impactful-intro-for-your-app/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2024-02 - Language: English - Tags: Android, Mobile, UI/UX, Video - Originally published at: https://www.youtube.com/watch?v=GLBY4YHoVFk In the world of mobile apps, first impressions are crucial. The Splash Screen is the first screen the user sees when opening your app — and it has the power to enchant or drive users away from the very first second. In this video, we'll create an amazing Splash Screen using Jetpack Compose, exploring modern Android APIs to build an impactful and professional introduction for your app. [Android Splash Screen in Jetpack Compose: Building an Impactful Intro for Your App](https://www.youtube.com/watch?v=GLBY4YHoVFk) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Android Splash Screen em Jetpack Compose: Construindo uma Introdução Impactante para seu App > No mundo dos aplicativos móveis, a primeira impressão é crucial. E a Splash Screen é a primeira tela que o usuário vê quando abre o seu aplicativo. Neste vídeo, vamos criar uma Splash Screen incrível para o nosso aplicativo usando Jetpack Compose. - HTML version: https://tiagodanin.com/br/post/android-splash-screen-in-jetpack-compose-building-an-impactful-intro-for-your-app/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2024-02 - Language: Portuguese - Tags: Android, Mobile, UI/UX, Video - Originally published at: https://www.youtube.com/watch?v=GLBY4YHoVFk No mundo dos aplicativos móveis, a primeira impressão é crucial. A Splash Screen é a primeira tela que o usuário vê quando abre o seu aplicativo — e ela tem o poder de encantar ou afastar desde o primeiro segundo. Neste vídeo, vamos criar uma Splash Screen incrível usando Jetpack Compose, explorando as APIs modernas do Android para criar uma introdução impactante e profissional para o seu app. [Android Splash Screen em Jetpack Compose: Construindo uma Introdução Impactante para seu App](https://www.youtube.com/watch?v=GLBY4YHoVFk) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Create Adaptive Icons for Android Apps > Learn how to create and configure adaptive icons in Android Studio, so your app looks consistent on any launcher and any icon shape the user picks. - HTML version: https://tiagodanin.com/post/create-adaptive-icons-for-android-apps/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2024-02 - Language: English - Tags: Android, Mobile, Tutorial, Video - Originally published at: https://www.youtube.com/watch?v=YRJnugSqrl4 Android adaptive icons allow your app to have a consistent and beautiful appearance on any device, regardless of the launcher or icon shape chosen by the user. In this video, you'll learn how to create and correctly configure adaptive icons in Android Studio. [Create Adaptive Icons for Android Apps](https://www.youtube.com/watch?v=YRJnugSqrl4) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Crie Ícones Adptativos para Aplicativos Android > Aprenda a criar e configurar ícones adaptáveis no Android Studio, para o app ficar consistente em qualquer launcher e em qualquer forma de ícone. - HTML version: https://tiagodanin.com/br/post/create-adaptive-icons-for-android-apps/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2024-02 - Language: Portuguese - Tags: Android, Mobile, Tutorial, Video - Originally published at: https://www.youtube.com/watch?v=YRJnugSqrl4 Os ícones adaptáveis do Android permitem que o seu app tenha uma aparência consistente e bonita em qualquer dispositivo, independente do launcher ou da forma do ícone escolhida pelo usuário. Neste vídeo, você aprende como criar e configurar ícones adaptáveis corretamente no Android Studio. [Crie Ícones Adaptáveis para Aplicativos Android](https://www.youtube.com/watch?v=YRJnugSqrl4) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Flutter: Configuring Your App Remotely with Firebase Remote Config > How to implement Firebase Remote Config in a Flutter app: creating parameters, setting conditions and applying changes without publishing a new version. - HTML version: https://tiagodanin.com/post/flutter-configuring-your-app-remotely-with-firebase-remote-config/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2024-02 - Language: English - Tags: Flutter, Mobile, Video - Originally published at: https://www.youtube.com/watch?v=ocZE-5QuhIQ Firebase Remote Config allows you to change the behavior and appearance of your Flutter app without needing to publish a new version. In this video, we'll implement Remote Config from scratch, learn how to create parameters, set conditions, and apply real-time changes for your users. [Flutter: Configuring Your App Remotely with Firebase Remote Config](https://www.youtube.com/watch?v=ocZE-5QuhIQ) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Flutter: Configurando Seu App à Distância com Firebase Remote Config > Como implementar o Firebase Remote Config num app Flutter: criar parâmetros, definir condições e aplicar mudanças sem precisar publicar uma nova versão. - HTML version: https://tiagodanin.com/br/post/flutter-configuring-your-app-remotely-with-firebase-remote-config/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2024-02 - Language: Portuguese - Tags: Flutter, Mobile, Video - Originally published at: https://www.youtube.com/watch?v=ocZE-5QuhIQ O Firebase Remote Config permite que você altere o comportamento e a aparência do seu aplicativo Flutter sem precisar publicar uma nova versão. Neste vídeo, vamos implementar o Remote Config do zero, aprender como criar parâmetros, definir condições e aplicar mudanças em tempo real para os seus usuários. [Flutter: Configurando Seu App à Distância com Firebase Remote Config](https://www.youtube.com/watch?v=ocZE-5QuhIQ) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Flutter: Implementing Amazing Animations with Lottie > How to integrate Lottie animations in Flutter, from installing the package to displaying complex animations made in After Effects or Figma, in few lines. - HTML version: https://tiagodanin.com/post/flutter-implementing-amazing-animations-with-lottie/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2024-02 - Language: English - Tags: Flutter, UI/UX, Mobile, Video - Originally published at: https://www.youtube.com/watch?v=9wvNjtbQIpk Want to make your Flutter apps more dynamic and attractive? Lottie is the solution! In this video, you'll learn how to integrate Lottie animations in Flutter, from installing the package to displaying complex animations created in After Effects or Figma, with very few lines of code. [Flutter: Implementing Amazing Animations with Lottie](https://www.youtube.com/watch?v=9wvNjtbQIpk) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Flutter: Implementando Animações Incríveis com Lottie > Como integrar animações Lottie no Flutter, da instalação do pacote até exibir animações complexas feitas no After Effects ou Figma, com poucas linhas. - HTML version: https://tiagodanin.com/br/post/flutter-implementing-amazing-animations-with-lottie/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2024-02 - Language: Portuguese - Tags: Flutter, UI/UX, Mobile, Video - Originally published at: https://www.youtube.com/watch?v=9wvNjtbQIpk Quer tornar seus aplicativos Flutter mais dinâmicos e atraentes? O Lottie é a solução! Neste vídeo, você vai aprender como integrar animações Lottie no Flutter, desde a instalação do pacote até a exibição de animações complexas criadas no After Effects ou Figma, com pouquíssimas linhas de código. [Flutter: Implementando Animações Incríveis com Lottie](https://www.youtube.com/watch?v=9wvNjtbQIpk) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Building Performant Lists in React Native > Learn how to build performant lists in React Native with FlatList and FlashList, the differences between them, and the practices that keep scrolling smooth. - HTML version: https://tiagodanin.com/post/building-performant-lists-in-react-native/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2023-03 - Language: English - Tags: React Native, UI/UX, Video - Originally published at: https://www.youtube.com/watch?v=Z3-v01J9n0I Slow lists are one of the biggest performance problems in React Native apps. In this video, you'll learn how to build performant lists using `FlatList` and `FlashList`, understand the differences between them, and apply best practices to ensure smooth scrolling even with large amounts of data. [Building Performant Lists in React Native](https://www.youtube.com/watch?v=Z3-v01J9n0I) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Construindo listas performáticas no ReactNative > Aprenda a construir listas performáticas no React Native com FlatList e FlashList, as diferenças entre elas e as práticas que mantêm a rolagem suave. - HTML version: https://tiagodanin.com/br/post/building-performant-lists-in-react-native/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2023-03 - Language: Portuguese - Tags: React Native, UI/UX, Video - Originally published at: https://www.youtube.com/watch?v=Z3-v01J9n0I Listas lentas são um dos maiores problemas de performance em apps React Native. Neste vídeo, você vai aprender como construir listas performáticas usando `FlatList` e `FlashList`, entender as diferenças entre elas e aplicar as melhores práticas para garantir uma rolagem suave mesmo com muitos dados. [Construindo listas performáticas no ReactNative](https://www.youtube.com/watch?v=Z3-v01J9n0I) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # How to Create Powerful Pipelines for React Native in GitLab > How to implement a test pipeline for React Native with GitLab Pipeline: declaring tasks in YML, their dependencies, and fast feedback on every commit. - HTML version: https://tiagodanin.com/post/how-to-create-powerful-pipelines-for-react-native-in-gitlab/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2023-02 - Language: English - Tags: React Native, DevOps, Tutorial, Testing, Article - Originally published at: https://medium.com/idopterlabs/como-criar-pipelines-poderosos-para-react-native-no-gitlab-5da018126289 ![How to Create Powerful Pipelines for React Native in GitLab](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/cover.png) ## GitLab Pipeline GitLab provides DevOps tools, and GitLab Pipeline is one of them. This tool allows you to "build an automation pipeline for continuous integration" that offers fast feedback on commits, branches, or merge requests. Tasks are declared in a YML file with their necessary dependencies, such as specific Docker images. ## Creating the Test Pipeline ![GitLab CI/CD pipeline diagram](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/pipeline-diagram.png) You need to have an application with tests already integrated before getting started. The `.gitlab-ci.yml` file should be created in the root folder. A basic example uses Node.js as a base: ```yaml image: node:16.10.0 cache: paths: - node_modules/ test_async: script: - npm install - node ./specs/start.js ./specs/async.spec.js ``` To use Yarn as the dependency manager, add the installation instructions through `before_script`: ```yaml image: node:16.10.0 before_script: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH" cache: paths: - node_modules/ test_async: script: - yarn install - yarn test ``` ## Improving the Pipeline ![GitLab CI/CD screenshot](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/gitlab-cicd.png) ### Split into Stages Organize tasks using the `stages` key: ```yaml stages: - build-deps - test build-deps: stage: build-deps script: - yarn install run_tests: stage: test script: - yarn test ``` ### Optimize Execution Time Implement variables for faster caching: ```yaml variables: FF_USE_FASTZIP: "true" ARTIFACT_COMPRESSION_LEVEL: "fast" CACHE_COMPRESSION_LEVEL: "fast" cache: - key: files: - yarn.lock paths: - node_modules/ policy: pull-push ``` ### Extract Test Coverage Use Regex to capture coverage: ```yaml run_tests: stage: test coverage: '/All\\sfiles\[\\s\]\*\\|\[\\s\]\*(\\d+\\.\\d+)/' script: - yarn test:ci ``` ### Configure Timezone In `package.json`, use `cross-env` to ensure compatibility: ```json { "scripts": { "test": "cross-env TZ=America/Sao_Paulo jest", "test:coverage": "cross-env TZ=America/Sao_Paulo jest --coverage --watchAll=false" }, "devDependencies": { "cross-env": "^7.0.3" } } ``` ![Pipeline configuration](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/pipeline-config.png) ### Add JUnit Reports Install `jest-junit`: ```bash yarn add jest-junit --dev ``` Configure in `package.json`: ```json { "scripts": { "test:ci": "cross-env TZ=America/Sao_Paulo jest --coverage --watchAll=false --reporters=default --reporters=jest-junit --silent" } } ``` And in the pipeline: ```yaml run_tests: stage: test coverage: '/All\\sfiles\[\\s\]\*\\|\[\\s\]\*(\\d+\\.\\d+)/' artifacts: when: always reports: junit: - junit.xml script: - yarn test:ci ``` ![Pipeline stage](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/pipeline-stage.png) ## Final Complete File ```yaml image: node:16.10.0 variables: FF_USE_FASTZIP: "true" ARTIFACT_COMPRESSION_LEVEL: "fast" CACHE_COMPRESSION_LEVEL: "fast" cache: - key: files: - yarn.lock paths: - node_modules/ policy: pull-push - key: yarn-$CI_JOB_IMAGE paths: - .yarn policy: pull-push before_script: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH" stages: - build-deps - test build-deps: stage: build-deps script: - yarn install --cache-folder .yarn run_tests: stage: test coverage: '/All\\sfiles\[\\s\]\*\\|\[\\s\]\*(\\d+\\.\\d+)/' artifacts: when: always reports: junit: - junit.xml script: - yarn test:ci ``` ![Pipeline result](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/pipeline-result.png) ## Setting a Timeout Configure the expiration time in CI/CD Settings >> General pipelines >> Timeout to avoid unexpected costs. The example uses 20 minutes. ## Conclusion The article demonstrates how to use GitLab Pipeline to test React Native applications with time optimizations and quality assurance before approving merge requests. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Como criar pipelines poderosos para React Native no Gitlab > Como implementar um pipeline de testes para React Native com o Gitlab Pipeline: declarar tarefas em YML, dependências e feedback rápido a cada commit. - HTML version: https://tiagodanin.com/br/post/how-to-create-powerful-pipelines-for-react-native-in-gitlab/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2023-02 - Language: Portuguese - Tags: React Native, DevOps, Tutorial, Testing, Article - Originally published at: https://medium.com/idopterlabs/como-criar-pipelines-poderosos-para-react-native-no-gitlab-5da018126289 ![Como criar pipelines poderosos para React Native no Gitlab](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/cover.png) ## Gitlab Pipeline O Gitlab fornece ferramentas de DevOps, sendo o Gitlab Pipeline uma delas. Esta ferramenta permite "construir uma esteira de automação para integração contínua" que oferece feedback rápido sobre commits, branches ou merge requests. As tarefas são declaradas em um arquivo YML com suas dependências necessárias, como imagens Docker específicas. ## Criação do pipeline de testes ![Diagrama do pipeline GitLab CI/CD](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/pipeline-diagram.png) É necessário possuir uma aplicação com testes já integrados antes de começar. O arquivo `.gitlab-ci.yml` deve ser criado na pasta principal. Um exemplo básico utiliza Node.js como base: ```yaml image: node:16.10.0 cache: paths: - node_modules/ test_async: script: - npm install - node ./specs/start.js ./specs/async.spec.js ``` Para usar Yarn como gerenciador de dependência, adicione as instruções de instalação através de `before_script`: ```yaml image: node:16.10.0 before_script: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH" cache: paths: - node_modules/ test_async: script: - yarn install - yarn test ``` ## Melhorando o Pipeline ![Screenshot do GitLab CI/CD](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/gitlab-cicd.png) ### Dividir em estágios Organize tarefas através da chave `stages`: ```yaml stages: - build-deps - test build-deps: stage: build-deps script: - yarn install run_tests: stage: test script: - yarn test ``` ### Otimizar tempo de execução Implemente variáveis para cache mais rápido: ```yaml variables: FF_USE_FASTZIP: "true" ARTIFACT_COMPRESSION_LEVEL: "fast" CACHE_COMPRESSION_LEVEL: "fast" cache: - key: files: - yarn.lock paths: - node_modules/ policy: pull-push ``` ### Extrair cobertura de testes Use Regex para capturar a cobertura: ```yaml run_tests: stage: test coverage: '/All\\sfiles\[\\s\]\*\\|\[\\s\]\*(\\d+\\.\\d+)/' script: - yarn test:ci ``` ### Configurar timezone No `package.json`, use `cross-env` para garantir compatibilidade: ```json { "scripts": { "test": "cross-env TZ=America/Sao_Paulo jest", "test:coverage": "cross-env TZ=America/Sao_Paulo jest --coverage --watchAll=false" }, "devDependencies": { "cross-env": "^7.0.3" } } ``` ![Configuração do pipeline](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/pipeline-config.png) ### Adicionar relatórios JUnit Instale `jest-junit`: ```bash yarn add jest-junit --dev ``` Configure no `package.json`: ```json { "scripts": { "test:ci": "cross-env TZ=America/Sao_Paulo jest --coverage --watchAll=false --reporters=default --reporters=jest-junit --silent" } } ``` E no pipeline: ```yaml run_tests: stage: test coverage: '/All\\sfiles\[\\s\]\*\\|\[\\s\]\*(\\d+\\.\\d+)/' artifacts: when: always reports: junit: - junit.xml script: - yarn test:ci ``` ![Estágio do pipeline](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/pipeline-stage.png) ## Arquivo final completo ```yaml image: node:16.10.0 variables: FF_USE_FASTZIP: "true" ARTIFACT_COMPRESSION_LEVEL: "fast" CACHE_COMPRESSION_LEVEL: "fast" cache: - key: files: - yarn.lock paths: - node_modules/ policy: pull-push - key: yarn-$CI_JOB_IMAGE paths: - .yarn policy: pull-push before_script: - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH" stages: - build-deps - test build-deps: stage: build-deps script: - yarn install --cache-folder .yarn run_tests: stage: test coverage: '/All\\sfiles\[\\s\]\*\\|\[\\s\]\*(\\d+\\.\\d+)/' artifacts: when: always reports: junit: - junit.xml script: - yarn test:ci ``` ![Resultado do pipeline](/images/posts/como-criar-pipelines-poderosos-para-react-native-no-gitlab/pipeline-result.png) ## Definir timeout Configure tempo de expiração em CI/CD Settings >> General pipelines >> Timeout para evitar custos inesperados. O exemplo utiliza 20 minutos. ## Conclusão O artigo demonstra como utilizar o Gitlab Pipeline para testar aplicações React Native com otimizações de tempo e garantia de qualidade antes de aprovar merge requests. --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Get Your LinkedIn Profile Ready for Recruiters with These Foolproof Tips > If you're just starting out in the job market and looking for tips to create a LinkedIn profile that catches recruiters' attention, this video is for you - HTML version: https://tiagodanin.com/post/get-your-linkedin-profile-ready-for-recruiters-with-these-foolproof-tips/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2023-02 - Language: English - Tags: Career, Video - Originally published at: https://www.youtube.com/watch?v=qVdrUKHjldM If you're just starting out in the job market or want to reposition your career, having a well-structured LinkedIn profile is essential for attracting recruiters. In this video, I share practical and foolproof tips to make your profile complete, professional, and discoverable. [Get Your LinkedIn Profile Ready for Recruiters with These Foolproof Tips](https://www.youtube.com/watch?v=qVdrUKHjldM) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Deixe seu perfil no LinkedIn pronto para os recrutadores com essas dicas infalíveis > Se você está começando agora no mercado de trabalho e busca dicas para criar um perfil no LinkedIn que chame a atenção dos recrutadores, este vídeo é para você - HTML version: https://tiagodanin.com/br/post/get-your-linkedin-profile-ready-for-recruiters-with-these-foolproof-tips/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2023-02 - Language: Portuguese - Tags: Career, Video - Originally published at: https://www.youtube.com/watch?v=qVdrUKHjldM Se você está começando no mercado de trabalho ou quer se reposicionar na carreira, ter um perfil no LinkedIn bem estruturado é fundamental para atrair recrutadores. Neste vídeo, compartilho dicas práticas e infalíveis para deixar o seu perfil completo, profissional e encontrável. [Deixe seu perfil no LinkedIn pronto para os recrutadores com essas dicas infalíveis](https://www.youtube.com/watch?v=qVdrUKHjldM) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Converting Figma to Jetpack Compose with RELAY > Relay converts Figma components directly into Jetpack Compose code with Material Design, speeding up design-to-code and keeping visual fidelity intact. - HTML version: https://tiagodanin.com/post/converting-figma-to-jetpack-compose-with-relay/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2023-01 - Language: English - Tags: Android, UI/UX, Video - Originally published at: https://www.youtube.com/watch?v=-VSB_Cxw5H0 In this video, we'll explore **Relay**, a tool that allows you to convert Figma components directly into Jetpack Compose code with Material Design. A powerful solution to speed up the design-to-code process and maintain visual fidelity between design and implementation. [Converting Figma to Jetpack Compose with RELAY](https://www.youtube.com/watch?v=-VSB_Cxw5H0) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Convertendo Figma para o Jetpack Compose com RELAY > O Relay converte componentes do Figma direto em código Jetpack Compose com Material Design, acelerando o design-to-code e mantendo a fidelidade visual. - HTML version: https://tiagodanin.com/br/post/converting-figma-to-jetpack-compose-with-relay/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2023-01 - Language: Portuguese - Tags: Android, UI/UX, Video - Originally published at: https://www.youtube.com/watch?v=-VSB_Cxw5H0 Neste vídeo, vamos explorar o **Relay**, uma ferramenta que permite converter componentes do Figma diretamente para código Jetpack Compose com Material Design. Uma solução poderosa para acelerar o processo de design-to-code e manter a fidelidade visual entre o design e a implementação. [Convertendo Figma para o Jetpack Compose com RELAY](https://www.youtube.com/watch?v=-VSB_Cxw5H0) --- Published by Tiago Danin. Free to quote with attribution and a link to https://tiagodanin.com. # Finally! The Secret of Testing in React Native Revealed > Everything you need to know about automated testing in React Native: why they pay off in reliability, speed and analysis, and how to implement them. - HTML version: https://tiagodanin.com/post/finally-the-secret-of-testing-in-react-native-revealed/ - Site index for AI assistants: https://tiagodanin.com/llms.txt - Date: 2022-09 - Language: English - Tags: React Native, Testing, Article - Originally published at: https://medium.com/idopterlabs/finalmente-o-segredo-dos-testes-no-react-native-foi-revelado-d997ffa82768 ![Finally! The Secret of Testing in React Native Revealed](/images/posts/finalmente-o-segredo-dos-testes-no-react-native-foi-revelado/cover.png) ## Introduction This article covers the implementation of automated tests in applications built with React Native. ## Reasons to Use Automated Tests ![Automated testing diagram](/images/posts/finalmente-o-segredo-dos-testes-no-react-native-foi-revelado/testing-01.png) There are several reasons to use automated tests, among which three points stand out: reliability, speed, and analysis. ### Reliability An important point when running tests is getting feedback that a flow is actually working as expected, with the inputs and outputs being executed as planned — that is, the returns match what was previously written. ### Speed As the features of an application grow, more tests are needed, and performing them manually every time a change is made starts to become unfeasible. Instead, automation will always run the same written tests, without skipping a step or missing one of the tests, in much less time than manual execution. ### Analysis Various reports can be generated from automated test execution, such as coverage reports or reports of passed tests that can be used as software quality control. Furthermore, the tests themselves function as a kind of documentation. For example, if we need to know what happens when there's a wrong password input, we just look at the test that references that action. ## Tools ![Testing tools for React Native](/images/posts/finalmente-o-segredo-dos-testes-no-react-native-foi-revelado/testing-02.png) There are many tools for testing mobile apps, but this article works with two: Jest and the React Native Test Library. ![Jest - testing framework](/images/posts/finalmente-o-segredo-dos-testes-no-react-native-foi-revelado/testing-03.png) ### Jest Jest is a testing framework maintained by Meta, the same company behind React Native development, which makes it much easier to use, configure, and include features, since its development is always aligned with the products it's used in. ![React Native Test Library](/images/posts/finalmente-o-segredo-dos-testes-no-react-native-foi-revelado/testing-04.png) ### React Native Test Library The second tool works as a utility that will facilitate testing, initially developed by the Test Library organization itself, and later migrated to Callstack, the organization responsible for various widely used libraries in React Native, such as React Native Paper. ## Configuring Jest ![Configuring Jest in the project](/images/posts/finalmente-o-segredo-dos-testes-no-react-native-foi-revelado/testing-05.png) Right away we'll need to install the tool packages in the project. We can do this through NPM or Yarn: ``` $ yarn add @testing-library/jest-native @testing-library/react-hooks @testing-library/react-native @types/jest babel-jest jest ``` And at the root of the project, create a configuration file for Jest: ```javascript // jest.config.js module.exports = { preset: 'react-native', }; ``` As mentioned earlier, Jest is already prepared for React Native, so we just need to tell it in the `preset` that the default configuration we want to use is react-native. To run the tests you can use the jest command in the terminal or the VS Code Jest Runner extension, which adds a `run` button to your test scopes. ## Creating Tests ![Test file structure](/images/posts/finalmente-o-segredo-dos-testes-no-react-native-foi-revelado/testing-06.png) Tests need to be created in a separate file. This file can be located inside a `__tests__` folder, or files ending in `.test.js`, `.test.ts`, `.test.jsx`, `.test.tsx`, `.spec.js`, `.spec.ts`, `.spec.jsx`, and `.spec.tsx` — this is given by the default regex rule and can be changed in Jest settings via the testMatch field. At IdopterLabs, the convention of placing tests next to the main file of the component, screen, or utility that will be the test scope was adopted. This makes it easy to know where tests are missing, because when you access a component folder, for example, it's clear whether or not the test file exists. ![Writing tests with Jest](/images/posts/finalmente-o-segredo-dos-testes-no-react-native-foi-revelado/testing-07.png) To write the tests, we'll initially need the following structure in our file: ```typescript describe('Button', () => { it('should render without crashing', () => { render(