Skip to content

Flutter: draw only when the artboard changed (didChange), like iOS and web #665

Closed

Description

@Michael-Betclic

Submission checklist

  • I have confirmed the issue is present in the latest version of the rive Flutter package
  • I have searched the documentation and forums and could not find an answer
  • I have searched existing issues and this is not a duplicate

Description

With Factory.flutter, FlutterRiveRenderBox calls markNeedsPaint() after every ticker advance while advance returns true; whether the artboard changed is never checked. The C++ runtime already tracks it (Artboard::didChange()), and the other runtimes use it:

  • iOS: RiveView.drawOptimization: DrawOptimization { alwaysDraw, drawOnChanged } (default .drawOnChanged).
  • Web: drawingOptions: DrawOptimizationOptions { AlwaysDraw, DrawOnChanged } (default DrawOnChanged).

Flutter doesn't even expose the flag, so any animation that keeps advancing redraws every frame, including frames where nothing changed.

Patch (ready to re-land)

rive_native has no public repo for a PR, so the series is on a fork, based on rive_native 0.2.0-dev.3 verbatim from pub.dev (4 commits, offered for re-landing with co-authorship as for #631):

What it does:

  • Exports riveArtboardDidChange and adds Artboard.didChange (looked up optionally: binaries without it return true, i.e. always draw).
  • Adds DrawOptimization { alwaysDraw, drawOnChanged } on ProceduralPainter, same names as iOS. Default alwaysDraw = unchanged behaviour.
  • With drawOnChanged, FlutterRiveRenderBox still advances every tick but only repaints when didChange is true or a redraw is forced (first frame, layout, painter swap, scheduleRepaint/fit/alignment, pointer, image decode, last advance before the ticker stops). The box becomes a repaint boundary.
  • Opt-in because a gap in dirt tracking (cf. #14701, opacity 0) would show a stale frame; the default can flip later like iOS/web. Factory.rive isn't covered (texture cleared before advance).

Benchmark (Galaxy Z Flip6, profile, 60 Hz, at rest, 10 s windows)

fps build ms raster ms GPU process CPU
tab bar, alwaysDraw 59.3 3.30 6.92 55 % 80 %
tab bar, drawOnChanged 59.8 1.77 6.78 55 % 71 %
player card, alwaysDraw 59.5 1.63 3.57 17 % 47 %
player card, drawOnChanged 59.8 1.01 2.87 18 % 36 %

Build time roughly halved and ~10 CPU points saved. GPU is unchanged because frames keep coming while the ticker runs (see the related issue).

Tests

New draw_optimization_test.dart 13/13 (fails without the gate); artboard_did_change_test.dart 1 pass / 2 skipped with the prebuilt library (no export; verified with llvm-nm on Android builds and on device); nearby existing suites 63/63 unchanged.

Reproduction steps / code

Any Rive animation that keeps advancing while holding still (e.g. a looping timeline with constant keys), shown with RiveWidget on Factory.flutter: DevTools shows a Rive repaint on every frame although Artboard::didChange() is false.

Rive Flutter package version

rive 0.14.11 (rive_native 0.1.11); patch based on rive_native 0.2.0-dev.3.

Flutter version

Flutter 3.47.3 (stable), Dart 3.13.3

Device

Samsung Galaxy Z Flip6 (SM-F741B)

OS version

Android 16

Additional context

Related: #664 (state machine never settles after the max-iterations cap).

🤖 Generated with Claude Code

Activity

  1. HayesGordon commented on Oct 20, 2026

    @HayesGordon
    Contributor

    The didChange check is something we've been meaning to implement for the Flutter runtime. Looking into it.

  2. added
    knownTriaged: understood and tracked on our side
    on Oct 21, 2026
  3. added
    fixed-pending-releaseFixed and merged; will close when it ships
    and removed
    knownTriaged: understood and tracked on our side
    on Oct 23, 2026
  4. HayesGordon commented on Oct 27, 2026

    @HayesGordon
    Contributor
  5. Michael-Betclic commented on Oct 28, 2026

    @Michael-Betclic
    Author

    Thanks for the update 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    fixed-pending-releaseFixed and merged; will close when it ships

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions