
Atlas Technologies
Three products in two years: a chat app inherited, a short-video feed built solo, and a chat platform rebuilt from scratch and taken to the Play Store.Três produtos em dois anos: um app de chat herdado, um feed de vídeos curtos feito sozinho e uma plataforma de chat reconstruída do zero e publicada na Play Store.
ProjectsProjetos
Fatal Chat
20202020- Android
- Web
I joined an existing Flutter chat app as one of the two developers on it, shipping into a product that was already live: push notifications, socket messaging, a REST layer on Dio, auth, deep links, and file upload in both the chat and the profile. We worked in sprints against Figma boards with the PO, the backend developers and the designer, reviewing each other’s code as we went, and the spec changed under us often enough that keeping up with it was part of the job. Flutter web was still in beta the whole time we were shipping to it, so plenty of what we needed had no established answer and we found our own.Entrei em um app de chat em Flutter que já existia, como um dos dois desenvolvedores nele, entregando em um produto que já estava no ar: notificações push, mensagens por socket, camada REST com Dio, autenticação, deep links e upload de arquivos tanto no chat quanto no perfil. Trabalhávamos em sprints a partir de boards no Figma, junto ao PO, ao time de backend e à designer, revisando o código um do outro, e a especificação mudava embaixo de nós com frequência suficiente para que acompanhá-la fizesse parte do trabalho. O Flutter web esteve em beta durante todo o tempo em que publicamos nele, então muita coisa de que precisávamos não tinha resposta pronta e nós encontramos a nossa.
- Mobile & WebMobile e Web
- Flutter
- Dart
- GetX
- Flutter Web
- Dio
- WebSockets
- Push Notifications
- Deep links
- shared_preferences
- cached_network_image
- File uploads
One codebase, two targets, never a separate web build. A UI that behaved as expected on Android would do something else entirely in the browser, and much of the work was tracing why. Often the cause wasn’t in our code at all, and the fix was finding a library that held up on both rather than forking for the web.Uma base de código, dois alvos, nunca uma versão web separada. Uma interface que se comportava como esperado no Android fazia outra coisa completamente diferente no navegador, e boa parte do trabalho era rastrear o porquê. Muitas vezes a causa nem estava no nosso código, e a saída era achar uma biblioteca que se sustentasse nos dois lados, em vez de bifurcar para a web.
Fatal Share
20212021- Android
A short-video feed in the mould of TikTok, built from scratch with me as the only developer on the Flutter side. It pulled videos and images from Atlas’s main site into an infinite scroll, open to anyone and with no login. Local storage tracked what each install had already seen so it could be filtered out of what came next, which is what kept the feed from repeating itself. This is also where I started proposing interface work back to the designers rather than only receiving it, including custom position and colour animations. It went out as a sideloaded APK rather than through a store, and is no longer available.Um feed de vídeos curtos nos moldes do TikTok, criado do zero comigo como único desenvolvedor do lado Flutter. Trazia vídeos e imagens do site principal da Atlas para uma rolagem infinita, aberta a qualquer pessoa e sem login. O armazenamento local guardava o que cada instalação já tinha visto para filtrar esse conteúdo do que vinha em seguida, e era isso que impedia o feed de se repetir. Foi também aqui que passei a propor interface de volta aos designers, em vez de apenas receber, incluindo animações personalizadas de posição e cor. Saiu como APK instalado fora das lojas e não está mais disponível.
- MobileMobile
- Flutter
- Dart
- GetX
- Video playback
- shared_preferences
Android only and unauthenticated, so the engineering went into the feed. Video controllers had to be initialised and disposed by hand: a clip needs to be decoded and ready before it scrolls into view, or playback stutters the moment it arrives. I kept a small window of live controllers around the current position and tore down everything outside it. Hold too few and the feed stalls; hold too many and a low-end phone runs out of memory, and low-end phones were what our users had. Scrolling back and forth over the same few clips was the case that stressed it hardest, because it churns initialise and dispose faster than anything a user does deliberately.Somente Android e sem autenticação, então a engenharia foi toda para o feed. Os controllers de vídeo precisavam ser inicializados e descartados na mão: um clipe precisa estar decodificado e pronto antes de entrar na tela, senão a reprodução engasga assim que chega. Eu mantinha uma janela pequena de controllers vivos em torno da posição atual e desmontava tudo fora dela. Poucos demais e o feed trava; muitos demais e um aparelho fraco fica sem memória, e aparelhos fracos eram o que nossos usuários tinham. Rolar para frente e para trás sobre os mesmos clipes era o caso que mais estressava isso, porque acelera o ciclo de inicializar e descartar mais do que qualquer coisa que o usuário faça de propósito.
Deal Talk
2021 – 20222021 – 2022- Android
- iOS
- Web
A rebuild of Fatal Chat, driven by the company moving away from the Fatal branding. That bought us a clean start rather than a migration, so we spent it trying different implementations and a new set of assets instead of carrying the old ones across. It is also where the Flutter side stopped being only me: I was the whole of it at the start, and when it came time to grow, HR sourced the candidates and the technical screen was mine, which came to around ten CVs, two interviews and the developer we hired. We reviewed each other’s code from then on, as on Fatal Chat. It reached Google Play while I was there and went live across a handful of test cities with real users on it, though it is no longer listed; the iOS build was finished by then too, but that release went out after I left.Uma reconstrução do Fatal Chat, motivada pela saída da empresa da marca Fatal. Isso nos deu um começo limpo em vez de uma migração, então aproveitamos para testar implementações diferentes e um novo conjunto de assets, em vez de carregar os antigos. É também onde o lado Flutter deixou de ser só eu: no começo eu era todo ele e, na hora de ampliar, o RH trouxe os candidatos e a entrevista técnica ficou comigo, o que deu cerca de dez currículos, duas entrevistas e o desenvolvedor que contratamos. Revisávamos o código um do outro dali em diante, como no Fatal Chat. Chegou à Google Play enquanto eu estava lá e entrou no ar em algumas cidades-piloto, com usuários reais, embora não esteja mais listado; a build de iOS também já estava pronta, mas essa publicação saiu depois que eu saí.
- Mobile & WebMobile e Web
- Flutter
- Dart
- GetX
- Flutter Web
- Navigator 2.0
- WebSockets
- Push Notifications
- Deep links
- shared_preferences
- cached_network_image
Three targets from one codebase, with far fewer of the web-versus-Android gaps Fatal Chat had: the ecosystem had matured and we knew which libraries held up on both. GetX carried state and dependency injection, picked for the reactive model and how little ceremony it needs, and its own router covered the mobile builds; the web needed real URLs, so routing there moved to Navigator 2.0, which had just been released and which I spent a long time studying before putting it in. One codebase never meant one behaviour, though, so verifying a release meant working through Android browsers, iPhone browsers and desktop browsers separately. Two limits came out of that: iOS browsers had no push notifications at all, which was a real problem for the business at the time, and on mobile the socket dropped the moment the app went to the background, so reopening meant refetching rather than resuming. The web build also pointed visitors at the store listings for the native apps.Três alvos a partir de uma base de código, com bem menos das diferenças entre web e Android que o Fatal Chat tinha: o ecossistema havia amadurecido e já sabíamos quais bibliotecas se sustentavam nos dois lados. O GetX cuidava do estado e da injeção de dependências, escolhido pelo modelo reativo e pela pouca cerimônia que exige, e o roteador dele dava conta das builds mobile; a web precisava de URLs de verdade, então o roteamento passou para o Navigator 2.0, que tinha acabado de ser lançado e que estudei bastante antes de colocar em uso. Uma base de código nunca significou um só comportamento, então validar uma entrega era passar por navegadores Android, navegadores de iPhone e navegadores de desktop separadamente. Dois limites saíram daí: navegadores iOS não tinham notificação push nenhuma, o que era um problema real para o negócio na época, e no mobile o socket caía assim que o app ia para segundo plano, então reabrir significava refazer a busca em vez de retomar. A versão web também indicava ao visitante as listagens dos apps nativos nas lojas.
- BackendBackend
- Socket.IO
- Redis
The socket rooms started out accepting any connection at all. A first pass, with one of the backend developers, authenticated clients after the fact and disconnected the ones that failed: it closed the hole, but every client still got connected first. I later replaced that with authentication in Socket.IO’s handshake middleware, so an unauthorised client is refused before the connection is established rather than dropped afterwards, and moved the transport to wss:// so the credentials in that handshake were not crossing the wire in clear. I was pulled onto backend reviews from time to time as well, and could read the JavaScript because I had been learning it in my own time; the backend team’s background was PHP. The service listened on Redis and pushed the resulting events down to the app.As salas de socket começaram aceitando qualquer conexão. Uma primeira correção, feita com um dos desenvolvedores de backend, autenticava o cliente depois do fato e derrubava quem falhasse: fechava a brecha, mas todo mundo ainda conectava antes. Depois substituí isso por autenticação no middleware de handshake do Socket.IO, de modo que um cliente não autorizado é recusado antes de a conexão ser estabelecida, em vez de derrubado depois, e passei o transporte para wss:// para que as credenciais daquele handshake não trafegassem em texto claro. De vez em quando eu também era chamado para revisar código do backend, e conseguia ler o JavaScript porque estava estudando por conta própria; a experiência do time de backend era em PHP. O serviço escutava o Redis e enviava os eventos resultantes para o app.