Bringing apps to life: How we use Flutter and Rive at Scapia to build engaging experiences

Press enter or click to view image in full size

[

](https://medium.com/@adityathakurxd?source=post_page---byline--ee6edd03d2fd---------------------------------------)

At Scapia, we believe great products don’t just work, they feel alive. Our goal is to build experiences that are engaging, responsive, and intuitive. Animations aren’t just decoration, they guide the user, reduce friction, and create those small moments of delight that make people want to come back.

We have been building with Flutter, leveraging its speed and flexibility to ship quickly while still crafting custom, design-led interfaces. But when you add Rive to the mix, you can move beyond static visuals into a space where motion becomes part of the language of the product.

This isn’t theory we have seen it play out directly with our users. One wrote:

“From the moment I started using Scapia, I was impressed by its intuitive interface and user-friendly design. Navigating through the app is a breeze…”

And on social media, people often share their excitement in lighter, more casual ways. One post simply read:

Press enter or click to view image in full size

via @samaiyamehak on X

These reactions validated an approach we had been refining internally. We combined Flutter’s rendering power with Rive’s expressive state machines, turning functional flows like onboarding or travel discovery into moments of delight.

In this post, I will share how we actually do this at Scapia. I will walk through the process, the patterns, the trade-offs, and the lessons that Flutter developers can take away if they want to build experiences that feel alive and not just functional.

Our journey with Rive

Rive transformed how our design and development teams collaborate. Previously, creating animations was difficult and often demanded significant developer bandwidth something that could easily get deprioritized at a high-growth company.

“As a tool it (Rive) is super intuitive to use and is quite instrumental in creating lightweight animations and interactions. Its ability to layer multiple different animations has unlocked a never seen before world of possibilities when it comes to creating delightful animations.” — Rithwik Nag (Designer)

With Rive, designers can build animations directly and define interaction logic visually, while developers simply hook into state machines or trigger animations in Flutter with minimal code.

Press enter or click to view image in full size

This shift has been a huge unlock for us. It keeps the creative intent intact, reduces handoff overhead, and lets us ship features at a pace that would have been unthinkable if we were building everything by scratch.

The proof is in how quickly we have been able to experiment.

We shipped a special hand cricket game alongside our RCB campaign in March from idea to production in a week.

When you can move that fast without sacrificing quality, it changes how you think about product development. Animation stops being “nice-to-have polish” at the end of the cycle and becomes a core part of the experience from day one.

Examples from our app

The best way to understand how Rive powers our Flutter experiences is to look at it in action. We use Rive throughout the app to provide users with visual feedback, draw their attention to key moments (like new offers on the home screen), and add simple moments of delight such as when booking travel.

During onboarding, where credit card applications can feel long and complex, Rive animations help simplify the journey. For example, in our progressive flow, we show a visual at each step so users can see their progress as they move forward.

Here are a few places where animation has gone from afterthought to core product experience.

Special hand cricket game for the RCB campaign

We built an entire interactive game inside Scapia during the cricket season, powered end-to-end by Rive. It wasn’t just a visual gimmick. It showed us how quickly we could experiment with playful mechanics, deploy them to production, and have them run smoothly across Android and iOS.

Press enter or click to view image in full size

Here’s a simplified version of how we wired up our hand cricket game:

RiveAnimation.asset( Assets.riveHandCricket, fit: BoxFit.cover, onInit: onRiveInit,),

void onRiveInit(Artboard artBoard) {

_riveSMCtr = StateMachineController.fromArtboard( artBoard,

'State Machine 1',

); artBoard.addController(_riveSMCtr);

_numberInput = _riveSMCtr.findInput<double>('Input') as SMINumber?;

}

The onInit callback initializes the state machine controller, attaches it to the artboard, and retrieves the input that drives the animation. Once the input is available, we can update its value in response to user actions. For example, assigning a number to _numberInput changes the animation instantly.

_numberInput?.value = number.toDouble();

In the screen recording below, you can see how adjusting this input translates directly into different states of the hand cricket game, creating a smooth, interactive experience powered entirely by Rive.

Press enter or click to view image in full size

Special (holiday) dates on the Calendar

Even in utility-heavy parts of the app, motion adds clarity. In our calendar across verticals, we use small animations to highlight special dates like upcoming holidays.

Press enter or click to view image in full size

These cues are subtle but effective, guiding the user without extra text or friction.

Travel Genie

For the Travel Genie sale, we had users “rub the lamp” to unlock travel offers. The interaction was built by listening to drag gestures and triggering the next state once the lamp was rubbed both left and right.

void _handleDragUpdate(DragUpdateDetails details) {
final dx = details.delta.dx;

if (dx > 5) {

HapticFeedback.lightImpact();

rubbedRight = true;

}

if (dx < -5) {

HapticFeedback.lightImpact();

rubbedLeft = true;

}

if (rubbedRight && rubbedLeft) {

_resetRubState(); moveToNextState(); }

}

Instead of a dry button click, they get playful feedback that feels rewarding. It turns a transactional step into a memorable moment and it only worked because Rive made it simple to hook animations into app logic.

Press enter or click to view image in full size

One underrated win is how seamlessly these Rive-driven experiences work across Android, iOS, and different screen sizes. We didn’t need to rebuild or rework assets for each platform. The same state machines, the same logic, and the same visual fidelity carried across, thanks to Flutter’s rendering and Rive’s runtime integration.

Technical integration

Getting Rive into Flutter isn’t complicated, but using it well requires a shift in mindset. We don’t treat Rive files as static assets, we treat them as interactive modules, closer to self-contained components than images or Lottie animations. That perspective influences both how we structure our code and how designers structure their files.

With the latest Rive Flutter package, we get direct hooks into artboards, state machines, and inputs. In practice, most Rive files ship with multiple state machines so that different interaction flows can be managed in one place.

For example, to integrate a state machine with a numeric input, you might write code like this:

static const String _stateMachineName = 'State Machine 1';
static const String _inputNumberName = 'Number 1';

void _onRiveInit(Artboard artboard) {

final controller = StateMachineController.fromArtboard(

artboard, _stateMachineName, onStateChange: _onStateChange, );

if (controller == null) {

log('Failed to initialize Rive controller for $_stateMachineName');
return; } artboard.addController(controller); riveAnimationController = controller;

final input = controller.findInput<double>(_inputNumberName);

if (input is SMINumber) { _riveInputNumber = input;

_riveInputNumber?.value = 0;

} else {
log('Input $_inputNumberName not found or not a number.'); } setState(() {});}

void _onStateChange(String machineName, String stateName) {

log('State changed → $machineName : $stateName');
}

In practice, this means we are wiring logic in Flutter to control the Rive state machine rather than recreating motion in code. A tap event sets an input on the Rive file that drives the animation forward. That keeps our Flutter code clean and our animations consistent with design intent.

Working this way also scales.

When we need to update an animation, designers adjust the Rive file, export it, and developers connect the same inputs without rewriting behavior. It’s faster, reduces bugs, and keeps the final product aligned with what was originally designed.

Scaling in production

It is one thing to get a Rive animation working in a prototype. It is another to run dozens of them in production without blowing up app size, memory, or performance. We’ve learned a few lessons the hard way.

Size optimization

Rive files can get heavy if you not not careful. Complex vector shapes, unnecessary artboards, or unused animations all add up. We keep files lean by stripping unused layers and splitting assets when needed. Every kilobyte matters at scale, especially when your app is already shipping with fonts, images, and platform-specific dependencies.

Caching and delivery

Animations don’t always live inside the app bundle. We often serve Rive files from the network, which gives us flexibility to update content without a new release. To keep things fast, we cache aggressively on-device and only re-fetch when configs change. This ensures animations load instantly after the first run without hammering the network or bloating memory.

Managing performance at scale

The real challenge shows up when you are running many animations at once. Our calendar view is a good example it can display dozens of special date indicators at the same time. Rendering all of them simultaneously is a performance killer.

The fix was simple but effective: only play animations for cells in view, and pause the rest.

VisibilityDetector(
key: Key('calendar_${widget.date}_special'), onVisibilityChanged: (visibilityInfo) {

if (visibilityInfo.visibleFraction > 0) {

smTrigger?.fire(); } }, child: ScapiaNetworkRiveAtom(

placeholderBuilder: ({required context}) => const SizedBox(),

url: Uri.parse(widget.riveAnimationURL), fit: BoxFit.cover, onRiveInit: (Artboard artboard) {

final controller = StateMachineController.fromArtboard(

artboard,

'State Machine 1',

);

if (controller != null) {

artboard.addController(controller);

smTrigger = controller.findSMI<SMITrigger>('State 1');

smTrigger?.fire(); } }, ),

);

By pairing VisibilityDetector with Rive’s state machine inputs, each calendar cell only animates when the user can see it. This keeps frame rates smooth and battery usage low without compromising the feel of the experience.

Scaling animations in Flutter is not about limiting creativity. It is about designing with constraints in mind, treating Rive as a living part of the UI while respecting the realities of mobile performance.

Versioning and runtime challenges

Working with Rive at scale also means keeping an eye on runtime compatibility. Designers often use the latest Rive editor builds, which sometimes export features unsupported by the current Flutter package. When that happens, animations that play flawlessly in the editor can crash the app in production. To prevent mismatches, we’ve had to enforce strict version alignment internally, and this has been a recurring challenge.

At the time of writing, our app is running on rive: ^0.13.20, the latest stable release, though it is already nine months old. It lacks some of the newer editor features our designers would like to use. We are keeping a close watch on the upcoming 0.14.0 release, which removes all Dart-based runtime code and replaces it with the C++ runtime under the hood. This promises better performance and closer feature parity with the editor.

Developer takeaways

Building engaging experiences with Rive in Flutter isn’t just about making things move. It is about designing with intent, staying mindful of performance, and creating workflows where designers and developers speak the same language.

Here’s what we’ve learned:

  • Design with performance in mind: Complex motion looks great in Figma or Rive, but not every curve or detail survives on a mid-range phone. Designers need to understand render costs, and developers should share constraints early.
  • Keep Rive files lean: Strip unused artboards, simplify vectors, and avoid bloated files. Smaller files load faster and use less memory.
  • Use state machines thoughtfully: They are powerful, but can become unmanageable. Group related flows together, but don’t overload a single file.
  • Cache smartly: Serve animations from the network when you need flexibility, but cache aggressively to keep load times invisible.
  • Play only what matters: Don’t waste cycles animating off-screen widgets. Pause or stop animations when they’re not in view.
  • Test on real devices: Frame drops or memory spikes often only show up on actual hardware, not emulators.
  • Stay platform-aware: Subtle differences between Android and iOS runtimes exist, always verify animations across both.

The throughline: designers and developers both need awareness of performance versus intent. The best experiences happen when both sides understand trade-offs and work together to shape motion that delights without slowing the product down.

Conclusion

Animations are one of the key elements that shape how users feel an app as much as how they use it. They guide attention, smooth friction, and create the kind of moments that make people remember the product.

At Scapia, Rive and Flutter have given us the tools to make those interactions part of the foundation not just polish at the end. The collaboration between design and engineering becomes tighter when state machines carry intent directly into code.

“With Rive, we can now create lightweight, dynamic animations that respond instantly to user input. It’s transformed how we approach motion-fast to build, seamless to integrate, and delightful to use. Our app now feels more engaging and interactive than ever before.” — Himanshu Garg (Developer)

The result is faster iteration, fewer mismatches, and features that ship with both usability and personality intact. Scaling these experiences in production isn’t automatic. It takes discipline to optimize file sizes, manage performance, and test across platforms. But the payoff is worth it: a product that feels alive, responsive, and enjoyable to use.

That is the standard we have set at Scapia and it is what any Flutter team can aim for when motion becomes part of the language of their app.

ホーム - Wiki
Copyright © 2011-2025 iteam. Current version is 2.147.1. UTC+08:00, 2025-11-08 06:46
浙ICP备14020137号-1 $お客様$