Making AI feel human in a mobile app with Expo, Reanimated, and Skia
Users•React Native•Development••13 minutes read
Daehyeon Mun
Guest Author
See how Callie, a Most Creative Expo App Awards winner, uses Expo, Reanimated, and Skia shaders to deliver a warm, emotionally safe mobile experience.

This is a guest post from Expo App Award Winner, Daehyeon Mun - a Senior React Native Engineer. Known in the React Native community for his design-forward engineering and expressive interface work, he builds products that make people say, “I didn’t know React Native could do that.”
...
Callie is an emotionally intelligent self-care companion designed to support people recovering from eating disorders.
The app focuses on creating a warm, calm, and charming experience that feels welcoming the moment you open it. Using Expo, React Native Reanimated, and React Native Skia, Callie delivers subtle visuals and expressive interactions that help users navigate their daily routines with a sense of comfort and stability.
The product is built end-to-end as a fully native-quality mobile experience. Every detail, from the interface and interaction language to the motion design and shader-based visuals was crafted with the unique needs of eating disorder recovery in mind.
Most Creative App of the year - Callie
Callie was selected as the Most Creative App of the 2025 Expo App Awards, recognized for its thoughtful design and distinctive interaction patterns. It currently holds over 3.2k+ App Store reviews with an average rating of 4.9+, reflecting strong engagement and positive sentiment from users.
Crafting Callie’s mobile experience as a one-person mobile team
I joined Nabi as a founding engineer and took full responsibility for the mobile app development. I engineered the entire mobile experience—from nuanced interactions and animations to shader-based visual effects and the overall feel of the app.
Crafting a polished product doesn’t happen by chance. It takes an eye for detail, a willingness to explore different ideas, and countless iterations of fine-tuning. That level of care demands significant time and focus. And in an early-stage startup, finding the space to invest in that kind of refinement is often the hardest part.
In this article, I’ll share how I approached building Callie’s delicate, emotionally aware mobile experience — and how, as a one-person mobile team in an early-stage startup, I was able to maintain fast development velocity while still delivering a high-quality product.
Designing and developing with eating disorder sensitivities in mind
Designing Callie required a careful understanding of the characteristics and sensitivities of people recovering from eating disorders. Many people in this space experience heightened emotional vulnerability, meaning that even small visual changes or interaction patterns can influence whether the app feels supportive or overwhelming. Because of this, the overall experience needed to avoid abrupt transitions or unnecessary stimulation, and instead maintain a steady, predictable sense of flow.
From a design perspective, we prioritized calm color palettes, gentle transitions, and interaction patterns that never feel demanding or intrusive. From a technical perspective, static screens alone weren’t enough to convey the level of nuance and sense of calm we intended. We needed more refined motion and expressive visual elements to deliver the subtle emotional quality the experience demanded.
Custom shaders for expressive visuals
The kind of experience we wanted to achieve with Callie couldn’t be expressed through static screens alone. Because the app is used by people recovering from eating disorders, many parts of the interface needed to feel psychologically comfortable—gentle, steady, and free from abrupt visual changes. React Native Skia Shaders became a natural choice for achieving this level of nuance.
While exploring different ways to bring this subtle emotional quality into the app, I also tested other approaches such as Lottie and Rive. Both tools are great for pre-designed motion graphics, but they are not well suited for interactions that need to respond to user inputs in real time or match psychological nuances frame by frame.
For Callie, I needed pixel-level control to shape how each movement feels moment to moment. This required deforming content based on user interaction, not just playing back an animation. Skia shaders provided the expressive range and flexibility I needed, enabling physically grounded motion and soft, organic transitions that would be impossible to achieve with traditional animation tools.
One of the key features in Callie that uses a custom shader is the “Callie’s journal about you”. This feature analyzes a user’s activity with AI and delivers a daily summary along with encouraging, positive messages presented in the form of a letter. When building AI-driven features, one of the most important considerations is making sure the experience doesn’t feel cold or mechanical, but instead carries as much human warmth as possible.
To make the journal feel more personal and inviting, I wanted the screen to look less like a block of AI-generated text and more like a thoughtful letter written specifically for the user. Using a shader, I added a “page curl” effect.
This visual treatment adds a small but meaningful human touch to an AI-generated feature, helping the experience feel warmer, more intentional, and more complete.
How to implement the Page Curl Effect
/*** Page Curl Shader Effect** Simulates a realistic page curl by modeling the page as a cylinder.* The shader divides the screen into three regions:* 1. Beyond the curl (transparent/hidden)* 2. The curl surface (cylindrical mapping with shadow)* 3. Before the curl (normal flat page)** Algorithm:* - Find the "curl line" perpendicular to curl direction* - Calculate each pixel's signed distance from the curl line* - Map pixels onto a cylinder surface based on distance* - Apply shadow gradient on the curled region** Uniforms:* u_image - Source texture to be curled* u_resolution - Canvas resolution (width, height) in pixels* u_originPos - Fixed corner where curl originates (typically bottom-right)* u_currPos - Animated curl tip position*/uniform shader u_image;uniform vec2 u_resolution;uniform vec2 u_originPos;uniform vec2 u_currPos;const float PI = 3.14159265359;const float RADIUS = 0.1; // Cylinder radius in normalized spaceconst float SHADOW_EXPONENT = 0.2; // Controls shadow gradient falloffvec4 main(vec2 fragCoord) {// 1. Setup coordinate systemsfloat aspect = u_resolution.x / u_resolution.y;vec2 aspectScale = vec2(aspect, 1.0); // Scale to square aspect ratiovec2 uvScale = vec2(u_resolution.x / aspect, u_resolution.y); // For texture samplingvec2 uv = fragCoord * aspectScale / u_resolution; // Normalized pixel coord [0,1]// 2. Define curl geometryvec2 curlTip = u_currPos * aspectScale / u_resolution; // Current curl tip positionvec2 curlDir = normalize(abs(u_originPos) - u_currPos); // Direction from origin to tip// Find where the curl line intersects the left edge (y-axis)// This is the "hinge" point where the curl beginsvec2 origin = clamp(curlTip - curlDir * curlTip.x / curlDir.x,0.0,1.0);// 3. Calculate curl line length// Base length from origin to tip, plus adjustment for canvas edgefloat curlLength = clamp(length(curlTip - origin) +(aspect - (abs(u_originPos.x) / u_resolution.x) * aspect) / curlDir.x,0.0,aspect / curlDir.x);// Special case: leftward curls don't need edge adjustmentif (curlDir.x < 0.0) {curlLength = distance(curlTip, origin);}// 4. Calculate pixel distance from curl line// Project pixel onto curl direction, subtract curl length// Positive = beyond curl, Negative = before curl, Near-zero = on curl surfacefloat dist = dot(uv - origin, curlDir) - curlLength;// Find closest point on the curl line to current pixelvec2 linePoint = uv - dist * curlDir;// 5. Render based on distance from curl line// REGION 1: Beyond the curl cylinder → fully transparentif (dist > RADIUS) {return vec4(0.0, 0.0, 0.0, 0.0);}// REGION 2: On the curl surface [0, RADIUS]// Map flat coordinates onto cylinder using arc lengthelse if (dist >= 0.0) {// Angle around the cylinder (from front to back)float theta = asin(dist / RADIUS);// Two possible texture coordinates: back side and front side of cylindervec2 backPoint = linePoint + curlDir * (PI - theta) * RADIUS;vec2 frontPoint = linePoint + curlDir * theta * RADIUS;// Use back side if it's within bounds, otherwise show frontbool isInBounds =backPoint.x <= aspect && backPoint.y <= 1.0 &&backPoint.x > 0.0 && backPoint.y > 0.0;uv = isInBounds ? backPoint : frontPoint;// Sample texture at the mapped positionvec4 fragColor = u_image.eval(uv * uvScale);// Apply shadow gradient: darker near the curl edge (dist=RADIUS), lighter at center (dist=0)float shadowFactor = pow(clamp((RADIUS - dist) / RADIUS, 0.0, 1.0),SHADOW_EXPONENT);fragColor.rgb *= shadowFactor;return fragColor;}// REGION 3: Before the curl (dist < 0)// Show flat page, but account for paper "consumed" by the cylinderelse {// Offset by the cylinder's circumference (PI * RADIUS)vec2 mappedPoint = linePoint + curlDir * (abs(dist) + PI * RADIUS);// Use mapped point if valid, otherwise use original UVbool isInBounds =mappedPoint.x <= aspect && mappedPoint.y <= 1.0 &&mappedPoint.x > 0.0 && mappedPoint.y > 0.0;uv = isInBounds ? mappedPoint : uv;return u_image.eval(uv * uvScale);}}
The following describes the page curl shader used in the “Callie’s journal about you” screen.
This shader takes an image as input and uses a progress value to create the visual of a sheet of paper gently unfolding. Instead of simply scaling or fading an image, the animation relies on geometric transformations to make the page appear as if it’s physically curling and uncurling.
To achieve a natural-looking page curl, the effect combines trigonometric functions, coordinate-space distortion, and geometry-based deformation. When these calculations work together smoothly, the animation produces a convincing, paper-like motion.
If you'd like to understand the mathematical principles behind this effect in more depth, I highly recommend this tutorial by William Candillon:
Making AI feel more human
One of the most important aspects of the AI-driven experience was shaping the tone of the generated responses. Even when the model produced accurate content, certain phrasings could feel overly direct, mechanical, or emotionally unbalanced. To avoid this, we implemented a layer of tone-shaping logic that adjusted the structure, warmth, and pacing of each AI message before it reached the user.
This included filtering expressions that could be misinterpreted as judgmental, ensuring that the language stayed non-directive and supportive, and guiding the AI toward a consistently gentle narrative style. These adjustments helped the experience feel more like a thoughtful companion rather than a generic AI assistant, strengthening the emotional connection between the user and Callie.
How to build a high-quality Expo app
Tip 1: Use worklet-related libraries for a Native Feel
One of the most important tips for achieving native-level UI performance in React Native is choosing libraries that run on the worklet and the UI thread. When animations or touch-based interactions rely on the JavaScript thread, they end up competing with other logic, which can easily lead to frame drops, input delays, and bottlenecks.
For an app like Callie—where subtle interactions are core to the experience—these small performance issues can quickly break the overall feel of the product.
A library I strongly recommend is react-native-sortables by Mateusz Łopaciński.
It’s built on react-native-reanimated and react-native-gesture-handler, delivering drag-and-drop interactions that run directly on the UI thread, providing smoothness and responsiveness. It also offers flexible customization options.
In Callie, we are using this library for drag-and-drop To-Do List, allowing us to deliver a stable, high-quality UX without building complex gesture logic from scratch.
Tip 2: Learn delightful animations
The best way to get better at building subtle, delightful animations is simple:
Take the interactions you imagine and try building them yourself.
To continuously sharpen my skills, I used to implement a new UI or interaction at least once a week. Nothing accelerates growth faster than hands-on practice, and each experiment helps expand your intuition for movement, timing, and visual polish.
Here are learning resources I highly recommend:
Their tutorials are great for learning essential concepts and patterns. After studying those foundations, try creating your own animations with the same ideas. Over time, these exercises naturally build confidence and broaden the range of interactions you can bring to life.
Save your time for users
One of the most important technical decisions I made during my first week on the Callie team was migrating the existing React Native CLI based project to Expo. From my previous experience maintaining RN CLI projects, I knew how often native configuration conflicts, build instability, and infrastructure maintenance issues could arise and how much engineering time they consumed without contributing to the user experience.
As the sole engineer responsible for the entire app in a small team, the highest priority was achieving fast and reliable execution speed. Building a high quality experience requires fine adjustments across many iterations, and my time needed to be invested in improvements that users could actually feel. Expo’s managed workflow accelerated those early development stages by removing the overhead that usually accompanies mobile development.
Expo Continuous Native Generation (CNG)
Expo CNG significantly reduced the time traditionally spent managing native configuration and platform specific maintenance. As a result, I was able to dedicate most of my engineering hours to user-facing improvements, feature development, UX enhancements, and refining interactions.
When a single engineer builds the entire app, efficient use of time becomes a critical factor in the product’s survival. After adopting CNG, I could focus far more on development that created direct, meaningful value for users rather than on infrastructure upkeep.
Expo's Build Service
Expo's Build service further streamlined our release workflow. With a single command, you can generate stable iOS and Android builds and publish them to each store. The Build service provided a reliable, consistent, and significantly more time efficient deployment process.
A Strategic Choice: Rapid Iteration with OTA Updates
Waiting several days for App Store review with every new feature can slow down product growth especially during the early stages when rapid learning is essential. To avoid this bottleneck, we shipped an initial store version with all near future required libraries included, and then delivered new features and improvements through Expo’s OTA updates without any release delays.
This approach allowed us to incorporate user feedback quickly while the user base was still small, and the product evolved visibly on a weekly basis. This short feedback loop was critical in accelerating Callie’s early development and shaping the product into what users truly needed.
By adopting Expo’s infrastructure early, we eliminated a considerable amount of engineering overhead and reinvested that time directly into improvements users can feel. As a result, we were able to deliver a thoughtful, high quality product in a short timeframe. This would have been much more difficult with a traditional React Native setup.
Advice for small teams
I hope this article is helpful for small teams or indie developers who want to build a polished product faster, just like Callie. In small teams, what matters most is having enough space to experiment, iterate, and refine the details that shape how a product feels.
Expo made that possible for us. By reducing the overhead that usually slows mobile development down, it allowed us to focus our time where it mattered most—on the experience itself.
If you're building with a small team and want to move quickly without sacrificing quality, Expo is a platform I can confidently recommend.



