Senior Mobile Engineer · native iOS, macOS & visionOS · Flutter and the APIs behind them

Maykon Meneghel

I ship native Apple apps, and the API they talk to.

Most CVs tell you what someone can do. This one lets you try it. Underneath all of it is one thing: a reading. I built the instrument that makes one — firmware, board, enclosure, buried in a field — and I have spent the seven years since on everything that happens to a number afterwards. Every chapter below is a working mini-game.

01 · THREAD iOS & Flutter · 2019 → today

The frame is sixteen milliseconds long

Every argument a mobile team has about concurrency is really an argument about one number. A screen owes a frame every 16.7 ms, and everything — async/await, actors, isolates, cancellation — is about what you are allowed to do inside it. Below is the same list loaded three ways, with the frame graph a profiler would show you.

instruments · frame timing

A screen owes a frame every 16.7 ms. This one is loading twenty-four rows, and the only thing that changes between the three buttons is where the decode happens. Watch the frame graph, not the list.

Where the work runs
iPhone · 60 Hz
Frame times 16.7 ms budget

on budget dropped

Worst frame
Dropped frames
Jank
Wasted after leaving

One frame that lasts almost three hundred milliseconds. The list does not stutter — it stops, and the operating system starts thinking about whether your app is still alive. Every mobile engineer has shipped this once.

Swift
 
Flutter
 

What you just learned → The middle button is the whole chapter. await suspends a function, it does not move it — the continuation resumes on the main actor and the expensive work still lands in a frame. Async code that never leaves the main thread is the most common performance bug in mobile, and it is invisible until somebody profiles it.

Swift Concurrencyasync/awaitactors@MainActorCombineIsolatescompute()InstrumentsTask cancellation60 fps

Act one

The screen

A reading is worth nothing until somebody sees it, and a phone allows 16.7 milliseconds to show it. Seven years of shipping apps, native and cross-platform — and before them three years a level down, in firmware, boards and machined parts, which the Apple Developer Academy turned into this: the same engineering, moved up the stack. This act is the last few centimetres of that trip — arriving in time, staying legible under conditions nobody designed for, and reaching the right person only.

02 · GLASS Swift & Flutter · 2019 → today

The card that survives the real world

Anyone can build the card in the design. The job is building the one that is still readable when the person holding the phone has set text to the largest accessibility size, is in split view, in a language whose words run longer, reading right to left, on a connection that never delivered the image. A designer hands you one state. You ship six.

PositionRow.swift / position_row.dart

One card, two builds. On the left is the card as it comes off a comp: one row, one line each, a height in points. On the right is the same card built to survive. Turn the conditions on and watch which one is still readable — the type size a person actually set, a narrow split view, a translation that runs longer, a right-to-left locale, an image that never arrived, an error with nowhere to go.

Available width
Conditions
View state
Built to the comp
BTC-USD Long · 20 lots +1,240.50
    Built to survive
    BTC-USD Long · 20 lots +1,240.50
      SwiftUI
      // The whole fix is three decisions.
      HStack(alignment: .firstTextBaseline, spacing: 12) {
        if let image { Thumbnail(image) }        // no image, no box
        VStack(alignment: .leading, spacing: 2) {
          Text(position.symbol)
            .font(.headline)
            .lineLimit(nil)                      // let it wrap
          if let error { Text(error).foregroundStyle(.red) }
          else { Text(position.summary).font(.subheadline) }
        }
        Spacer(minLength: 12)
        Text(position.pnl, format: .currency(code: code))
          .monospacedDigit()
      }
      .padding(.horizontal, 16).padding(.vertical, 12)
      .frame(minHeight: 44)                      // grows, never clips
      .dynamicTypeSize(...DynamicTypeSize.accessibility5)
      .accessibilityElement(children: .combine)
      .accessibilityLabel(position.spokenLabel)
      Flutter
      // Same three decisions, same order.
      Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          if (image != null) Thumbnail(image!),
          const SizedBox(width: 12),
          Expanded(                              // min-width: 0, in Flutter
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(p.symbol, style: t.titleMedium, softWrap: true),
                error != null
                  ? Text(error!, style: t.bodySmall!.copyWith(color: cs.error))
                  : Text(p.summary, style: t.bodySmall),
              ],
            ),
          ),
          const SizedBox(width: 12),
          Text(p.pnl, style: t.titleMedium),
        ],
      )
      // Directionality mirrors the row; Semantics gives it one spoken label.

      What you just learned → The layout drawn to the comp survives one of the seven conditions below — the one it was drawn for. The difference is not taste, it is which question the layout asks: not "is the type large?" but "does this fit?". Ask the second one and a longer translation stops being a bug report.

      SwiftUIUIKitFlutterDynamic TypeVoiceOverLocalizationRTLAdaptive layoutAccessibilityDesign systems

      03 · GATE Tradx · 2022 → today

      Signing in is where apps get broken into

      Everything else in a mobile app fails visibly. This fails silently: the sign-in works, the user gets in, and the flow is wide open the whole time. I rebuilt this one for Tradx after reading our own implementation properly — it is the fifty lines I would ask about in any senior interview.

      oauth · redirect handling

      Signing somebody in through a browser and handing the result back to the app is the most security-sensitive fifty lines in a mobile codebase. Start from the version every tutorial shows and close it one switch at a time. The order matters more than you would like.

      The redirect
      What comes back
      Guards
      1. The app opens the browser ASWebAuthenticationSession, not a web view — the session cookie has to be the real one
      2. The user signs in and the authorisation server redirects back
      3. The device routes the redirect to whichever app claims that address
      4. The app reads the callback off the URL, which is written to logs and history
      5. The app exchanges it for a session, over TLS, from the app itself
      6. Somebody presents it again the same value, a second time
      7. The session lands in the keychain and the user is in
      Redirect reaches
      Attacks that land
      • Scheme hijack any other app on the device can register tradx:// and receive the redirect instead
      • Token in the logs an access token on a query string is written to system logs, browser history and the referrer
      • Code replay a code that is accepted twice is an access token with extra steps
      • Code interception without PKCE the exchange proves nothing about who started the flow, so whoever holds the code can spend it
      • Forged callback without an unguessable state value the app accepts a sign-in the user never started

      Start with the tutorial version and close it one switch at a time. Watch how far the redirect gets.

      What you just learned → Swapping the token for a one-time code is the fix everybody reaches for first, and on its own it opens two holes for the one it closes. A code needs PKCE so only the app that began the flow can spend it, and single use so it cannot be spent twice. Half a security migration is worse than none, because it feels finished.

      OAuth 2.0PKCEUniversal LinksASWebAuthenticationSessionKeychainApp LinksDeep linkingThreat modelling

      Act two

      What the app talks to

      Work backwards from the screen and you find the trip the number took to reach it. It has to arrive while it is still true, be found among hundreds of thousands of its siblings, hold up when everybody asks at once, and finally be worth a decision. Four chapters — and in the last one, all four decisions are mine.

      04 · PULSE Tradx · 2022 → today

      The client should not have to ask

      Most of what an app shows is the answer to a question it asked. A live price is not: by the time the answer arrives the question has changed, and asking again spends a whole request to be told, usually, that nothing happened. This chapter is the difference between an endpoint that answers and a channel that tells you.

      tradx · price feed

      One minute of a price feed, replayed. Choose how the client finds out — ask on a timer, or be told — and then watch the two numbers: what the market is doing, and what the reader is actually looking at.

      Transport
      The market
      On screen
      0.0s
      Requests
      Bytes a minute
      Ticks seen
      Out of date

      One connection, {bytes} for the whole minute, and all {emitted} ticks arrived. The screen showed a superseded price {behind} of the time — the flight itself, and nothing else.

      What you just learned → Polling once a second cost eight times the bytes of holding one socket open, and still missed a quarter of the ticks. Frequency is not freshness — the only way to stop being late is to stop asking.

      Node.jsNestJSWebSocketsREST APIsEvent-driven architectureAPI design

      05 · SERVICE Backend · 2018 → today

      Somebody has to remember all of it

      A reading nobody kept is a reading that did not happen. Behind every device and every client sits an API and a database, and the difference between a fast product and a slow one is usually a single decision made here. The table below holds two hundred and forty thousand sensor readings — the kind the probe on the other page spent two years producing.

      psql · meneghel_db

      A table of sensor readings, 240,000 rows deep. Pick a query, decide which columns get an index, and run it. The strip below is the table; watch how much of it the database has to touch.

      Query
      SELECT * FROM readings WHERE reading_id = 128374
      CREATE INDEX ON

      index · B-tree

      18 pages

      table · readings · 240,000 rows

      Rows examined
      Query time
      vs full scan
      Write cost

      What you just learned → Without an index the database reads every row to answer you. With one, it jumps straight there. Same query, same data, same server — a thousand times faster.

      Node.jsExpressRestifyMongoDBPostgreSQLREST

      06 · SWARM Backend · 2018 → today

      One box is never enough

      One reader is a query; ten thousand at once is an architecture. Traffic does not arrive politely — it arrives all at once, at 3am, on launch day. Containers and Kubernetes are how you answer that without waking up.

      kubectl · production

      Traffic on this service spikes every few seconds, the way a launch day does. Add replicas and the latency falls; add too many and you are paying for idle machines. Then hand it to the autoscaler and watch where it still loses.

      Incoming Replicas × 60 rps

      ready 0 starting 0

      You are scaling by hand. The spike arrives whether you are ready or not.

      Incoming 0
      Saturation 0%
      p99 latency 0 ms
      Requests dropped 0
      Cost $0

      What you just learned → You did not make the code faster — you made more of it. Horizontal scaling trades money for latency, and an autoscaler makes that trade for you, automatically.

      DockerKubernetesHPALoad balancingCI/CD

      07 · FLOW Tradx · 2022 → today

      And then I built the tool

      Tradx is my sandbox — the place I get to make every decision and live with all of them. A desktop trading client written in Flutter, where a strategy is a graph you wire rather than code you write, published on the Mac App Store and the Microsoft Store, about fifteen thousand downloads between them. Two of us build it: me and Luiz Veloso, on pull requests, with reviews neither of us skips.

      tradx · strategy graph

      This is what Tradx does, in miniature. You do not write the loop — you wire the logic, and the engine runs it over the candles. Move the parameters and watch the graph and the trades change together.

      The Tradx strategy editor: a candlestick chart of PETR4 above a node graph wiring a moving average and an RSI into two market entries.
      Tradx · the strategy editor. Seven nodes, seven connections, and a backtest running against PETR4 on a fifteen-minute chart.

      Two people, pull requests, and reviews neither of us skips.

      Market data RSI indicator Cross up Cross down Market entry Market exit
      Trades 0
      Return 0%
      Win rate 0%

      Tune it until the number is beautiful. That is the easy part, and it is what every backtest screenshot on the internet is showing you.

      What you just learned → A backtest is a hypothesis, not a result. Anyone can tune a curve until it is beautiful on the data they already have; the entire craft is knowing what that number is worth on the data they do not.

      Node.jsNestJSFlutterMongoDBRedis + BullMQBacktesting enginePaper trading

      Act three

      And the models underneath

      Everything so far moves a reading that already exists. This one invents the next one — and it is where the two halves of this site turn out to be one half. I trained the model on market data; it runs here on a soil probe I buried years earlier, unchanged, because a time series does not know what it measures.

      08 · MIND StockGAN · 2021

      Teach the machine to guess what happens next

      In 2021 I built a generative adversarial network for time series from scratch — the work where my postgraduate specialization in AI and my Master in Bioengineering stop being lines on a diploma — two networks, one trying to invent the next reading and one trying to catch it lying. I trained it on financial data. It runs here on a soil probe I designed and buried years earlier, unchanged, because a time series does not know what it measures.

      gan_model.py — train_step()

      In 2021 I wrote a GAN that forecasts the next value of a time series — generator, discriminator and training loop from scratch. I trained it on thirty years of daily bars, because that is where clean history was free, but the network reads log returns and has no idea what the numbers measure. So here it is running on a soil probe I built years before any of this. The part worth showing is this: the discriminator never judges a predicted reading on its own. The reading is glued onto the nine real ones before it, and the network has to say whether the whole window came off the sensor. That is the game. You are the discriminator.

      StockGAN · LSTM(128) · window of 10 · trained on 30 years of daily bars, running here on soil-1

      One of these two windows ends with a reading the probe actually took. The other ends with one the generator invented. Which is which?

      Rounds 0
      You
      The discriminator
      It fooled the critic

      Pick one. Nine real readings, and a tenth that is either the probe or my generator.

      1 − λ · MSE λ · critic what I shipped · 0.60
      Close to the truth
      Moves like the sensor
      What is actually in the file generator LSTM(128, stateful) → Dropout(0.3) → Dense(n_features) discriminator LSTM(128, stateful) → Dropout(0.1) → Dense(1, sigmoid)

      RMSprop on a polynomial decay from 2.5e-4 down to 1e-4 over a thousand steps. Cross-entropy with label smoothing at 0.2, so the discriminator is never allowed to be completely sure of anything. Both the real and the generated target multiplied by noise at σ = 0.25 while training, so it cannot win by noticing which numbers are too clean. Twelve indicators — MACD, EMA, RSI, Bollinger, ROC, volatility — cut down by PCA and XGBoost before the network ever saw them; on a soil probe those same twelve become moving averages and rate of change of moisture, which is exactly the point: the architecture changed domain and the feature pipeline barely blinked. The loss functions on this page are ports of the real ones, constants included; the generator here is a stand-in that reproduces the trade-off without carrying 128 LSTM units into your browser.

      What you just learned → Two models, one loss function, and a knob between them. Grade the forecaster on being close and it answers with the average of everything, which is safe and useless. Grade it on being believable and it answers with something that moves like the sensor and knows nothing. Every model I ship is that trade made on purpose, written down, and measured.

      TensorFlow / KerasGANsLSTMTime seriesPCA & XGBoost feature selection

      Act four

      And then it left the screen

      And now the measurement is the object. At Eldorado, on macOS and visionOS, a three-dimensional thing stops being something an engineer drew and becomes something a camera measured — the same move I made in 2019, run once more, on the world itself.

      09 · FIELD Eldorado · 2022 → today

      And now the object is made of light

      The last chapter invented the next number in a series. This one stops treating the world as something anybody draws at all. A scene stops being a surface someone modelled and becomes a cloud of oriented gaussians, fitted by gradient descent until renders of them match the photographs — which means nobody draws it, and there is no mesh left to convert. Real reconstructions start from a camera walking around a room. The demo below deliberately does not: it uses a part I designed and printed myself, because the only way to see what the method changes is to watch the same object built both ways.

      gaussian splatting · viewer

      One object, built twice. On the left of the switch it is the mesh the part was printed from. On the right it is a field of gaussians over that same mesh — scattered here rather than fitted, so the two can sit side by side and the difference is the method and nothing else.

      ↔ drag to turn

      loading the mesh…

      Primitives
      Authored by hand

      997 triangles, each one placed by somebody in SolidWorks in 2017.

      What you just learned → A mesh says where the surfaces are, and an engineer drew every one. A splat field says where the light is, and nobody drew any of it — it was fitted until the render stopped disagreeing with the photograph.

      Gaussian Splatting3DGSvisionOSmacOSPyTorchCUDAPhotogrammetryRadiance fields

      10 · RECORD

      The receipts

      Nine chapters of demonstration. This one is just evidence. Drag through the years and watch what was running at the same time — because the honest headline of this timeline is not any single job, it is how rarely there was only one.

      timeline · 2012 → 2026
      2026
      ← drag the year →

      Education

      B.Eng. Control & Automation Engineering PUC-PR
      M.Sc. Bioengineering PUC-PR
      Postgraduate specialization, iOS Development Apple Developer Academy
      Specialization, Applied Artificial Intelligence PUC-PR

      Research

      Trainee, Laboratory of Automation and Systems PUC-PR · LAS
      Software Engineer — applied research, AI and embedded health Fundação Araucária

      Industry

      Software Developer, freelance Self-employed
      Trainee · Apple Residency Eldorado · Apple
      Junior Developer · Apple Project Eldorado · Apple
      Tech Lead · Apple Internship Eldorado · Apple
      Full Stack Developer · Apple Project Eldorado · Apple
      Senior Developer · Apple Project Eldorado · Apple
      Technology Advisor · Renault Project GHEL

      Ventures

      Administrative Advisor — the family holding RJ Meneghel Holding
      COO & Co-founder Tradx

      From trainee to senior in four years

      I joined the Apple engagement in 2022 as a trainee and I am still on the same product, now as a senior. Nobody promotes on a schedule: each step came from taking something nobody wanted — the release process, the flaky suite, the screen everybody rewrote twice — and making it somebody else’s easy problem. Drag the year and watch the industry lane: the four bands that follow each other are the same product, and the same team, seen from four different amounts of responsibility.

      Six companies

      Founded or co-founded. Some grew, some did not — which is the point of founding six.

      • Tradx Co-founder · fintech
      • Agrom.IO Founder · agritech
      • Dommuz Co-founder · smart home
      • Psiu Co-founder · proptech
      • Hubli Co-founder · edtech
      • PreditChart Co-founder · fintech

      Published and awarded

      Six pieces of academic work, three of them peer-reviewed, and none of them about mobile — which is the point. They are where I learned to be wrong in public: to state a method precisely enough that somebody else can attack it, take the review, and change the claim. That habit is worth more in a code review than any of the results are.

      • Instrument for Measuring Factors and/or Natural Elements of the Soil 2016 · Control & Automation Engineering, PUC-PR
      • A Tool to Select FES Parameters for chronic SCI 2019 · 41st Annual International Conference of the IEEE EMBS · read the paper ↗ · doi:10.1109/EMBC.2019.8857421 · source ↗
      • Indoors Wi-Fi Fall Detector Buckle for the Elderly 2019 · Advanced Materials Proceedings, 4(1), 40–45 · read the paper ↗ · doi:10.5185/amp.2019.1450 · source ↗
      • Application, in silico, of electrical stimuli in a neuro-muscular model compatible with chronic spinal cord injury 2019 · M.Sc. dissertation in Bioengineering, PUC-PR · read the paper ↗ · source ↗
      • Mathematical modeling of the balance maintenance system using system identification techniques 2018 · V Congresso Brasileiro de Eletromiografia e Cinesiologia · X Simpósio de Engenharia Biomédica · read the paper ↗
      • Forecasting financial series with Generative Adversarial Networks 2020 · Applied AI specialization, PUC-PR

      In the press

      • Hubli, built at the Apple Developer Academy — Apple names me among its five creators Apple Newsroom · 2021 · read the article ↗
      • Neon Wave, an iOS game covered by MacMagazine MacMagazine · 2020 · read the article ↗

      I build the app, and I build what it talks to. The screen, the state on it, the API that fills it, the database under that, and the model that reads it. Most people own one of those. I have shipped all of them, and I still know which one is the hard part.

      What I work with

      Everything below is credited by a chapter on this page or sitting in a public repository. Nothing here is aspirational.

      iOS · Swift
      Swift · SwiftUI · UIKit · visionOS · macOS · Swift Concurrency · async/await · actors · Combine · MVVM · Coordinators · Clean Architecture · Dependency injection · SwiftData · Core Data · URLSession · Swift Package Manager · XCTest · XCUITest · Instruments · Xcode · App Store Connect · TestFlight · VoiceOver · Dynamic Type · Localization · Push notifications · Deep linking
      Cross-platform
      Flutter · Dart · Riverpod · Bloc · Provider · Kotlin · Jetpack Compose · Android · Platform channels · Offline-first · State management · Widget testing
      Shipping & release
      fastlane · Xcode Cloud · GitHub Actions · CI/CD · App Store review · Phased release · Firebase Crashlytics · Crash-free rate · Analytics · Feature flags · Semantic versioning
      Languages
      Swift · Dart · Kotlin · Java · TypeScript · JavaScript · Python · C · C++ · MATLAB · SQL
      Backend & APIs
      Node.js · NestJS · Express · Java · REST APIs · WebSockets · Microservices · OAuth 2.0 · JWT · API versioning · Distributed systems · System design · Event-driven architecture · API design
      Data
      MongoDB · PostgreSQL · Redis · BullMQ · Database design · Indexing and query planning · Time series · Data pipelines
      Cloud & DevOps
      AWS · Amazon S3 · CloudFront · Route 53 · AWS IoT Core · IAM · EC2 · Docker · Kubernetes · Terraform · Infrastructure as Code · GitHub Actions · CI/CD · OIDC · Observability · Scalability · Linux
      Web
      Astro · HTML · CSS · Accessibility · Design systems · Responsive layout
      AI & 3D
      Machine learning · PyTorch · TensorFlow · Keras · GANs · LSTM · Deep learning · Feature selection · PCA · XGBoost · Gaussian Splatting · 3DGS · CUDA · Photogrammetry · Computer vision
      Embedded & hardware
      Embedded C · ESP32 · PIC18F4550 · ATmega · PWM · ADC · MQTT · IoT · PCB design · EAGLE · Altium · SolidWorks · CAD · 3D printing · Design for manufacturing
      Ways of working
      Git · Code review · Unit testing · Technical leadership · Mentoring · Product engineering · Backtesting · Paper trading

      Talk to me

      If any of this is the shape of a problem you have, I would like to hear about it. Email or LinkedIn, whichever you prefer — the CV is a two-page PDF and the code is on GitHub.