In September 2025, I had the pleasure of hosting the CocoaHeads Hamburg iOS developer meetup at the LichtBlick office. As always, it was a great evening: exchanging ideas, talking shop, eating pizza, and drinking beers (or lemonade). Meeting like-minded developers and hearing what everyone is currently working on is one of the most inspiring parts of our community.
That evening, I also gave the keynote talk titled Code generation in the LichtBlick app. Since then, I’ve been asked to write down the ideas and lessons from that talk, especially around design tokens. This post is meant to do exactly that: to serve as a written companion to the talk and as an introduction to the problems and solutions we discussed.
The presentation was intended to spark conversation and covered three different technologies we use in the LichtBlick app that all revolve around code generation:
- Apollo GraphQL for networking
- Trakken for sharing analytics and tracking identifiers
- Dissen for sharing design tokens
All three approaches follow the same core idea: define data once, then generate type-safe Swift and Kotlin code for our native iOS and Android apps.
Apollo GraphQL
GraphQL works by defining a schema for objects in a graph. Clients then define their own queries to fetch exactly the data they need, in the shape they need it. Changes to data are expressed via mutations.
If you’re familiar with OpenAPI for REST APIs, the idea should feel similar: you define a schema or specification and generate client code from it. This makes API access type-safe, lets the compiler help you convert between data types, and avoids a whole class of errors caused by typos or mismatched models.
At LichtBlick, we use GraphQL to provide a single API surface across different teams, services, and products. In the LichtBlick app specifically, we also share GraphQL queries between iOS and Android. Since both platforms implement the same UIs, this allows us to share the work of writing and maintaining queries and ensures that both clients fetch the same data set.
Lessons learned
| Positive | Negative |
|---|---|
| Separation of concerns: Each team can provide and maintain their own set of fields on the schema. | Communication overhead: You still need to talk to each other and have a shared understanding of GraphQL. Who owns which field? |
| Structure: GraphQL defines error reporting (including partial success) and allows defining and reusing custom types. | Opinionated: Coming from REST, it’s not always straightforward to implement GraphQL in a way that actually leverages its strengths. |
| Version-less by design: Field migrations are usage-based rather than version-based. | Version-less cuts both ways: Deprecation happens in the schema, then clients migrate. Actual removal is server-side and can break existing deployments if not handled carefully. |
Trakken
Before Trakken, we had a file containing tracking strings that were used to generate a Swift and Kotlin enum.
This setup had a couple of problems:
- No support for parameters
- Manual maintenance
- Confusion about which tracking changes belonged to which app version
- Frequent cherry-picking of commits
- Potential to ship outdated tracking implementations because changes were missed
Trakken is a custom code generator written in Kotlin. It takes a custom YAML specification as input and generates Swift and Kotlin code for type-safe access to tracking identifiers and parameters.
The generated types are then used for analytics calls. Because the tracking strings are shared, we avoid typos and get compile-time feedback if an identifier changes, gains new parameters, or is removed.
After adding a few custom view modifiers, tracking a screen view becomes trivial and type-safe. The modifier automatically fires the event on enter and ensures it is not sent twice.
| |
Lessons learned
| Positive | Negative |
|---|---|
| Versioning & reliability: Having the correct tracking strings and up-to-date parameters across platforms increases confidence when shipping. | Maintainability: A custom file format and generator require specialized knowledge to maintain. |
| Speed: Once the YAML files exist, the tracking types are immediately available on iOS and Android. | Net worth: In many cases, the manual effort of a simpler type-safe solution might be lower than maintaining a fully automated system. |
| Type-safety: Autocompletion and compile-time checks directly in views improve developer productivity. | Overengineering risk: To be reusable beyond its original scope, the system would need to be more flexible, and therefore even more complex. |
Dissen
We already had many manually defined colors, shapes, and fonts, accessed in a type-safe way using tools like SwiftGen. However, the underlying definitions could still be incorrect or outdated, and nothing prevented platforms from drifting apart over time.
There are many good design token definitions, but they all describe the same core idea:
Design tokens are the building blocks of all UI elements. The same tokens are used in designs, tools, and code.
— Material Design 3
Design tokens are name and value pairings that represent small, repeatable design decisions. A token can be a color, font style, unit of white space, or even a motion animation designed for a specific need.
— Atlassian
Design tokens are a method for managing design properties and values across a design system. Each token stores a piece of information—such as color, sizing, spacing, font, animations, and so on. To make them easier to refer to, each token also gets a name.
— Figma
Design tokens can look like and be used like this:

In the LichtBlick app, we define tokens in two hierarchies:
- Core tokens: raw values (e.g. colors) with stable names
- Semantic tokens: meaningful, reusable abstractions built on top of core tokens
An example structure:

Dissen is a custom code generator inspired by Trakken. It consumes a design token definition, for example exported from Figma, and generates type-safe Swift and Kotlin code.
Internally, this ensures that the design of our native apps does not diverge:
- Developers no longer create colors by hand in code.
- Designers define exact values by name.
- Core and semantic hierarchies encode intent directly into the system.
A key benefit is the repository setup: designers can open pull requests with updated JSON token definitions. Once merged, the corresponding code is generated automatically and becomes available to app developers.
The Swift code generated by Dissen exposes design tokens as strongly typed APIs.
| |
On the app side, we add custom view modifiers that accept these generated types and make them pleasant to use.
| |
Lessons learned
| Positive | Negative |
|---|---|
| Consistency: A single design system is correctly available across platforms. | Complexity: Building and maintaining a custom code generator is hard. App-side extensions are still required for good ergonomics. |
| Clarity: Designers and developers can only use what is explicitly defined. | Specificity: The standard is still evolving; custom extensions are inevitable. |
| Ease of use: The system nudges everyone toward using the design system by default. | Overengineering risk: Like Trakken, the system would need more flexibility to be reused outside its original scope. |
What’s next?
Using GraphQL is excellent for service landscapes that are built from many microservices. It is great for clients with different and evolving data needs without the need to adjust the backend accordingly. This allows teams to work independently from each other.
Use cases for shared, type-safe resources are pretty common, tracking strings are a good example. The consistency across platforms is worth a lot of effort since you can reduce the quality of your analytics data otherwise much more easily.
You should be cautious with custom generators, though, since their creation and maintenance takes a continuous burden on the team.
Design tokens are all the new hotness in design circles for the past two years. The coming standard will improve adoption and tooling arond their use. My suggestion is to start using them now so you can learn the best practices early. Adopting will still depend on specific projects’ setup. It will take some time to use the design tokens across the board but future changes are much more easy and uniform.
Thanks to everyone who was at the CocoaHeads Hamburg meetup last September. I hope this post helps fill in the gaps, and maybe encourages you to experiment with code generation and design tokens in your own projects.